aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/DocServer.ts253
-rw-r--r--src/client/Network.ts11
-rw-r--r--src/client/apis/GoogleAuthenticationManager.tsx119
-rw-r--r--src/client/apis/google_docs/GoogleApiClientUtils.ts164
-rw-r--r--src/client/apis/google_docs/GooglePhotosClientUtils.ts91
-rw-r--r--src/client/apis/gpt/GPT.ts18
-rw-r--r--src/client/apis/youtube/YoutubeBox.scss126
-rw-r--r--src/client/apis/youtube/YoutubeBox.tsx369
-rw-r--r--src/client/cognitive_services/CognitiveServices.ts70
-rw-r--r--src/client/documents/DocFromField.ts50
-rw-r--r--src/client/documents/DocUtils.ts875
-rw-r--r--src/client/documents/DocumentTypes.ts7
-rw-r--r--src/client/documents/Documents.ts1266
-rw-r--r--src/client/goldenLayout.d.ts3
-rw-r--r--src/client/util/BranchingTrailManager.tsx46
-rw-r--r--src/client/util/CalendarManager.tsx58
-rw-r--r--src/client/util/CaptureManager.tsx28
-rw-r--r--src/client/util/CurrentUserUtils.ts328
-rw-r--r--src/client/util/DictationManager.ts123
-rw-r--r--src/client/util/DocumentManager.ts221
-rw-r--r--src/client/util/DragManager.ts205
-rw-r--r--src/client/util/DropActionTypes.ts9
-rw-r--r--src/client/util/DropConverter.ts77
-rw-r--r--src/client/util/GroupManager.tsx113
-rw-r--r--src/client/util/GroupMemberView.tsx36
-rw-r--r--src/client/util/History.ts38
-rw-r--r--src/client/util/HypothesisUtils.ts37
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx1
-rw-r--r--src/client/util/Import & Export/ImageUtils.ts12
-rw-r--r--src/client/util/Import & Export/ImportMetadataEntry.tsx40
-rw-r--r--src/client/util/InteractionUtils.tsx317
-rw-r--r--src/client/util/KeyCodes.ts2
-rw-r--r--src/client/util/LinkFollower.ts46
-rw-r--r--src/client/util/LinkManager.ts90
-rw-r--r--src/client/util/PingManager.ts8
-rw-r--r--src/client/util/RTFMarkup.tsx25
-rw-r--r--src/client/util/ReplayMovements.ts30
-rw-r--r--src/client/util/ScriptManager.ts10
-rw-r--r--src/client/util/Scripting.ts63
-rw-r--r--src/client/util/ScriptingGlobals.ts48
-rw-r--r--src/client/util/SearchUtil.ts26
-rw-r--r--src/client/util/SelectionManager.ts45
-rw-r--r--src/client/util/SerializationHelper.ts33
-rw-r--r--src/client/util/ServerStats.tsx47
-rw-r--r--src/client/util/SettingsManager.tsx420
-rw-r--r--src/client/util/SharingManager.tsx751
-rw-r--r--src/client/util/SnappingManager.ts51
-rw-r--r--src/client/util/TrackMovements.ts24
-rw-r--r--src/client/util/Transform.ts14
-rw-r--r--src/client/util/TypedEvent.ts18
-rw-r--r--src/client/util/UndoManager.ts99
-rw-r--r--src/client/util/bezierFit.ts401
-rw-r--r--src/client/util/reportManager/ReportManager.tsx67
-rw-r--r--src/client/util/reportManager/ReportManagerComponents.tsx131
-rw-r--r--src/client/util/reportManager/reportManagerSchema.ts1
-rw-r--r--src/client/util/reportManager/reportManagerUtils.ts17
-rw-r--r--src/client/util/request-image-size.ts26
-rw-r--r--src/client/views/AntimodeMenu.tsx26
-rw-r--r--src/client/views/ComponentDecorations.tsx7
-rw-r--r--src/client/views/ContextMenu.tsx49
-rw-r--r--src/client/views/ContextMenuItem.tsx42
-rw-r--r--src/client/views/DashboardView.tsx154
-rw-r--r--src/client/views/DictationOverlay.tsx55
-rw-r--r--src/client/views/DocComponent.tsx89
-rw-r--r--src/client/views/DocViewUtils.ts21
-rw-r--r--src/client/views/DocumentButtonBar.tsx159
-rw-r--r--src/client/views/DocumentDecorations.tsx228
-rw-r--r--src/client/views/EditableView.tsx23
-rw-r--r--src/client/views/FieldsDropdown.tsx38
-rw-r--r--src/client/views/FilterPanel.tsx173
-rw-r--r--src/client/views/GestureOverlay.tsx267
-rw-r--r--src/client/views/GlobalKeyHandler.ts196
-rw-r--r--src/client/views/InkControlPtHandles.tsx39
-rw-r--r--src/client/views/InkStrokeProperties.ts85
-rw-r--r--src/client/views/InkTangentHandles.tsx16
-rw-r--r--src/client/views/InkTranscription.tsx1
-rw-r--r--src/client/views/InkingStroke.tsx157
-rw-r--r--src/client/views/KeyphraseQueryView.tsx26
-rw-r--r--src/client/views/LightboxView.tsx138
-rw-r--r--src/client/views/Main.tsx19
-rw-r--r--src/client/views/MainView.tsx254
-rw-r--r--src/client/views/MainViewModal.tsx9
-rw-r--r--src/client/views/MarqueeAnnotator.tsx79
-rw-r--r--src/client/views/MetadataEntryMenu.tsx197
-rw-r--r--src/client/views/ObservableReactComponent.tsx7
-rw-r--r--src/client/views/OverlayView.tsx58
-rw-r--r--src/client/views/PinFuncs.ts139
-rw-r--r--src/client/views/PreviewCursor.tsx21
-rw-r--r--src/client/views/PropertiesButtons.tsx212
-rw-r--r--src/client/views/PropertiesDocBacklinksSelector.tsx12
-rw-r--r--src/client/views/PropertiesDocContextSelector.tsx15
-rw-r--r--src/client/views/PropertiesSection.tsx60
-rw-r--r--src/client/views/PropertiesView.tsx1157
-rw-r--r--src/client/views/ScriptBox.tsx47
-rw-r--r--src/client/views/ScriptingRepl.tsx199
-rw-r--r--src/client/views/SidebarAnnos.tsx37
-rw-r--r--src/client/views/StyleProp.ts24
-rw-r--r--src/client/views/StyleProvider.tsx269
-rw-r--r--src/client/views/TemplateMenu.tsx73
-rw-r--r--src/client/views/Touchable.tsx213
-rw-r--r--src/client/views/UndoStack.tsx19
-rw-r--r--src/client/views/animationtimeline/Timeline.tsx63
-rw-r--r--src/client/views/animationtimeline/TimelineMenu.tsx3
-rw-r--r--src/client/views/animationtimeline/TimelineOverview.tsx16
-rw-r--r--src/client/views/animationtimeline/Track.tsx2
-rw-r--r--src/client/views/collections/CollectionCalendarView.tsx34
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx104
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx16
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx210
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx101
-rw-r--r--src/client/views/collections/CollectionMenu.scss2
-rw-r--r--src/client/views/collections/CollectionMenu.tsx174
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx115
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewColumn.tsx53
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewDivider.tsx5
-rw-r--r--src/client/views/collections/CollectionPileView.tsx40
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx180
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx95
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx92
-rw-r--r--src/client/views/collections/CollectionSubView.tsx215
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx44
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx126
-rw-r--r--src/client/views/collections/CollectionTreeViewType.ts5
-rw-r--r--src/client/views/collections/CollectionView.tsx102
-rw-r--r--src/client/views/collections/KeyRestrictionRow.tsx32
-rw-r--r--src/client/views/collections/TabDocView.tsx604
-rw-r--r--src/client/views/collections/TreeView.tsx382
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts223
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx31
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx70
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx62
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx25
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx76
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx997
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx12
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx82
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx58
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.tsx77
-rw-r--r--src/client/views/collections/collectionLinear/index.ts2
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx57
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx24
-rw-r--r--src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx26
-rw-r--r--src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx2
-rw-r--r--src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx3
-rw-r--r--src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx28
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx249
-rw-r--r--src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx21
-rw-r--r--src/client/views/collections/collectionSchema/SchemaRowBox.tsx15
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx60
-rw-r--r--src/client/views/global/globalEnums.tsx57
-rw-r--r--src/client/views/global/globalScripts.ts210
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx31
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx77
-rw-r--r--src/client/views/linking/LinkPopup.tsx22
-rw-r--r--src/client/views/linking/LinkRelationshipSearch.tsx63
-rw-r--r--src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx5
-rw-r--r--src/client/views/newlightbox/ExploreView/index.ts2
-rw-r--r--src/client/views/newlightbox/ExploreView/utils.ts21
-rw-r--r--src/client/views/newlightbox/Header/LightboxHeader.tsx53
-rw-r--r--src/client/views/newlightbox/Header/index.ts2
-rw-r--r--src/client/views/newlightbox/NewLightboxView.tsx368
-rw-r--r--src/client/views/newlightbox/RecommendationList/index.ts2
-rw-r--r--src/client/views/newlightbox/RecommendationList/utils.ts11
-rw-r--r--src/client/views/newlightbox/components/EditableText/index.ts2
-rw-r--r--src/client/views/newlightbox/components/Recommendation/Recommendation.tsx12
-rw-r--r--src/client/views/newlightbox/components/Recommendation/index.ts4
-rw-r--r--src/client/views/newlightbox/components/Recommendation/utils.ts38
-rw-r--r--src/client/views/newlightbox/components/SkeletonDoc/index.ts2
-rw-r--r--src/client/views/newlightbox/components/Template/index.ts2
-rw-r--r--src/client/views/newlightbox/components/index.ts6
-rw-r--r--src/client/views/newlightbox/utils.ts7
-rw-r--r--src/client/views/nodes/AudioBox.tsx151
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx107
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx121
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.tsx174
-rw-r--r--src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx65
-rw-r--r--src/client/views/nodes/DataVizBox/components/Histogram.tsx247
-rw-r--r--src/client/views/nodes/DataVizBox/components/LineChart.tsx153
-rw-r--r--src/client/views/nodes/DataVizBox/components/PieChart.tsx216
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx116
-rw-r--r--src/client/views/nodes/DataVizBox/utils/D3Utils.ts29
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx49
-rw-r--r--src/client/views/nodes/DocumentIcon.tsx70
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx62
-rw-r--r--src/client/views/nodes/DocumentView.tsx564
-rw-r--r--src/client/views/nodes/EquationBox.tsx31
-rw-r--r--src/client/views/nodes/FaceRectangle.tsx10
-rw-r--r--src/client/views/nodes/FieldView.tsx45
-rw-r--r--src/client/views/nodes/FocusViewOptions.ts24
-rw-r--r--src/client/views/nodes/FontIconBox/ButtonInterface.ts2
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx134
-rw-r--r--src/client/views/nodes/FontIconBox/TrailsIcon.tsx86
-rw-r--r--src/client/views/nodes/FunctionPlotBox.tsx20
-rw-r--r--src/client/views/nodes/ImageBox.tsx163
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx123
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx24
-rw-r--r--src/client/views/nodes/LabelBox.tsx46
-rw-r--r--src/client/views/nodes/LinkAnchorBox.scss34
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx115
-rw-r--r--src/client/views/nodes/LinkBox.tsx109
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.tsx36
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx96
-rw-r--r--src/client/views/nodes/LoadingBox.tsx27
-rw-r--r--src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx39
-rw-r--r--src/client/views/nodes/MapBox/AnimationUtility.ts59
-rw-r--r--src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx18
-rw-r--r--src/client/views/nodes/MapBox/GeocoderControl.tsx7
-rw-r--r--src/client/views/nodes/MapBox/MapAnchorMenu.tsx106
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx362
-rw-r--r--src/client/views/nodes/MapBox/MapBox2.tsx6
-rw-r--r--src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx2
-rw-r--r--src/client/views/nodes/MapBox/MapPushpinBox.tsx7
-rw-r--r--src/client/views/nodes/MapBox/MapboxApiUtility.ts76
-rw-r--r--src/client/views/nodes/MapBox/MarkerIcons.tsx3
-rw-r--r--src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx151
-rw-r--r--src/client/views/nodes/OpenWhere.ts25
-rw-r--r--src/client/views/nodes/PDFBox.scss2
-rw-r--r--src/client/views/nodes/PDFBox.tsx172
-rw-r--r--src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx383
-rw-r--r--src/client/views/nodes/RadialMenu.tsx140
-rw-r--r--src/client/views/nodes/RadialMenuItem.tsx4
-rw-r--r--src/client/views/nodes/RecordingBox/ProgressBar.tsx205
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingBox.tsx80
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingView.tsx43
-rw-r--r--src/client/views/nodes/RecordingBox/index.ts4
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx65
-rw-r--r--src/client/views/nodes/ScriptingBox.tsx171
-rw-r--r--src/client/views/nodes/SliderBox-components.tsx122
-rw-r--r--src/client/views/nodes/TaskCompletedBox.tsx2
-rw-r--r--src/client/views/nodes/VideoBox.tsx211
-rw-r--r--src/client/views/nodes/WebBox.tsx228
-rw-r--r--src/client/views/nodes/WebBoxRenderer.js133
-rw-r--r--src/client/views/nodes/audio/AudioWaveform.tsx8
-rw-r--r--src/client/views/nodes/audio/WaveCanvas.tsx12
-rw-r--r--src/client/views/nodes/calendarBox/CalendarBox.tsx17
-rw-r--r--src/client/views/nodes/formattedText/DashDocCommentView.tsx159
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx127
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx319
-rw-r--r--src/client/views/nodes/formattedText/EquationEditor.tsx3
-rw-r--r--src/client/views/nodes/formattedText/EquationView.tsx93
-rw-r--r--src/client/views/nodes/formattedText/FootnoteView.tsx8
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss5
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx626
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx33
-rw-r--r--src/client/views/nodes/formattedText/OrderedListView.tsx9
-rw-r--r--src/client/views/nodes/formattedText/ParagraphNodeSpec.ts100
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts129
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx281
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts144
-rw-r--r--src/client/views/nodes/formattedText/SummaryView.tsx29
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts43
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts37
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFill.tsx133
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx10
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts6
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts8
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts36
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts18
-rw-r--r--src/client/views/nodes/importBox/ImportElementBox.tsx5
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx951
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx118
-rw-r--r--src/client/views/nodes/trails/index.ts6
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx48
-rw-r--r--src/client/views/pdf/Annotation.tsx168
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx109
-rw-r--r--src/client/views/pdf/PDFViewer.tsx92
-rw-r--r--src/client/views/search/SearchBox.tsx274
-rw-r--r--src/client/views/selectedDoc/SelectedDocView.tsx18
-rw-r--r--src/client/views/selectedDoc/index.ts2
-rw-r--r--src/client/views/topbar/TopBar.tsx38
-rw-r--r--src/client/views/webcam/DashWebRTCVideo.scss82
-rw-r--r--src/client/views/webcam/DashWebRTCVideo.tsx76
-rw-r--r--src/client/views/webcam/WebCamLogic.js292
274 files changed, 14691 insertions, 14782 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 321572071..ac865382d 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,20 +1,15 @@
import { runInAction } from 'mobx';
-import * as rp from 'request-promise';
-import { Doc, DocListCast, Opt } from '../fields/Doc';
+import { Socket, io } from 'socket.io-client';
+import { ClientUtils } from '../ClientUtils';
+import { Utils, emptyFunction } from '../Utils';
+import { Doc, Opt } from '../fields/Doc';
import { UpdatingFromServer } from '../fields/DocSymbols';
import { FieldLoader } from '../fields/FieldLoader';
import { HandleUpdate, Id, Parent } from '../fields/FieldSymbols';
-import { ObjectField } from '../fields/ObjectField';
+import { ObjectField, SetObjGetRefField, SetObjGetRefFields } from '../fields/ObjectField';
import { RefField } from '../fields/RefField';
-import { DocCast, StrCast } from '../fields/Types';
-//import MobileInkOverlay from '../mobile/MobileInkOverlay';
-import { io, Socket } from 'socket.io-client';
-import { emptyFunction, Utils } from '../Utils';
-import { GestureContent, Message, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from './../server/Message';
-import { DocumentType } from './documents/DocumentTypes';
-import { LinkManager } from './util/LinkManager';
+import { GestureContent, Message, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from '../server/Message';
import { SerializationHelper } from './util/SerializationHelper';
-//import { GestureOverlay } from './views/GestureOverlay';
/**
* This class encapsulates the transfer and cross-client synchronization of
@@ -30,86 +25,53 @@ import { SerializationHelper } from './util/SerializationHelper';
* or update ourselves based on the server's update message, that occurs here
*/
export namespace DocServer {
+ // eslint-disable-next-line import/no-mutable-exports
let _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
+ export function Cache() {
+ return _cache;
+ }
+
+ function errorFunc(): never {
+ throw new Error("Can't use DocServer without calling init first");
+ }
+ let _UpdateField: (id: string, diff: any) => void = errorFunc;
+ let _CreateField: (field: RefField) => void = errorFunc;
export function AddServerHandler<T>(socket: Socket, message: Message<T>, handler: (args: T) => any) {
socket.on(message.Message, Utils.loggingCallback('Incoming', handler, message.Name));
}
export function Emit<T>(socket: Socket, message: Message<T>, args: T) {
- //log('Emit', message.Name, args, false);
+ // log('Emit', message.Name, args, false);
socket.emit(message.Message, args);
}
export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T): Promise<any>;
export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T, fn: (args: any) => any): void;
export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T, fn?: (args: any) => any): void | Promise<any> {
- //log('Emit', message.Name, args, false);
+ // log('Emit', message.Name, args, false);
if (fn) {
socket.emit(message.Message, args, Utils.loggingCallback('Receiving', fn, message.Name));
} else {
- return new Promise<any>(res => socket.emit(message.Message, args, Utils.loggingCallback('Receiving', res, message.Name)));
+ return new Promise<any>(res => {
+ socket.emit(message.Message, args, Utils.loggingCallback('Receiving', res, message.Name));
+ });
}
}
- export function FindDocByTitle(title: string) {
- const foundDocId =
- title &&
- Array.from(Object.keys(_cache))
- .filter(key => _cache[key] instanceof Doc)
- .find(key => (_cache[key] as Doc).title === title);
-
- return foundDocId ? (_cache[foundDocId] as Doc) : undefined;
- }
- let cacheDocumentIds = ''; // ; separate string of all documents ids in the user's working set (cached on the server)
- export let CacheNeedsUpdate = false;
- export function UPDATE_SERVER_CACHE() {
- const prototypes = Object.values(DocumentType)
- .filter(type => type !== DocumentType.NONE)
- .map(type => _cache[type + 'Proto'])
- .filter(doc => doc instanceof Doc)
- .map(doc => doc as Doc);
- const references = new Set<Doc>(prototypes);
- Doc.FindReferences(Doc.UserDoc(), references, undefined);
- DocListCast(DocCast(Doc.UserDoc().myLinkDatabase).data).forEach(link => {
- if (!references.has(DocCast(link.link_anchor_1)) && !references.has(DocCast(link.link_anchor_2))) {
- Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myLinkDatabase), 'data', link);
- Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link);
- }
- });
- LinkManager.Instance.userLinkDBs.forEach(linkDb => Doc.FindReferences(linkDb, references, undefined));
- const filtered = Array.from(references);
-
- const newCacheUpdate = filtered.map(doc => doc[Id]).join(';');
- if (newCacheUpdate === cacheDocumentIds) return;
- cacheDocumentIds = newCacheUpdate;
-
- // print out cached docs
- Doc.MyDockedBtns.linearView_IsOpen && console.log('Set cached docs = ');
- const is_filtered = filtered.filter(doc => !Doc.IsSystem(doc));
- const strings = is_filtered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)'));
- Doc.MyDockedBtns.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
-
- rp.post(Utils.prepend('/setCacheDocumentIds'), {
- body: {
- cacheDocumentIds,
- },
- json: true,
- });
- }
- export let _socket: Socket;
+ let _socket: Socket;
// this client's distinct GUID created at initialization
let USER_ID: string;
// indicates whether or not a document is currently being udpated, and, if so, its id
export enum WriteMode {
- Default = 0, //Anything goes
- Playground = 1, //Playground (write own/no read other updates)
- LiveReadonly = 2, //Live Readonly (no write/read others)
- LivePlayground = 3, //Live Playground (write own/read others)
+ Default = 0, // Anything goes
+ Playground = 1, // Playground (write own/no read other updates)
+ LiveReadonly = 2, // Live Readonly (no write/read others)
+ LivePlayground = 3, // Live Playground (write own/read others)
}
const fieldWriteModes: { [field: string]: WriteMode } = {};
const docsWithUpdates: { [field: string]: Set<Doc> } = {};
- export var PlaygroundFields: string[] = [];
+ export const PlaygroundFields: string[] = [];
export function setLivePlaygroundFields(livePlaygroundFields: string[]) {
DocServer.PlaygroundFields.push(...livePlaygroundFields);
livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground));
@@ -134,7 +96,7 @@ export namespace DocServer {
}
export function getFieldWriteMode(field: string) {
- return Doc.CurrentUserEmail === 'guest' ? WriteMode.LivePlayground : fieldWriteModes[field] || WriteMode.Default;
+ return ClientUtils.CurrentUserEmail() === 'guest' ? WriteMode.LivePlayground : fieldWriteModes[field] || WriteMode.Default;
}
export function registerDocWithCachedUpdate(doc: Doc, field: string, oldValue: any) {
@@ -185,56 +147,14 @@ export namespace DocServer {
window.location.reload();
}
- export function init(protocol: string, hostname: string, port: number, identifier: string) {
- _cache = {};
- USER_ID = identifier;
- protocol = protocol.startsWith('https') ? 'wss' : 'ws';
- _socket = io(`${protocol}://${hostname}:${port}`, { transports: ['websocket'], rejectUnauthorized: false });
- _socket.on("connect_error", (err:any) => console.log(err));
- // io.connect(`https://7f079dda.ngrok.io`);// if using ngrok, create a special address for the websocket
-
- _GetCachedRefField = _GetCachedRefFieldImpl;
- _GetRefField = _GetRefFieldImpl;
- _GetRefFields = _GetRefFieldsImpl;
- _CreateField = _CreateFieldImpl;
- _UpdateField = _UpdateFieldImpl;
-
- /**
- * Whenever the server sends us its handshake message on our
- * websocket, we use the above function to return the handshake.
- */
- DocServer.AddServerHandler(_socket, MessageStore.Foo, onConnection);
- DocServer.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate);
- DocServer.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete);
- DocServer.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
- DocServer.AddServerHandler(_socket, MessageStore.ConnectionTerminated, alertUser);
-
- // // mobile ink overlay socket events to communicate between mobile view and desktop view
- // _socket.addEventListener('receiveGesturePoints', (content: GestureContent) => {
- // // MobileInkOverlay.Instance.drawStroke(content);
- // });
- // _socket.addEventListener('receiveOverlayTrigger', (content: MobileInkOverlayContent) => {
- // //GestureOverlay.Instance.enableMobileInkOverlay(content);
- // // MobileInkOverlay.Instance.initMobileInkOverlay(content);
- // });
- // _socket.addEventListener('receiveUpdateOverlayPosition', (content: UpdateMobileInkOverlayPositionContent) => {
- // // MobileInkOverlay.Instance.updatePosition(content);
- // });
- // _socket.addEventListener('receiveMobileDocumentUpload', (content: MobileDocumentUploadContent) => {
- // // MobileInkOverlay.Instance.uploadDocument(content);
- // });
- }
-
- function errorFunc(): never {
- throw new Error("Can't use DocServer without calling init first");
- }
-
export namespace Control {
let _isReadOnly = false;
export function makeReadOnly() {
if (!_isReadOnly) {
_isReadOnly = true;
- _CreateField = field => (_cache[field[Id]] = field);
+ _CreateField = field => {
+ _cache[field[Id]] = field;
+ };
_UpdateField = emptyFunction;
// _RespondToUpdate = emptyFunction; // bcz: option: don't clear RespondToUpdate to continue to receive updates as others change the DB
}
@@ -302,7 +222,7 @@ export namespace DocServer {
const deserializeField = getSerializedField.then(async fieldJson => {
// deserialize
const field = await SerializationHelper.Deserialize(fieldJson);
- if (force && field instanceof Doc && cached instanceof Doc) {
+ if (force && field && cached instanceof Doc) {
cached[UpdatingFromServer] = true;
Array.from(Object.keys(field)).forEach(key => {
const fieldval = field[key];
@@ -313,7 +233,8 @@ export namespace DocServer {
});
cached[UpdatingFromServer] = false;
return cached;
- } else if (field !== undefined) {
+ }
+ if (field !== undefined) {
_cache[id] = field;
} else {
delete _cache[id];
@@ -327,24 +248,25 @@ export namespace DocServer {
// being retrieved and cached
!force && (_cache[id] = deserializeField);
return force ? (cached as any) : deserializeField;
- } else if (cached instanceof Promise) {
+ }
+ if (cached instanceof Promise) {
// BEING RETRIEVED AND CACHED => some other caller previously (likely recently) called GetRefField(s),
// and requested the document I'm looking for. Shouldn't fetch again, just
// return this promise which will resolve to the field itself (see 7)
return cached;
- } else {
- // CACHED => great, let's just return the cached field we have
- return Promise.resolve(cached).then(field => {
- //(field instanceof Doc) && fetchProto(field);
- return field;
- });
}
+ // CACHED => great, let's just return the cached field we have
+ return Promise.resolve(cached).then(
+ field => field
+ // (field instanceof Doc) && fetchProto(field);
+ );
};
const _GetCachedRefFieldImpl = (id: string): Opt<RefField> => {
const cached = _cache[id];
if (cached !== undefined && !(cached instanceof Promise)) {
return cached;
}
+ return undefined;
};
let _GetRefField: (id: string, force: boolean) => Promise<Opt<RefField>> = errorFunc;
@@ -358,7 +280,7 @@ export namespace DocServer {
}
export async function getYoutubeChannels() {
- return await DocServer.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.Channels });
+ return DocServer.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.Channels });
}
export function getYoutubeVideos(videoTitle: string, callBack: (videos: any[]) => void) {
@@ -379,21 +301,24 @@ export namespace DocServer {
const requestedIds: string[] = [];
const promises: Promise<any>[] = [];
- let defaultRes: any = undefined;
- const defaultPromise = new Promise<any>(res => (defaultRes = res));
- let defaultPromises: { p: Promise<any>; id: string }[] = [];
+ let defaultRes: any;
+ const defaultPromise = new Promise<any>(res => {
+ defaultRes = res;
+ });
+ const defaultPromises: { p: Promise<any>; id: string }[] = [];
// 1) an initial pass through the cache to determine
// i) which documents need to be fetched
// ii) which are already in the process of being fetched
// iii) which already exist in the cache
- for (const id of ids.filter(id => id)) {
+ // eslint-disable-next-line no-restricted-syntax
+ for (const id of ids.filter(filterid => filterid)) {
const cached = _cache[id];
if (cached === undefined) {
defaultPromises.push({
id,
- p: (_cache[id] = new Promise<any>(async res => {
- await defaultPromise;
- res(_cache[id]);
+ // eslint-disable-next-line no-loop-func
+ p: (_cache[id] = new Promise<any>(res => {
+ defaultPromise.then(() => res(_cache[id]));
})),
});
// NOT CACHED => we'll have to send a request to the server
@@ -415,7 +340,11 @@ export namespace DocServer {
// fields for the given ids. This returns a promise, which, when resolved, indicates that all the JSON serialized versions of
// the fields have been returned from the server
console.log('Requesting ' + requestedIds.length);
- setTimeout(() => runInAction(() => (FieldLoader.ServerLoadStatus.requested = requestedIds.length)));
+ setTimeout(() =>
+ runInAction(() => {
+ FieldLoader.ServerLoadStatus.requested = requestedIds.length;
+ })
+ );
const serializedFields = await DocServer.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds);
// 3) when the serialized RefFields have been received, go head and begin deserializing them into objects.
@@ -423,19 +352,27 @@ export namespace DocServer {
// future .proto calls on the Doc won't have to go farther than the cache to get their actual value.
let processed = 0;
console.log('deserializing ' + serializedFields.length + ' fields');
+ // eslint-disable-next-line no-restricted-syntax
for (const field of serializedFields) {
processed++;
if (processed % 150 === 0) {
- runInAction(() => (FieldLoader.ServerLoadStatus.retrieved = processed));
- await new Promise(res => setTimeout(res)); // force loading to yield to splash screen rendering to update progress
+ // eslint-disable-next-line no-loop-func
+ runInAction(() => {
+ FieldLoader.ServerLoadStatus.retrieved = processed;
+ });
+ // eslint-disable-next-line no-await-in-loop
+ await new Promise(res => {
+ setTimeout(res);
+ }); // force loading to yield to splash screen rendering to update progress
}
const cached = _cache[field.id];
if (!cached || (cached instanceof Promise && defaultPromises.some(dp => dp.p === cached))) {
// deserialize
// adds to a list of promises that will be awaited asynchronously
promises.push(
+ // eslint-disable-next-line no-loop-func
(_cache[field.id] = SerializationHelper.Deserialize(field).then(deserialized => {
- //overwrite or delete any promises (that we inserted as flags
+ // overwrite or delete any promises (that we inserted as flags
// to indicate that the field was in the process of being fetched). Now everything
// should be an actual value within or entirely absent from the cache.
if (deserialized !== undefined) {
@@ -455,7 +392,7 @@ export namespace DocServer {
// get the resolved field
} else if (cached instanceof Promise) {
console.log('.');
- //promises.push(cached);
+ // promises.push(cached);
} else if (field) {
// console.log('-');
}
@@ -472,7 +409,7 @@ export namespace DocServer {
// 6) with this confidence, we can now go through and update the cache at the ids of the fields that
// we explicitly had to fetch. To finish it off, we add whatever value we've come up with for a given
// id to the soon-to-be-returned field mapping.
- //ids.forEach(id => (map[id] = _cache[id] as any));
+ // ids.forEach(id => (map[id] = _cache[id] as any));
// 7) those promises we encountered in the else if of 1), which represent
// other callers having already submitted a request to the server for (a) document(s)
@@ -481,7 +418,7 @@ export namespace DocServer {
//
// fortunately, those other callers will also hit their own version of 6) and clean up
// the shared cache when these promises resolve, so all we have to do is...
- //const otherCallersFetching = await Promise.all(promises);
+ // const otherCallersFetching = await Promise.all(promises);
// ...extract the RefFields returned from the resolution of those promises and add them to our
// own map.
// waitingIds.forEach((id, index) => (map[id] = otherCallersFetching[index]));
@@ -506,6 +443,10 @@ export namespace DocServer {
}
// WRITE A NEW DOCUMENT TO THE SERVER
+ let _cacheNeedsUpdate = false;
+ export function CacheNeedsUpdate() {
+ return _cacheNeedsUpdate;
+ }
/**
* A wrapper around the function local variable _createField.
@@ -514,18 +455,16 @@ export namespace DocServer {
* @param field the [RefField] to be serialized and sent to the server to be stored in the database
*/
export function CreateField(field: RefField) {
- CacheNeedsUpdate = true;
+ _cacheNeedsUpdate = true;
_CreateField(field);
}
function _CreateFieldImpl(field: RefField) {
_cache[field[Id]] = field;
const initialState = SerializationHelper.Serialize(field);
- Doc.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.CreateField, initialState);
+ ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Emit(_socket, MessageStore.CreateField, initialState);
}
- let _CreateField: (field: RefField) => void = errorFunc;
-
// NOTIFY THE SERVER OF AN UPDATE TO A DOC'S STATE
/**
@@ -541,13 +480,11 @@ export namespace DocServer {
}
function _UpdateFieldImpl(id: string, diff: any) {
- !DocServer.Control.isReadOnly() && Doc.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.UpdateField, { id, diff });
+ !DocServer.Control.isReadOnly() && ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Emit(_socket, MessageStore.UpdateField, { id, diff });
}
- let _UpdateField: (id: string, diff: any) => void = errorFunc;
-
function _respondToUpdateImpl(diff: any) {
- const id = diff.id;
+ const { id } = diff;
// to be valid, the Diff object must reference
// a document's id
if (id === undefined) {
@@ -578,11 +515,11 @@ export namespace DocServer {
}
export function DeleteDocument(id: string) {
- Doc.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteField, id);
+ ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteField, id);
}
export function DeleteDocuments(ids: string[]) {
- Doc.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteFields, ids);
+ ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteFields, ids);
}
function _respondToDeleteImpl(ids: string | string[]) {
@@ -596,7 +533,7 @@ export namespace DocServer {
}
}
- let _RespondToUpdate = _respondToUpdateImpl;
+ const _RespondToUpdate = _respondToUpdateImpl;
const _respondToDelete = _respondToDeleteImpl;
function respondToUpdate(diff: any) {
@@ -606,4 +543,28 @@ export namespace DocServer {
function respondToDelete(ids: string | string[]) {
_respondToDelete(ids);
}
+
+ export function init(protocol: string, hostname: string, port: number, identifier: string) {
+ _cache = {};
+ USER_ID = identifier;
+ _socket = io(`${protocol.startsWith('https') ? 'wss' : 'ws'}://${hostname}:${port}`, { transports: ['websocket'], rejectUnauthorized: false });
+ _socket.on('connect_error', (err: any) => console.log(err));
+ // io.connect(`https://7f079dda.ngrok.io`);// if using ngrok, create a special address for the websocket
+
+ _GetCachedRefField = _GetCachedRefFieldImpl;
+ SetObjGetRefField((_GetRefField = _GetRefFieldImpl));
+ SetObjGetRefFields((_GetRefFields = _GetRefFieldsImpl));
+ _CreateField = _CreateFieldImpl;
+ _UpdateField = _UpdateFieldImpl;
+
+ /**
+ * Whenever the server sends us its handshake message on our
+ * websocket, we use the above function to return the handshake.
+ */
+ DocServer.AddServerHandler(_socket, MessageStore.Foo, onConnection);
+ DocServer.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate);
+ DocServer.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete);
+ DocServer.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
+ DocServer.AddServerHandler(_socket, MessageStore.ConnectionTerminated, alertUser);
+ }
}
diff --git a/src/client/Network.ts b/src/client/Network.ts
index cdcd5225a..968c407b2 100644
--- a/src/client/Network.ts
+++ b/src/client/Network.ts
@@ -1,5 +1,6 @@
-import { Utils } from '../Utils';
import * as requestPromise from 'request-promise';
+import { ClientUtils } from '../ClientUtils';
+import { Utils } from '../Utils';
import { Upload } from '../server/SharedMediaTypes';
/**
@@ -14,7 +15,7 @@ export namespace Networking {
export async function PostToServer(relativeRoute: string, body?: any) {
const options = {
- uri: Utils.prepend(relativeRoute),
+ uri: ClientUtils.prepend(relativeRoute),
method: 'POST',
body,
json: true,
@@ -49,14 +50,14 @@ export namespace Networking {
}
const maxFileSize = 50000000;
if (fileguidpairs.some(f => f.file.size > maxFileSize)) {
- return new Promise<any>(res =>
+ return new Promise<any>(res => {
res([
{
source: { name: '', type: '', size: 0, toJson: () => ({ name: '', type: '' }) },
result: { name: '', message: `max file size (${maxFileSize / 1000000}MB) exceeded` },
},
- ])
- );
+ ]);
+ });
}
formData.set('fileguids', fileguidpairs.map(pair => pair.guid).join(';'));
formData.set('filesize', fileguidpairs.reduce((sum, pair) => sum + pair.file.size, 0).toString());
diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
index 855f48f7e..5269f763b 100644
--- a/src/client/apis/GoogleAuthenticationManager.tsx
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -1,14 +1,14 @@
-import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { Opt } from "../../fields/Doc";
-import { Networking } from "../Network";
-import { ScriptingGlobals } from "../util/ScriptingGlobals";
-import { MainViewModal } from "../views/MainViewModal";
-import "./GoogleAuthenticationManager.scss";
+import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Opt } from '../../fields/Doc';
+import { Networking } from '../Network';
+import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { MainViewModal } from '../views/MainViewModal';
+import './GoogleAuthenticationManager.scss';
-const AuthenticationUrl = "https://accounts.google.com/o/oauth2/v2/auth";
-const prompt = "Paste authorization code here...";
+const AuthenticationUrl = 'https://accounts.google.com/o/oauth2/v2/auth';
+const prompt = 'Paste authorization code here...';
@observer
export class GoogleAuthenticationManager extends React.Component<{}> {
@@ -23,11 +23,11 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
private disposer: Opt<IReactionDisposer>;
private set isOpen(value: boolean) {
- runInAction(() => this.openState = value);
+ runInAction(() => (this.openState = value));
}
private set shouldShowPasteTarget(value: boolean) {
- runInAction(() => this.showPasteTargetState = value);
+ runInAction(() => (this.showPasteTargetState = value));
}
public cancel() {
@@ -35,7 +35,7 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
}
public fetchOrGenerateAccessToken = async (displayIfFound = false) => {
- let response: any = await Networking.FetchFromServer("/readGoogleAccessToken");
+ let response: any = await Networking.FetchFromServer('/readGoogleAccessToken');
// if this is an authentication url, activate the UI to register the new access token
if (new RegExp(AuthenticationUrl).test(response)) {
this.isOpen = true;
@@ -47,7 +47,7 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
async authenticationCode => {
if (authenticationCode && /\d{1}\/[\w-]{55}/.test(authenticationCode)) {
this.disposer?.();
- const response = await Networking.PostToServer("/writeGoogleAccessToken", { authenticationCode });
+ const response = await Networking.PostToServer('/writeGoogleAccessToken', { authenticationCode });
runInAction(() => {
this.success = true;
this.credentials = response;
@@ -71,7 +71,7 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
this.isOpen = true;
}
return response.access_token;
- }
+ };
resetState = action((visibleForMS: number = 3000, fadesOutInMS: number = 500) => {
if (!visibleForMS && !fadesOutInMS) {
@@ -89,14 +89,20 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
this.displayLauncher = false;
this.shouldShowPasteTarget = false;
if (visibleForMS > 0 && fadesOutInMS > 0) {
- setTimeout(action(() => {
- this.isOpen = false;
- setTimeout(action(() => {
- this.success = undefined;
- this.displayLauncher = true;
- this.credentials = undefined;
- }), fadesOutInMS);
- }), visibleForMS);
+ setTimeout(
+ action(() => {
+ this.isOpen = false;
+ setTimeout(
+ action(() => {
+ this.success = undefined;
+ this.displayLauncher = true;
+ this.credentials = undefined;
+ }),
+ fadesOutInMS
+ );
+ }),
+ visibleForMS
+ );
}
});
@@ -108,61 +114,44 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
private get renderPrompt() {
return (
<div className={'authorize-container'}>
-
- {this.displayLauncher ? <button
- className={"dispatch"}
- onClick={() => {
- window.open(this.authenticationLink);
- setTimeout(() => this.shouldShowPasteTarget = true, 500);
- }}
- style={{ marginBottom: this.showPasteTargetState ? 15 : 0 }}
- >Authorize a Google account...</button> : (null)}
- {this.showPasteTargetState ? <input
- className={'paste-target'}
- onChange={action(e => this.authenticationCode = e.currentTarget.value)}
- placeholder={prompt}
- /> : (null)}
- {this.credentials ?
+ {this.displayLauncher ? (
+ <button
+ className={'dispatch'}
+ onClick={() => {
+ window.open(this.authenticationLink);
+ setTimeout(() => (this.shouldShowPasteTarget = true), 500);
+ }}
+ style={{ marginBottom: this.showPasteTargetState ? 15 : 0 }}>
+ Authorize a Google account...
+ </button>
+ ) : null}
+ {this.showPasteTargetState ? <input className={'paste-target'} onChange={action(e => (this.authenticationCode = e.currentTarget.value))} placeholder={prompt} /> : null}
+ {this.credentials ? (
<>
- <img
- className={'avatar'}
- src={this.credentials.userInfo.picture}
- />
- <span
- className={'welcome'}
- >Welcome to Dash, {this.credentials.userInfo.name}
- </span>
+ <img className={'avatar'} src={this.credentials.userInfo.picture} />
+ <span className={'welcome'}>Welcome to Dash, {this.credentials.userInfo.name}</span>
<div
className={'disconnect'}
onClick={async () => {
- await Networking.FetchFromServer("/revokeGoogleAccessToken");
+ await Networking.FetchFromServer('/revokeGoogleAccessToken');
this.resetState(0, 0);
- }}
- >Disconnect Account</div>
- </> : (null)}
+ }}>
+ Disconnect Account
+ </div>
+ </>
+ ) : null}
</div>
);
}
private get dialogueBoxStyle() {
- const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red";
- return { borderColor, transition: "0.2s borderColor ease", zIndex: 1002 };
+ const borderColor = this.success === undefined ? 'black' : this.success ? 'green' : 'red';
+ return { borderColor, transition: '0.2s borderColor ease', zIndex: 1002 };
}
render() {
- return (
- <MainViewModal
- isDisplayed={this.openState}
- interactive={true}
- contents={this.renderPrompt}
- // overlayDisplayedOpacity={0.9}
- dialogueBoxStyle={this.dialogueBoxStyle}
- overlayStyle={{ zIndex: 1001 }}
- closeOnExternalClick={action(() => this.isOpen = false)}
- />
- );
+ return <MainViewModal isDisplayed={this.openState} interactive={true} contents={this.renderPrompt} dialogueBoxStyle={this.dialogueBoxStyle} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={action(() => (this.isOpen = false))} />;
}
-
}
-ScriptingGlobals.add("GoogleAuthenticationManager", GoogleAuthenticationManager); \ No newline at end of file
+ScriptingGlobals.add('GoogleAuthenticationManager', GoogleAuthenticationManager);
diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index c8f381cc0..0b303eacf 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -1,45 +1,46 @@
-import { docs_v1 } from "googleapis";
-import { Opt } from "../../../fields/Doc";
-import { isArray } from "util";
-import { EditorState } from "prosemirror-state";
-import { Networking } from "../../Network";
+/* eslint-disable no-restricted-syntax */
+/* eslint-disable no-use-before-define */
+import { docs_v1 as docsV1 } from 'googleapis';
+// eslint-disable-next-line node/no-deprecated-api
+import { isArray } from 'util';
+import { EditorState } from 'prosemirror-state';
+import { Opt } from '../../../fields/Doc';
+import { Networking } from '../../Network';
-export const Pulls = "googleDocsPullCount";
-export const Pushes = "googleDocsPushCount";
+export const Pulls = 'googleDocsPullCount';
+export const Pushes = 'googleDocsPushCount';
export namespace GoogleApiClientUtils {
-
export enum Actions {
- Create = "create",
- Retrieve = "retrieve",
- Update = "update"
+ Create = 'create',
+ Retrieve = 'retrieve',
+ Update = 'update',
}
export namespace Docs {
-
- export type RetrievalResult = Opt<docs_v1.Schema$Document>;
- export type UpdateResult = Opt<docs_v1.Schema$BatchUpdateDocumentResponse>;
+ export type RetrievalResult = Opt<docsV1.Schema$Document>;
+ export type UpdateResult = Opt<docsV1.Schema$BatchUpdateDocumentResponse>;
export interface UpdateOptions {
documentId: DocumentId;
- requests: docs_v1.Schema$Request[];
+ requests: docsV1.Schema$Request[];
}
export enum WriteMode {
Insert,
- Replace
+ Replace,
}
export type DocumentId = string;
export type Reference = DocumentId | CreateOptions;
export interface Content {
text: string | string[];
- requests: docs_v1.Schema$Request[];
+ requests: docsV1.Schema$Request[];
}
export type IdHandler = (id: DocumentId) => any;
export type CreationResult = Opt<DocumentId>;
- export type ReadLinesResult = Opt<{ title?: string, bodyLines?: string[] }>;
- export type ReadResult = { title: string, body: string };
+ export type ReadLinesResult = Opt<{ title?: string; bodyLines?: string[] }>;
+ export type ReadResult = { title: string; body: string };
export interface ImportResult {
title: string;
text: string;
@@ -67,23 +68,23 @@ export namespace GoogleApiClientUtils {
}
/**
- * After following the authentication routine, which connects this API call to the current signed in account
- * and grants the appropriate permissions, this function programmatically creates an arbitrary Google Doc which
- * should appear in the user's Google Doc library instantaneously.
- *
- * @param options the title to assign to the new document, and the information necessary
- * to store the new documentId returned from the creation process
- * @returns the documentId of the newly generated document, or undefined if the creation process fails.
- */
+ * After following the authentication routine, which connects this API call to the current signed in account
+ * and grants the appropriate permissions, this function programmatically creates an arbitrary Google Doc which
+ * should appear in the user's Google Doc library instantaneously.
+ *
+ * @param options the title to assign to the new document, and the information necessary
+ * to store the new documentId returned from the creation process
+ * @returns the documentId of the newly generated document, or undefined if the creation process fails.
+ */
export const create = async (options: CreateOptions): Promise<CreationResult> => {
const path = `/googleDocs/Documents/${Actions.Create}`;
const parameters = {
requestBody: {
- title: options.title || `Dash Export (${new Date().toDateString()})`
- }
+ title: options.title || `Dash Export (${new Date().toDateString()})`,
+ },
};
try {
- const schema: docs_v1.Schema$Document = await Networking.PostToServer(path, parameters);
+ const schema: docsV1.Schema$Document = await Networking.PostToServer(path, parameters);
return schema.documentId === null ? undefined : schema.documentId;
} catch {
return undefined;
@@ -91,19 +92,25 @@ export namespace GoogleApiClientUtils {
};
export namespace Utils {
-
- export type ExtractResult = { text: string, paragraphs: DeconstructedParagraph[] };
- export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false): ExtractResult => {
+ export type ExtractResult = { text: string; paragraphs: DeconstructedParagraph[] };
+ export const extractText = (document: docsV1.Schema$Document, removeNewlines = false): ExtractResult => {
const paragraphs = extractParagraphs(document);
- let text = paragraphs.map(paragraph => paragraph.contents.filter(content => !("inlineObjectId" in content)).map(run => (run as docs_v1.Schema$TextRun).content).join("")).join("");
+ let text = paragraphs
+ .map(paragraph =>
+ paragraph.contents
+ .filter(content => !('inlineObjectId' in content))
+ .map(run => (run as docsV1.Schema$TextRun).content)
+ .join('')
+ )
+ .join('');
text = text.substring(0, text.length - 1);
- removeNewlines && text.replace(/\n/g, "");
+ removeNewlines && text.replace(/\n/g, '');
return { text, paragraphs };
};
- export type ContentArray = (docs_v1.Schema$TextRun | docs_v1.Schema$InlineObjectElement)[];
- export type DeconstructedParagraph = { contents: ContentArray, bullet: Opt<number> };
- const extractParagraphs = (document: docs_v1.Schema$Document, filterEmpty = true): DeconstructedParagraph[] => {
+ export type ContentArray = (docsV1.Schema$TextRun | docsV1.Schema$InlineObjectElement)[];
+ export type DeconstructedParagraph = { contents: ContentArray; bullet: Opt<number> };
+ const extractParagraphs = (document: docsV1.Schema$Document, filterEmpty = true): DeconstructedParagraph[] => {
const fragments: DeconstructedParagraph[] = [];
if (document.body && document.body.content) {
for (const element of document.body.content) {
@@ -132,7 +139,7 @@ export namespace GoogleApiClientUtils {
return fragments;
};
- export const endOf = (schema: docs_v1.Schema$Document): number | undefined => {
+ export const endOf = (schema: docsV1.Schema$Document): number | undefined => {
if (schema.body && schema.body.content) {
const paragraphs = schema.body.content.filter(el => el.paragraph);
if (paragraphs.length) {
@@ -146,10 +153,10 @@ export namespace GoogleApiClientUtils {
}
}
}
+ return undefined;
};
- export const initialize = async (reference: Reference) => typeof reference === "string" ? reference : create(reference);
-
+ export const initialize = async (reference: Reference) => (typeof reference === 'string' ? reference : create(reference));
}
export const retrieve = async (options: RetrieveOptions): Promise<RetrievalResult> => {
@@ -168,8 +175,8 @@ export namespace GoogleApiClientUtils {
const parameters = {
documentId: options.documentId,
requestBody: {
- requests: options.requests
- }
+ requests: options.requests,
+ },
};
try {
const replies: UpdateResult = await Networking.PostToServer(path, parameters);
@@ -179,83 +186,84 @@ export namespace GoogleApiClientUtils {
}
};
- export const read = async (options: ReadOptions): Promise<Opt<ReadResult>> => {
- return retrieve({ documentId: options.documentId }).then(document => {
+ export const read = async (options: ReadOptions): Promise<Opt<ReadResult>> =>
+ retrieve({ documentId: options.documentId }).then(document => {
if (document) {
const title = document.title!;
const body = Utils.extractText(document, options.removeNewlines).text;
return { title, body };
}
+ return undefined;
});
- };
- export const readLines = async (options: ReadOptions): Promise<Opt<ReadLinesResult>> => {
- return retrieve({ documentId: options.documentId }).then(document => {
+ export const readLines = async (options: ReadOptions): Promise<Opt<ReadLinesResult>> =>
+ retrieve({ documentId: options.documentId }).then(document => {
if (document) {
- const title = document.title;
- let bodyLines = Utils.extractText(document).text.split("\n");
+ const { title } = document;
+ let bodyLines = Utils.extractText(document).text.split('\n');
options.removeNewlines && (bodyLines = bodyLines.filter(line => line.length));
- return { title: title ?? "", bodyLines };
+ return { title: title ?? '', bodyLines };
}
+ return undefined;
});
- };
export const setStyle = async (options: UpdateOptions) => {
const replies: any = await update({
documentId: options.documentId,
- requests: options.requests
+ requests: options.requests,
});
- if ("errors" in replies) {
- console.log("Write operation failed:");
+ if ('errors' in replies) {
+ console.log('Write operation failed:');
console.log(replies.errors.map((error: any) => error.message));
}
return replies;
};
export const write = async (options: WriteOptions): Promise<UpdateResult> => {
- const requests: docs_v1.Schema$Request[] = [];
+ const requests: docsV1.Schema$Request[] = [];
const documentId = await Utils.initialize(options.reference);
if (!documentId) {
return undefined;
}
- let index = options.index;
- const mode = options.mode;
+ let { index } = options;
+ const { mode } = options;
if (!(index && mode === WriteMode.Insert)) {
const schema = await retrieve({ documentId });
+ // eslint-disable-next-line no-cond-assign
if (!schema || !(index = Utils.endOf(schema))) {
return undefined;
}
}
if (mode === WriteMode.Replace) {
- index > 1 && requests.push({
- deleteContentRange: {
- range: {
- startIndex: 1,
- endIndex: index
- }
- }
- });
+ index > 1 &&
+ requests.push({
+ deleteContentRange: {
+ range: {
+ startIndex: 1,
+ endIndex: index,
+ },
+ },
+ });
index = 1;
}
- const text = options.content.text;
- text.length && requests.push({
- insertText: {
- text: isArray(text) ? text.join("\n") : text,
- location: { index }
- }
- });
+ const { text } = options.content;
+ text.length &&
+ requests.push({
+ insertText: {
+ text: isArray(text) ? text.join('\n') : text,
+ location: { index },
+ },
+ });
if (!requests.length) {
return undefined;
}
requests.push(...options.content.requests);
const replies: any = await update({ documentId, requests });
- if ("errors" in replies) {
- console.log("Write operation failed:");
+ if ('errors' in replies) {
+ console.log('Write operation failed:');
console.log(replies.errors.map((error: any) => error.message));
}
return replies;
};
-
}
-
-} \ No newline at end of file
+}
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index e8fd8fb8a..fdc185a8e 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -1,18 +1,19 @@
+/* eslint-disable no-use-before-define */
+import Photos = require('googlephotos');
import { AssertionError } from 'assert';
import { EditorState } from 'prosemirror-state';
+import { ClientUtils } from '../../../ClientUtils';
import { Doc, DocListCastAsync, Opt } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { RichTextField } from '../../../fields/RichTextField';
import { RichTextUtils } from '../../../fields/RichTextUtils';
-import { Cast, StrCast } from '../../../fields/Types';
-import { ImageField } from '../../../fields/URLField';
+import { Cast, ImageCast, StrCast } from '../../../fields/Types';
import { MediaItem, NewMediaItemResult } from '../../../server/apis/google/SharedTypes';
-import { Utils } from '../../../Utils';
-import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents';
import { Networking } from '../../Network';
+import { Docs, DocumentOptions } from '../../documents/Documents';
+import { DocUtils } from '../../documents/DocUtils';
import { FormattedTextBox } from '../../views/nodes/formattedText/FormattedTextBox';
import { GoogleAuthenticationManager } from '../GoogleAuthenticationManager';
-import Photos = require('googlephotos');
export namespace GooglePhotos {
const endpoint = async () => new Photos(await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken());
@@ -76,17 +77,16 @@ export namespace GooglePhotos {
export const CollectionToAlbum = async (options: AlbumCreationOptions): Promise<Opt<AlbumCreationResult>> => {
const { collection, title, descriptionKey, tag } = options;
const dataDocument = Doc.GetProto(collection);
- const images = ((await DocListCastAsync(dataDocument.data)) || []).filter(doc => Cast(doc.data, ImageField));
+ const images = ((await DocListCastAsync(dataDocument.data)) || []).filter(doc => ImageCast(doc.data));
if (!images || !images.length) {
return undefined;
}
- const resolved = title ? title : StrCast(collection.title) || `Dash Collection (${collection[Id]}`;
+ const resolved = title || StrCast(collection.title) || `Dash Collection (${collection[Id]}`;
const { id, productUrl } = await Create.Album(resolved);
const response = await Transactions.UploadImages(images, { id }, descriptionKey);
if (response) {
const { results, failed } = response;
- let index: Opt<number>;
- while ((index = failed.pop()) !== undefined) {
+ for (let index = failed.pop(); index !== undefined; index = failed.pop()) {
Doc.RemoveDocFromList(dataDocument, 'data', images.splice(index, 1)[0]);
}
const mediaItems: MediaItem[] = results.map(item => item.mediaItem);
@@ -97,13 +97,12 @@ export namespace GooglePhotos {
for (let i = 0; i < images.length; i++) {
const image = Doc.GetProto(images[i]);
const mediaItem = mediaItems[i];
- if (!mediaItem) {
- continue;
+ if (mediaItem) {
+ image.googlePhotosId = mediaItem.id;
+ image.googlePhotosAlbumUrl = productUrl;
+ image.googlePhotosUrl = mediaItem.productUrl || mediaItem.baseUrl;
+ idMapping[mediaItem.id] = image;
}
- image.googlePhotosId = mediaItem.id;
- image.googlePhotosAlbumUrl = productUrl;
- image.googlePhotosUrl = mediaItem.productUrl || mediaItem.baseUrl;
- idMapping[mediaItem.id] = image;
}
collection.googlePhotosAlbumUrl = productUrl;
collection.googlePhotosIdMapping = idMapping;
@@ -111,9 +110,10 @@ export namespace GooglePhotos {
await Query.TagChildImages(collection);
}
collection.albumId = id;
- Transactions.AddTextEnrichment(collection, `Find me at ${Utils.prepend(`/doc/${collection[Id]}?sharing=true`)}`);
+ Transactions.AddTextEnrichment(collection, `Find me at ${ClientUtils.prepend(`/doc/${collection[Id]}?sharing=true`)}`);
return { albumId: id, mediaItems };
}
+ return undefined;
};
}
@@ -124,7 +124,7 @@ export namespace GooglePhotos {
await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
const response = await Query.ContentSearch(requested);
const uploads = await Transactions.WriteMediaItemsToServer(response);
- const children = uploads.map((upload: Transactions.UploadInformation) => Docs.Create.ImageDocument(Utils.fileUrl(upload.fileNames.clean) /*, {"data_contentSize":upload.contentSize}*/));
+ const children = uploads.map((upload: Transactions.UploadInformation) => Docs.Create.ImageDocument(ClientUtils.fileUrl(upload.fileNames.clean) /* , {"data_contentSize":upload.contentSize} */));
const options = { _width: 500, _height: 500 };
return constructor(children, options);
};
@@ -144,7 +144,7 @@ export namespace GooglePhotos {
const images = (await DocListCastAsync(collection.data))!.map(Doc.GetProto);
images?.forEach(image => tagMapping.set(image[Id], ContentCategories.NONE));
const values = Object.values(ContentCategories).filter(value => value !== ContentCategories.NONE);
- for (const value of values) {
+ values.forEach(async value => {
const searched = (await ContentSearch({ included: [value] }))?.mediaItems?.map(({ id }) => id);
searched?.forEach(async id => {
const image = await Cast(idMapping[id], Doc);
@@ -154,7 +154,7 @@ export namespace GooglePhotos {
!tags?.includes(value) && tagMapping.set(key, tags + delimiter + value);
}
});
- }
+ });
images?.forEach(image => {
const concatenated = tagMapping.get(image[Id])!;
const tags = concatenated.split(delimiter);
@@ -200,9 +200,10 @@ export namespace GooglePhotos {
export const AlbumSearch = async (albumId: string, pageSize = 100): Promise<MediaItem[]> => {
const photos = await endpoint();
const mediaItems: MediaItem[] = [];
- let nextPageTokenStored: Opt<string> = undefined;
+ let nextPageTokenStored: Opt<string>;
const found = 0;
do {
+ // eslint-disable-next-line no-await-in-loop
const response: any = await photos.mediaItems.search(albumId, pageSize, nextPageTokenStored);
mediaItems.push(...response.mediaItems);
nextPageTokenStored = response.nextPageToken;
@@ -222,7 +223,7 @@ export namespace GooglePhotos {
excluded.length && excluded.forEach(category => contentFilter.addExcludedContentCategories(category));
filters.setContentFilter(contentFilter);
- const date = options.date;
+ const { date } = options;
if (date) {
const dateFilter = new photos.DateFilter();
if (date instanceof Date) {
@@ -240,15 +241,11 @@ export namespace GooglePhotos {
});
};
- export const GetImage = async (mediaItemId: string): Promise<Transactions.MediaItem> => {
- return (await endpoint()).mediaItems.get(mediaItemId);
- };
+ export const GetImage = async (mediaItemId: string): Promise<Transactions.MediaItem> => (await endpoint()).mediaItems.get(mediaItemId);
}
namespace Create {
- export const Album = async (title: string) => {
- return (await endpoint()).albums.create(title);
- };
+ export const Album = async (title: string) => (await endpoint()).albums.create(title);
}
export namespace Transactions {
@@ -278,6 +275,7 @@ export namespace GooglePhotos {
return enrichmentItem.id;
}
}
+ return undefined;
};
export const WriteMediaItemsToServer = async (body: { mediaItems: any[] }): Promise<UploadInformation[]> => {
@@ -291,9 +289,12 @@ export namespace GooglePhotos {
return undefined;
}
const baseUrls: string[] = await Promise.all(
- response.results.map(item => {
- return new Promise<string>(resolve => Query.GetImage(item.mediaItem.id).then(item => resolve(item.baseUrl)));
- })
+ response.results.map(
+ item =>
+ new Promise<string>(resolve => {
+ Query.GetImage(item.mediaItem.id).then(itm => resolve(itm.baseUrl));
+ })
+ )
);
return baseUrls;
};
@@ -303,31 +304,29 @@ export namespace GooglePhotos {
failed: number[];
}
- export const UploadImages = async (sources: Doc[], album?: AlbumReference, descriptionKey = 'caption'): Promise<Opt<ImageUploadResults>> => {
+ export const UploadImages = async (sources: Doc[], albumIn?: AlbumReference, descriptionKey = 'caption'): Promise<Opt<ImageUploadResults>> => {
await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
- if (album && 'title' in album) {
- album = await Create.Album(album.title);
- }
+ const album = albumIn && 'title' in albumIn ? await Create.Album(albumIn.title) : albumIn;
const media: MediaInput[] = [];
- for (const source of sources) {
- const data = Cast(Doc.GetProto(source).data, ImageField);
- if (!data) {
- return;
- }
- const url = data.url.href;
- const target = Doc.MakeEmbedding(source);
- const description = parseDescription(target, descriptionKey);
- await DocUtils.makeCustomViewClicked(target, Docs.Create.FreeformDocument);
- media.push({ url, description });
- }
+ sources
+ .filter(source => ImageCast(Doc.GetProto(source).data))
+ .forEach(async source => {
+ const data = ImageCast(Doc.GetProto(source).data);
+ const url = data.url.href;
+ const target = Doc.MakeEmbedding(source);
+ const description = parseDescription(target, descriptionKey);
+ await DocUtils.makeCustomViewClicked(target, Docs.Create.FreeformDocument);
+ media.push({ url, description });
+ });
if (media.length) {
const results = await Networking.PostToServer('/googlePhotosMediaPost', { media, album });
return results;
}
+ return undefined;
};
const parseDescription = (document: Doc, descriptionKey: string) => {
- let description: string = Utils.prepend(`/doc/${document[Id]}?sharing=true`);
+ let description: string = ClientUtils.prepend(`/doc/${document[Id]}?sharing=true`);
const target = document[descriptionKey];
if (typeof target === 'string') {
description = target;
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 30194f9f8..8f58ec364 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -27,24 +27,28 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
data: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful resarch assistant. Analyze the user's data to find meaningful patterns and/or correlation. Please keep your response short and to the point." },
};
+let lastCall = '';
+let lastResp = '';
/**
* Calls the OpenAI API.
*
* @param inputText Text to process
* @returns AI Output
*/
-const gptAPICall = async (inputText: string, callType: GPTCallType, prompt?: any) => {
- if (callType === GPTCallType.SUMMARY) inputText += '.';
+const gptAPICall = async (inputTextIn: string, callType: GPTCallType, prompt?: any) => {
+ const inputText = callType === GPTCallType.SUMMARY ? inputTextIn + '.' : inputTextIn;
const opts: GPTCallOpts = callTypeMap[callType];
+ if (lastCall === inputText) return lastResp;
try {
const configuration: ClientOptions = {
apiKey: process.env.OPENAI_KEY,
dangerouslyAllowBrowser: true,
};
+ lastCall = inputText;
const openai = new OpenAI(configuration);
- let usePrompt = prompt ? opts.prompt + prompt : opts.prompt;
- let messages: ChatCompletionMessageParam[] = [
+ const usePrompt = prompt ? opts.prompt + prompt : opts.prompt;
+ const messages: ChatCompletionMessageParam[] = [
{ role: 'system', content: usePrompt },
{ role: 'user', content: inputText },
];
@@ -55,8 +59,8 @@ const gptAPICall = async (inputText: string, callType: GPTCallType, prompt?: any
temperature: opts.temp,
messages,
});
- const content = response.choices[0].message.content;
- return content;
+ lastResp = response.choices[0].message.content ?? '';
+ return lastResp;
} catch (err) {
console.log(err);
return 'Error connecting with API.';
@@ -79,8 +83,8 @@ const gptImageCall = async (prompt: string, n?: number) => {
return response.data.map(data => data.url);
} catch (err) {
console.error(err);
- return;
}
+ return undefined;
};
export { gptAPICall, gptImageCall, GPTCallType };
diff --git a/src/client/apis/youtube/YoutubeBox.scss b/src/client/apis/youtube/YoutubeBox.scss
deleted file mode 100644
index eabdbb1ac..000000000
--- a/src/client/apis/youtube/YoutubeBox.scss
+++ /dev/null
@@ -1,126 +0,0 @@
-.youtubeBox-cont {
- ul {
- list-style-type: none;
- padding-inline-start: 10px;
- }
-
-
- li {
- margin: 4px;
- display: inline-flex;
- }
-
- li:hover {
- cursor: pointer;
- opacity: 0.8;
- }
-
- .search_wrapper {
- width: 100%;
- display: inline-flex;
- height: 175px;
-
- .video_duration {
- // margin: 0;
- // padding: 0;
- border: 0;
- background: transparent;
- display: inline-block;
- position: relative;
- bottom: 25px;
- left: 85%;
- margin: 4px;
- color: #FFFFFF;
- background-color: rgba(0, 0, 0, 0.80);
- padding: 2px 4px;
- border-radius: 2px;
- letter-spacing: .5px;
- font-size: 1.2rem;
- font-weight: 500;
- line-height: 1.2rem;
-
- }
-
- .textual_info {
- font-family: Arial, Helvetica, sans-serif;
-
- .videoTitle {
- margin-left: 4px;
- // display: inline-block;
- color: #0D0D0D;
- -webkit-line-clamp: 2;
- display: block;
- max-height: 4.8rem;
- overflow: hidden;
- font-size: 1.8rem;
- font-weight: 400;
- line-height: 2.4rem;
- -webkit-box-orient: vertical;
- text-overflow: ellipsis;
- white-space: normal;
- display: -webkit-box;
- }
-
- .channelName {
- color: #606060;
- margin-left: 4px;
- font-size: 1.3rem;
- font-weight: 400;
- line-height: 1.8rem;
- text-transform: none;
- margin-top: 0px;
- display: inline-block;
- }
-
- .video_description {
- margin-left: 4px;
- // font-size: 12px;
- color: #606060;
- padding-top: 8px;
- margin-bottom: 8px;
- display: block;
- line-height: 1.8rem;
- max-height: 4.2rem;
- overflow: hidden;
- font-size: 1.3rem;
- font-weight: 400;
- text-transform: none;
- }
-
- .publish_time {
- //display: inline-block;
- margin-left: 8px;
- padding: 0;
- border: 0;
- background: transparent;
- color: #606060;
- max-width: 100%;
- line-height: 1.8rem;
- max-height: 3.6rem;
- overflow: hidden;
- font-size: 1.3rem;
- font-weight: 400;
- text-transform: none;
- }
-
- .viewCount {
-
- margin-left: 8px;
- padding: 0;
- border: 0;
- background: transparent;
- color: #606060;
- max-width: 100%;
- line-height: 1.8rem;
- max-height: 3.6rem;
- overflow: hidden;
- font-size: 1.3rem;
- font-weight: 400;
- text-transform: none;
- }
-
-
-
- }
- }
-} \ No newline at end of file
diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
deleted file mode 100644
index d3a15cd84..000000000
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ /dev/null
@@ -1,369 +0,0 @@
-import { action, observable, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import { Doc, DocListCastAsync } from '../../../fields/Doc';
-import { InkTool } from '../../../fields/InkField';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { Utils } from '../../../Utils';
-import { DocServer } from '../../DocServer';
-import { Docs } from '../../documents/Documents';
-import { DocumentView } from '../../views/nodes/DocumentView';
-import { FieldView, FieldViewProps } from '../../views/nodes/FieldView';
-import '../../views/nodes/WebBox.scss';
-import './YoutubeBox.scss';
-import * as React from 'react';
-import { SnappingManager } from '../../util/SnappingManager';
-
-interface VideoTemplate {
- thumbnailUrl: string;
- videoTitle: string;
- videoId: string;
- duration: string;
- channelTitle: string;
- viewCount: string;
- publishDate: string;
- videoDescription: string;
-}
-
-/**
- * This class models the youtube search document that can be dropped on to canvas.
- */
-@observer
-export class YoutubeBox extends React.Component<FieldViewProps> {
- @observable YoutubeSearchElement: HTMLInputElement | undefined;
- @observable searchResultsFound: boolean = false;
- @observable searchResults: any[] = [];
- @observable videoClicked: boolean = false;
- @observable selectedVideoUrl: string = '';
- @observable lisOfBackUp: JSX.Element[] = [];
- @observable videoIds: string | undefined;
- @observable videoDetails: any[] = [];
- @observable curVideoTemplates: VideoTemplate[] = [];
-
- public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(YoutubeBox, fieldKey);
- }
-
- /**
- * When component mounts, last search's results are laoded in based on the back up stored
- * in the document of the props.
- */
- async componentDidMount() {
- //DocServer.getYoutubeChannels();
- const castedSearchBackUp = Cast(this.props.Document.cachedSearchResults, Doc);
- const awaitedBackUp = await castedSearchBackUp;
- const castedDetailBackUp = Cast(this.props.Document.cachedDetails, Doc);
- const awaitedDetails = await castedDetailBackUp;
-
- if (awaitedBackUp) {
- const jsonList = await DocListCastAsync(awaitedBackUp.json);
- const jsonDetailList = await DocListCastAsync(awaitedDetails!.json);
-
- if (jsonList!.length !== 0) {
- runInAction(() => (this.searchResultsFound = true));
- let index = 0;
- //getting the necessary information from backUps and building templates that will be used to map in render
- for (const video of jsonList!) {
- const videoId = await Cast(video.id, Doc);
- const id = StrCast(videoId!.videoId);
- const snippet = await Cast(video.snippet, Doc);
- const videoTitle = this.filterYoutubeTitleResult(StrCast(snippet!.title));
- const thumbnail = await Cast(snippet!.thumbnails, Doc);
- const thumbnailMedium = await Cast(thumbnail!.medium, Doc);
- const thumbnailUrl = StrCast(thumbnailMedium!.url);
- const videoDescription = StrCast(snippet!.description);
- const pusblishDate = this.roundPublishTime(StrCast(snippet!.publishedAt))!;
- const channelTitle = StrCast(snippet!.channelTitle);
- let duration: string = '';
- let viewCount: string = '';
- if (jsonDetailList!.length !== 0) {
- const contentDetails = await Cast(jsonDetailList![index].contentDetails, Doc);
- const statistics = await Cast(jsonDetailList![index].statistics, Doc);
- duration = this.convertIsoTimeToDuration(StrCast(contentDetails!.duration));
- viewCount = this.abbreviateViewCount(parseInt(StrCast(statistics!.viewCount)))!;
- }
- index = index + 1;
- const newTemplate: VideoTemplate = {
- videoId: id,
- videoTitle: videoTitle,
- thumbnailUrl: thumbnailUrl,
- publishDate: pusblishDate,
- channelTitle: channelTitle,
- videoDescription: videoDescription,
- duration: duration,
- viewCount: viewCount,
- };
- runInAction(() => this.curVideoTemplates.push(newTemplate));
- }
- }
- }
- }
-
- _ignore = 0;
- onPreWheel = (e: React.WheelEvent) => {
- this._ignore = e.timeStamp;
- };
- onPrePointer = (e: React.PointerEvent) => {
- this._ignore = e.timeStamp;
- };
- onPostPointer = (e: React.PointerEvent) => {
- if (this._ignore !== e.timeStamp) {
- e.stopPropagation();
- }
- };
- onPostWheel = (e: React.WheelEvent) => {
- if (this._ignore !== e.timeStamp) {
- e.stopPropagation();
- }
- };
-
- /**
- * Function that submits the title entered by user on enter press.
- */
- onEnterKeyDown = (e: React.KeyboardEvent) => {
- if (e.keyCode === 13) {
- const submittedTitle = this.YoutubeSearchElement!.value;
- this.YoutubeSearchElement!.value = '';
- this.YoutubeSearchElement!.blur();
- DocServer.getYoutubeVideos(submittedTitle, this.processesVideoResults);
- }
- };
-
- /**
- * The callback that is passed in to server, which functions as a way to
- * get videos that is returned by search. It also makes a call to server
- * to get details for the videos found.
- */
- @action
- processesVideoResults = (videos: any[]) => {
- this.searchResults = videos;
- if (this.searchResults.length > 0) {
- this.searchResultsFound = true;
- this.videoIds = '';
- videos.forEach(video => {
- if (this.videoIds === '') {
- this.videoIds = video.id.videoId;
- } else {
- this.videoIds = this.videoIds! + ', ' + video.id.videoId;
- }
- });
- //Asking for details that include duration and viewCount from server for videoIds
- DocServer.getYoutubeVideoDetails(this.videoIds, this.processVideoDetails);
- this.backUpSearchResults(videos);
- if (this.videoClicked) {
- this.videoClicked = false;
- }
- }
- };
-
- /**
- * The callback that is given to server to process and receive returned details about the videos.
- */
- @action
- processVideoDetails = (videoDetails: any[]) => {
- this.videoDetails = videoDetails;
- this.props.Document.cachedDetails = Doc.Get.FromJson({ data: videoDetails, title: 'detailBackUp' });
- };
-
- /**
- * The function that stores the search results in the props document.
- */
- backUpSearchResults = (videos: any[]) => {
- this.props.Document.cachedSearchResults = Doc.Get.FromJson({ data: videos, title: 'videosBackUp' });
- };
-
- /**
- * The function that filters out escaped characters returned by the api
- * in the title of the videos.
- */
- filterYoutubeTitleResult = (resultTitle: string) => {
- let processedTitle: string = resultTitle.replace(/&amp;/g, '&'); //.ReplaceAll("&amp;", "&");
- processedTitle = processedTitle.replace(/"&#39;/g, "'");
- processedTitle = processedTitle.replace(/&quot;/g, '"');
- return processedTitle;
- };
-
- /**
- * The function that converts ISO date, which is passed in, to normal date and finds the
- * difference between today's date and that date, in terms of "ago" to imitate youtube.
- */
- roundPublishTime = (publishTime: string) => {
- const date = new Date(publishTime).getTime();
- const curDate = new Date().getTime();
- const timeDif = curDate - date;
- const totalSeconds = timeDif / 1000;
- const totalMin = totalSeconds / 60;
- const totalHours = totalMin / 60;
- const totalDays = totalHours / 24;
- const totalMonths = totalDays / 30.417;
- const totalYears = totalMonths / 12;
-
- const truncYears = Math.trunc(totalYears);
- const truncMonths = Math.trunc(totalMonths);
- const truncDays = Math.trunc(totalDays);
- const truncHours = Math.trunc(totalHours);
- const truncMin = Math.trunc(totalMin);
- const truncSec = Math.trunc(totalSeconds);
-
- let pluralCase = '';
-
- if (truncYears !== 0) {
- truncYears > 1 ? (pluralCase = 's') : (pluralCase = '');
- return truncYears + ' year' + pluralCase + ' ago';
- } else if (truncMonths !== 0) {
- truncMonths > 1 ? (pluralCase = 's') : (pluralCase = '');
- return truncMonths + ' month' + pluralCase + ' ago';
- } else if (truncDays !== 0) {
- truncDays > 1 ? (pluralCase = 's') : (pluralCase = '');
- return truncDays + ' day' + pluralCase + ' ago';
- } else if (truncHours !== 0) {
- truncHours > 1 ? (pluralCase = 's') : (pluralCase = '');
- return truncHours + ' hour' + pluralCase + ' ago';
- } else if (truncMin !== 0) {
- truncMin > 1 ? (pluralCase = 's') : (pluralCase = '');
- return truncMin + ' minute' + pluralCase + ' ago';
- } else if (truncSec !== 0) {
- truncSec > 1 ? (pluralCase = 's') : (pluralCase = '');
- return truncSec + ' second' + pluralCase + ' ago';
- }
- };
-
- /**
- * The function that converts the passed in ISO time to normal duration time.
- */
- convertIsoTimeToDuration = (isoDur: string) => {
- const convertedTime = isoDur.replace(/D|H|M/g, ':').replace(/P|T|S/g, '').split(':');
-
- if (1 === convertedTime.length) {
- 2 !== convertedTime[0].length && (convertedTime[0] = '0' + convertedTime[0]), (convertedTime[0] = '0:' + convertedTime[0]);
- } else {
- for (var r = 1, l = convertedTime.length - 1; l >= r; r++) {
- 2 !== convertedTime[r].length && (convertedTime[r] = '0' + convertedTime[r]);
- }
- }
-
- return convertedTime.join(':');
- };
-
- /**
- * The function that rounds the viewCount to the nearest
- * thousand, million or billion, given a viewCount number.
- */
- abbreviateViewCount = (viewCount: number) => {
- if (viewCount < 1000) {
- return viewCount.toString();
- } else if (viewCount >= 1000 && viewCount < 1000000) {
- return Math.trunc(viewCount / 1000) + 'K';
- } else if (viewCount >= 1000000 && viewCount < 1000000000) {
- return Math.trunc(viewCount / 1000000) + 'M';
- } else if (viewCount >= 1000000000) {
- return Math.trunc(viewCount / 1000000000) + 'B';
- }
- };
-
- /**
- * The function that is called to decide on what'll be rendered by the component.
- * It renders search Results if found. If user didn't do a new search, it renders from the videoTemplates
- * generated by the backUps. If none present, renders nothing.
- */
- renderSearchResultsOrVideo = () => {
- if (this.searchResultsFound) {
- if (this.searchResults.length !== 0) {
- return (
- <ul>
- {this.searchResults.map((video, index) => {
- const filteredTitle = this.filterYoutubeTitleResult(video.snippet.title);
- const channelTitle = video.snippet.channelTitle;
- const videoDescription = video.snippet.description;
- const pusblishDate = this.roundPublishTime(video.snippet.publishedAt);
- let duration;
- let viewCount;
- if (this.videoDetails.length !== 0) {
- duration = this.convertIsoTimeToDuration(this.videoDetails[index].contentDetails.duration);
- viewCount = this.abbreviateViewCount(this.videoDetails[index].statistics.viewCount);
- }
-
- return (
- <li onClick={() => this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={Utils.GenerateGuid()}>
- <div className="search_wrapper">
- <div style={{ backgroundColor: 'yellow' }}>
- <img src={video.snippet.thumbnails.medium.url} />
- <span className="video_duration">{duration}</span>
- </div>
- <div className="textual_info">
- <span className="videoTitle">{filteredTitle}</span>
- <span className="channelName">{channelTitle}</span>
- <span className="viewCount">{viewCount}</span>
- <span className="publish_time">{pusblishDate}</span>
- <p className="video_description">{videoDescription}</p>
- </div>
- </div>
- </li>
- );
- })}
- </ul>
- );
- } else if (this.curVideoTemplates.length !== 0) {
- return (
- <ul>
- {this.curVideoTemplates.map((video: VideoTemplate) => {
- return (
- <li onClick={() => this.embedVideoOnClick(video.videoId, video.videoTitle)} key={Utils.GenerateGuid()}>
- <div className="search_wrapper">
- <div style={{ backgroundColor: 'yellow' }}>
- <img src={video.thumbnailUrl} />
- <span className="video_duration">{video.duration}</span>
- </div>
- <div className="textual_info">
- <span className="videoTitle">{video.videoTitle}</span>
- <span className="channelName">{video.channelTitle}</span>
- <span className="viewCount">{video.viewCount}</span>
- <span className="publish_time">{video.publishDate}</span>
- <p className="video_description">{video.videoDescription}</p>
- </div>
- </div>
- </li>
- );
- })}
- </ul>
- );
- }
- } else {
- return null;
- }
- };
-
- /**
- * Given a videoId and title, creates a new youtube embedded url, and uses that
- * to create a new video document.
- */
- @action
- embedVideoOnClick = (videoId: string, filteredTitle: string) => {
- const embeddedUrl = 'https://www.youtube.com/embed/' + videoId;
- this.selectedVideoUrl = embeddedUrl;
- const addFunction = this.props.addDocument!;
- const newVideoX = NumCast(this.props.Document.x);
- const newVideoY = NumCast(this.props.Document.y) + NumCast(this.props.Document.height);
-
- addFunction(Docs.Create.VideoDocument(embeddedUrl, { title: filteredTitle, _width: 400, _height: 315, x: newVideoX, y: newVideoY }));
- this.videoClicked = true;
- };
-
- render() {
- const content = (
- <div className="youtubeBox-cont" style={{ width: '100%', height: '100%', position: 'absolute' }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
- <input type="text" placeholder="Search for a video" onKeyDown={this.onEnterKeyDown} style={{ height: 40, width: '100%', border: '1px solid black', padding: 5, textAlign: 'center' }} ref={e => (this.YoutubeSearchElement = e!)} />
- {this.renderSearchResultsOrVideo()}
- </div>
- );
-
- const frozen = !this.props.isSelected() || SnappingManager.IsResizing;
-
- const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !SnappingManager.IsResizing ? '-interactive' : '');
- return (
- <>
- <div className={classname}>{content}</div>
- {!frozen ? null : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />}
- </>
- );
- }
-}
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 408903324..9808b6a01 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -1,18 +1,21 @@
-import * as request from 'request-promise';
-import { Doc, Field } from '../../fields/Doc';
-import { Cast } from '../../fields/Types';
-import { Utils } from '../../Utils';
+/* eslint-disable @typescript-eslint/no-unused-vars */
+/* eslint-disable camelcase */
+/* eslint-disable no-useless-catch */
+/* eslint-disable no-use-before-define */
+import * as rp from 'request-promise';
+import { Doc, FieldType } from '../../fields/Doc';
import { InkData } from '../../fields/InkField';
-import { UndoManager } from '../util/UndoManager';
-import requestPromise = require('request-promise');
import { List } from '../../fields/List';
+import { Cast } from '../../fields/Types';
+import { UndoManager } from '../util/UndoManager';
+import { ClientUtils } from '../../ClientUtils';
type APIManager<D> = { converter: BodyConverter<D>; requester: RequestExecutor };
type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise<string>;
type AnalysisApplier<D> = (target: Doc, relevantKeys: string[], data: D, ...args: any) => any;
type BodyConverter<D> = (data: D) => string;
-type Converter = (results: any) => Field;
-type TextConverter = (results: any, data: string) => Promise<{ keyterms: Field; external_recommendations: any; kp_string: string[] }>;
+type Converter = (results: any) => FieldType;
+type TextConverter = (results: any, data: string) => Promise<{ keyterms: FieldType; external_recommendations: any; kp_string: string[] }>;
type BingConverter = (results: any) => Promise<{ title_vals: string[]; url_vals: string[] }>;
export type Tag = { name: string; confidence: number };
@@ -42,7 +45,7 @@ export enum Confidence {
*/
export namespace CognitiveServices {
const ExecuteQuery = async <D>(service: Service, manager: APIManager<D>, data: D): Promise<any> => {
- let apiKey = process.env[service.toUpperCase()];
+ const apiKey = process.env[service.toUpperCase()];
if (!apiKey) {
console.log(`No API key found for ${service}: ensure youe root directory has .env file with _CLIENT_${service.toUpperCase()}.`);
return undefined;
@@ -53,7 +56,6 @@ export namespace CognitiveServices {
results = await manager.requester(apiKey, manager.converter(data), service).then(json => JSON.parse(json));
} catch (e) {
throw e;
- results = undefined;
}
return results;
};
@@ -72,7 +74,7 @@ export namespace CognitiveServices {
parameters = {
returnFaceId: 'true',
returnFaceLandmarks: 'false',
- returnFaceAttributes: 'age,gender,headPose,smile,facialHair,glasses,' + 'emotion,hair,makeup,occlusion,accessories,blur,exposure,noise',
+ returnFaceAttributes: 'age,gender,headPose,smile,facialHair,glasses,emotion,hair,makeup,occlusion,accessories,blur,exposure,noise',
};
break;
case Service.ComputerVision:
@@ -83,6 +85,7 @@ export namespace CognitiveServices {
language: 'en',
};
break;
+ default:
}
const options = {
@@ -95,7 +98,7 @@ export namespace CognitiveServices {
},
};
- return request.post(options);
+ return rp.post(options);
},
};
@@ -111,12 +114,10 @@ export namespace CognitiveServices {
const results = await ExecuteQuery(service, Manager, url);
if (!results) {
toStore = 'Cognitive Services could not process the given image URL.';
+ } else if (!results.length) {
+ toStore = converter(results);
} else {
- if (!results.length) {
- toStore = converter(results);
- } else {
- toStore = results.length > 0 ? converter(results) : 'Empty list returned.';
- }
+ toStore = results.length > 0 ? converter(results) : 'Empty list returned.';
}
target[storageKey] = toStore;
@@ -150,7 +151,7 @@ export namespace CognitiveServices {
const endpoint = serverAddress + '/inkrecognizer/v1.0-preview/recognize';
return new Promise<string>((resolve, reject) => {
- xhttp.onreadystatechange = function () {
+ xhttp.onreadystatechange = function (this: XMLHttpRequest) {
if (this.readyState === 4) {
const result = xhttp.responseText;
switch (this.status) {
@@ -161,6 +162,7 @@ export namespace CognitiveServices {
return reject(result);
}
}
+ return undefined;
};
xhttp.open('PUT', endpoint, true);
@@ -213,15 +215,13 @@ export namespace CognitiveServices {
export namespace BingSearch {
export const Manager: APIManager<string> = {
- converter: (data: string) => {
- return data;
- },
+ converter: (data: string) => data,
requester: async (apiKey: string, query: string) => {
const xhttp = new XMLHttpRequest();
const serverAddress = 'https://api.cognitive.microsoft.com';
const endpoint = serverAddress + '/bing/v5.0/search?q=' + encodeURIComponent(query);
const promisified = (resolve: any, reject: any) => {
- xhttp.onreadystatechange = function () {
+ xhttp.onreadystatechange = function (this: XMLHttpRequest) {
if (this.readyState === 4) {
const result = xhttp.responseText;
switch (this.status) {
@@ -232,6 +232,7 @@ export namespace CognitiveServices {
return reject(result);
}
}
+ return undefined;
};
if (apiKey) {
@@ -259,15 +260,13 @@ export namespace CognitiveServices {
export namespace HathiTrust {
export const Manager: APIManager<string> = {
- converter: (data: string) => {
- return data;
- },
+ converter: (data: string) => data,
requester: async (apiKey: string, query: string) => {
const xhttp = new XMLHttpRequest();
const serverAddress = 'https://babel.hathitrust.org/cgi/htd/​';
const endpoint = serverAddress + '/bing/v5.0/search?q=' + encodeURIComponent(query);
const promisified = (resolve: any, reject: any) => {
- xhttp.onreadystatechange = function () {
+ xhttp.onreadystatechange = function (this: XMLHttpRequest) {
if (this.readyState === 4) {
const result = xhttp.responseText;
switch (this.status) {
@@ -278,6 +277,7 @@ export namespace CognitiveServices {
return reject(result);
}
}
+ return undefined;
};
if (apiKey) {
@@ -305,8 +305,8 @@ export namespace CognitiveServices {
export namespace Text {
export const Manager: APIManager<string> = {
- converter: (data: string) => {
- return JSON.stringify({
+ converter: (data: string) =>
+ JSON.stringify({
documents: [
{
id: 1,
@@ -314,8 +314,7 @@ export namespace CognitiveServices {
text: data,
},
],
- });
- },
+ }),
requester: async (apiKey: string, body: string, service: Service) => {
const serverAddress = 'https://eastus.api.cognitive.microsoft.com';
const endpoint = serverAddress + '/text/analytics/v2.1/keyPhrases';
@@ -344,10 +343,10 @@ export namespace CognitiveServices {
export namespace Appliers {
export async function vectorize(keyterms: any, dataDoc: Doc, mainDoc: boolean = false) {
console.log('vectorizing...');
- //keyterms = ["father", "king"];
+ // keyterms = ["father", "king"];
- const args = { method: 'POST', uri: Utils.prepend('/recommender'), body: { keyphrases: keyterms }, json: true };
- await requestPromise.post(args).then(async wordvecs => {
+ const args = { method: 'POST', uri: ClientUtils.prepend('/recommender'), body: { keyphrases: keyterms }, json: true };
+ await rp.post(args).then(async wordvecs => {
if (wordvecs) {
const indices = Object.keys(wordvecs);
console.log('successful vectorization!');
@@ -355,7 +354,7 @@ export namespace CognitiveServices {
indices.forEach((ind: any) => {
vectorValues.push(wordvecs[ind]);
});
- //ClientRecommender.Instance.processVector(vectorValues, dataDoc, mainDoc);
+ // ClientRecommender.Instance.processVector(vectorValues, dataDoc, mainDoc);
} // adds document to internal doc set
else {
console.log('unsuccessful :( word(s) not in vocabulary');
@@ -369,11 +368,12 @@ export namespace CognitiveServices {
const { keyterms, external_recommendations, kp_string } = await converter(results, data);
target[keys[0]] = keyterms;
if (isInternal) {
- //await vectorize([data], dataDoc, isMainDoc);
+ // await vectorize([data], dataDoc, isMainDoc);
await vectorize(kp_string, dataDoc, isMainDoc);
} else {
return { recs: external_recommendations, keyterms: keyterms };
}
+ return undefined;
};
// export async function countFrequencies()
diff --git a/src/client/documents/DocFromField.ts b/src/client/documents/DocFromField.ts
new file mode 100644
index 000000000..1c0a9755b
--- /dev/null
+++ b/src/client/documents/DocFromField.ts
@@ -0,0 +1,50 @@
+/* eslint-disable prefer-destructuring */
+/* eslint-disable default-param-last */
+/* eslint-disable no-use-before-define */
+import { Doc, DocListCast } from '../../fields/Doc';
+import { InkField } from '../../fields/InkField';
+import { List } from '../../fields/List';
+import { AudioField, ImageField, PdfField, VideoField } from '../../fields/URLField';
+import { InkingStroke } from '../views/InkingStroke';
+import { CollectionView } from '../views/collections/CollectionView';
+import { AudioBox } from '../views/nodes/AudioBox';
+import { ImageBox } from '../views/nodes/ImageBox';
+import { PDFBox } from '../views/nodes/PDFBox';
+import { VideoBox } from '../views/nodes/VideoBox';
+import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
+import { Docs, DocumentOptions } from './Documents';
+
+export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined {
+ let created: Doc | undefined;
+ const field = target[fieldKey];
+ const resolved = options ?? {};
+ if (field instanceof ImageField) {
+ created = Docs.Create.ImageDocument(field.url.href, resolved);
+ created.layout = ImageBox.LayoutString(fieldKey);
+ } else if (field instanceof Doc) {
+ created = field;
+ } else if (field instanceof VideoField) {
+ created = Docs.Create.VideoDocument(field.url.href, resolved);
+ created.layout = VideoBox.LayoutString(fieldKey);
+ } else if (field instanceof PdfField) {
+ created = Docs.Create.PdfDocument(field.url.href, resolved);
+ created.layout = PDFBox.LayoutString(fieldKey);
+ } else if (field instanceof AudioField) {
+ created = Docs.Create.AudioDocument(field.url.href, resolved);
+ created.layout = AudioBox.LayoutString(fieldKey);
+ } else if (field instanceof InkField) {
+ created = Docs.Create.InkDocument(field.inkData, resolved);
+ created.layout = InkingStroke.LayoutString(fieldKey);
+ } else if (field instanceof List && field[0] instanceof Doc) {
+ created = Docs.Create.StackingDocument(DocListCast(field), resolved);
+ created.layout = CollectionView.LayoutString(fieldKey);
+ } else {
+ created = Docs.Create.TextDocument('', { ...{ _width: 200, _height: 25, _layout_autoHeight: true }, ...resolved });
+ created.layout = FormattedTextBox.LayoutString(fieldKey);
+ }
+ if (created) {
+ created.title = fieldKey;
+ proto && created.proto && (created.proto = Doc.GetProto(proto));
+ }
+ return created;
+}
diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
new file mode 100644
index 000000000..0c9fe0315
--- /dev/null
+++ b/src/client/documents/DocUtils.ts
@@ -0,0 +1,875 @@
+/* eslint-disable prefer-destructuring */
+/* eslint-disable default-param-last */
+/* eslint-disable no-use-before-define */
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { saveAs } from 'file-saver';
+import * as JSZip from 'jszip';
+import { action, runInAction } from 'mobx';
+import { ClientUtils } from '../../ClientUtils';
+import * as JSZipUtils from '../../JSZipUtils';
+import { decycle } from '../../decycler/decycler';
+import { DateField } from '../../fields/DateField';
+import { Doc, DocListCast, Field, FieldResult, FieldType, LinkedTo, Opt, StrListCast } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
+import { Id } from '../../fields/FieldSymbols';
+import { InkDataFieldName, InkField } from '../../fields/InkField';
+import { List, ListFieldName } from '../../fields/List';
+import { ProxyField } from '../../fields/Proxy';
+import { RichTextField } from '../../fields/RichTextField';
+import { ComputedField, ScriptField } from '../../fields/ScriptField';
+import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types';
+import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from '../../fields/URLField';
+import { SharingPermissions } from '../../fields/util';
+import { Upload } from '../../server/SharedMediaTypes';
+import { DocServer } from '../DocServer';
+import { Networking } from '../Network';
+import { LinkManager } from '../util/LinkManager';
+import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { SerializationHelper } from '../util/SerializationHelper';
+import { UndoManager, undoable } from '../util/UndoManager';
+import { ContextMenu } from '../views/ContextMenu';
+import { ContextMenuProps } from '../views/ContextMenuItem';
+import { LinkDescriptionPopup } from '../views/nodes/LinkDescriptionPopup';
+import { OpenWhere } from '../views/nodes/OpenWhere';
+import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
+import { DocumentType } from './DocumentTypes';
+import { Docs, DocumentOptions } from './Documents';
+
+const { DFLT_IMAGE_NATIVE_DIM } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
+
+const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', ''));
+
+export namespace DocUtils {
+ function matchFieldValue(doc: Doc, key: string, valueIn: any): boolean {
+ let value = valueIn;
+ const hasFunctionFilter = ClientUtils.HasFunctionFilter(value);
+ if (hasFunctionFilter) {
+ return hasFunctionFilter(StrCast(doc[key]));
+ }
+ if (key === LinkedTo) {
+ // links are not a field value, so handled here. value is an expression of form ([field=]idToDoc("..."))
+ const allLinks = Doc.Links(doc);
+ const matchLink = (val: string, anchor: Doc) => {
+ const linkedToExp = (val ?? '').split('=');
+ if (linkedToExp.length === 1) return Field.toScriptString(anchor) === val;
+ return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1];
+ };
+ // prettier-ignore
+ return (value === Doc.FilterNone && !allLinks.length) ||
+ (value === Doc.FilterAny && !!allLinks.length) ||
+ (allLinks.some(link => matchLink(value,DocCast(link.link_anchor_1)) ||
+ matchLink(value,DocCast(link.link_anchor_2)) ));
+ }
+ if (typeof value === 'string') {
+ value = value.replace(`,${ClientUtils.noRecursionHack}`, '');
+ }
+ const fieldVal = doc[key];
+ // prettier-ignore
+ if ((value === Doc.FilterAny && fieldVal !== undefined) ||
+ (value === Doc.FilterNone && fieldVal === undefined)) {
+ return true;
+ }
+ const vals = StrListCast(fieldVal); // list typing is very imperfect. casting to a string list doesn't mean that the entries will actually be strings
+ if (vals.length) {
+ return vals.some(v => typeof v === 'string' && v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+ }
+ return Field.toString(fieldVal as FieldType).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+ }
+ /**
+ * @param docs
+ * @param childFilters
+ * @param childFiltersByRanges
+ * @param parentCollection
+ * Given a list of docs and childFilters, @returns the list of Docs that match those filters
+ */
+ export function FilterDocs(childDocs: Doc[], childFilters: string[], childFiltersByRanges: string[], parentCollection?: Doc) {
+ if (!childFilters?.length && !childFiltersByRanges?.length) {
+ return childDocs.filter(d => !d.cookies); // remove documents that need a cookie if there are no filters to provide one
+ }
+
+ const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
+ childFilters.forEach(filter => {
+ const fields = filter.split(Doc.FilterSep);
+ const key = fields[0];
+ const value = fields[1];
+ const modifiers = fields[2];
+ if (!filterFacets[key]) {
+ filterFacets[key] = {};
+ }
+ filterFacets[key][value] = modifiers;
+ });
+
+ const filteredDocs = childFilters.length
+ ? childDocs.filter(d => {
+ if (d.z) return true;
+ // if the document needs a cookie but no filter provides the cookie, then the document does not pass the filter
+ if (d.cookies && (!filterFacets.cookies || !Object.keys(filterFacets.cookies).some(key => d.cookies === key))) {
+ return false;
+ }
+ const facetKeys = Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== ClientUtils.noDragDocsFilter.split(Doc.FilterSep)[0]);
+ // eslint-disable-next-line no-restricted-syntax
+ for (const facetKey of facetKeys) {
+ const facet = filterFacets[facetKey];
+
+ // facets that match some value in the field of the document (e.g. some text field)
+ const matches = Object.keys(facet).filter(value => value !== 'cookies' && facet[value] === 'match');
+
+ // facets that have a check next to them
+ const checks = Object.keys(facet).filter(value => facet[value] === 'check');
+
+ // metadata facets that exist
+ const exists = Object.keys(facet).filter(value => facet[value] === 'exists');
+
+ // facets that unset metadata (a hack for making cookies work)
+ const unsets = Object.keys(facet).filter(value => facet[value] === 'unset');
+
+ // facets that specify that a field must not match a specific value
+ const xs = Object.keys(facet).filter(value => facet[value] === 'x');
+
+ if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true;
+ const failsNotEqualFacets = !xs.length ? false : xs.some(value => matchFieldValue(d, facetKey, value));
+ const satisfiesCheckFacets = !checks.length ? true : checks.some(value => matchFieldValue(d, facetKey, value));
+ const satisfiesExistsFacets = !exists.length ? true : facetKey !== LinkedTo ? d[facetKey] !== undefined : Doc.Links(d).length;
+ const satisfiesUnsetsFacets = !unsets.length ? true : d[facetKey] === undefined;
+ const satisfiesMatchFacets = !matches.length
+ ? true
+ : matches.some(value => {
+ if (facetKey.startsWith('*')) {
+ // fields starting with a '*' are used to match families of related fields. ie, *modificationDate will match text_modificationDate, data_modificationDate, etc
+ const allKeys = Array.from(Object.keys(d));
+ allKeys.push(...Object.keys(Doc.GetProto(d)));
+ const keys = allKeys.filter(key => key.includes(facetKey.substring(1)));
+ return keys.some(key => Field.toString(d[key] as FieldType).includes(value));
+ }
+ return Field.toString(d[facetKey] as FieldType).includes(value);
+ });
+ // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria
+ if (parentCollection?.childFilters_boolean === 'OR') {
+ if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true;
+ }
+ // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria
+ else if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
+ }
+ return parentCollection?.childFilters_boolean !== 'OR';
+ })
+ : childDocs;
+ const rangeFilteredDocs = filteredDocs.filter(d => {
+ for (let i = 0; i < childFiltersByRanges.length; i += 3) {
+ const key = childFiltersByRanges[i];
+ const min = Number(childFiltersByRanges[i + 1]);
+ const max = Number(childFiltersByRanges[i + 2]);
+ const val = typeof d[key] === 'string' ? (Number(StrCast(d[key])).toString() === StrCast(d[key]) ? Number(StrCast(d[key])) : undefined) : Cast(d[key], 'number', null);
+ if (val === undefined) {
+ // console.log("Should 'undefined' pass range filter or not?")
+ } else if (val < min || val > max) return false;
+ }
+ return true;
+ });
+ return rangeFilteredDocs;
+ }
+
+ export function MakeLink(source: Doc, target: Doc, linkSettings: { link_relationship?: string; link_description?: string }, id?: string, showPopup?: number[]) {
+ if (!linkSettings.link_relationship) linkSettings.link_relationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link';
+ if (target.doc === Doc.UserDoc()) return undefined;
+
+ const makeLink = action((linkDoc: Doc, showAt?: number[]) => {
+ if (showAt) {
+ LinkManager.Instance.currentLink = linkDoc;
+
+ TaskCompletionBox.textDisplayed = 'Link Created';
+ TaskCompletionBox.popupX = showAt[0];
+ TaskCompletionBox.popupY = showAt[1] - 33;
+ TaskCompletionBox.taskCompleted = true;
+
+ LinkDescriptionPopup.Instance.popupX = showAt[0];
+ LinkDescriptionPopup.Instance.popupY = showAt[1];
+ LinkDescriptionPopup.Instance.display = true;
+
+ const rect = document.body.getBoundingClientRect();
+ if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) {
+ LinkDescriptionPopup.Instance.popupX -= 190;
+ TaskCompletionBox.popupX -= 40;
+ }
+ if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) {
+ LinkDescriptionPopup.Instance.popupY -= 40;
+ TaskCompletionBox.popupY -= 40;
+ }
+
+ setTimeout(
+ action(() => {
+ TaskCompletionBox.taskCompleted = false;
+ }),
+ 2500
+ );
+ }
+ return linkDoc;
+ });
+
+ const a = source.layout_unrendered ? 'link_anchor_1?.annotationOn' : 'link_anchor_1';
+ const b = target.layout_unrendered ? 'link_anchor_2?.annotationOn' : 'link_anchor_2';
+
+ return makeLink(
+ Docs.Create.LinkDocument(
+ source,
+ target,
+ {
+ acl_Guest: SharingPermissions.Augment,
+ _acl_Guest: SharingPermissions.Augment,
+ title: ComputedField.MakeFunction('generateLinkTitle(this)') as any,
+ link_anchor_1_useSmallAnchor: source.useSmallAnchor ? true : undefined,
+ link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined,
+ link_relationship: linkSettings.link_relationship,
+ link_description: linkSettings.link_description,
+ x: ComputedField.MakeFunction(`((this.${a}?.x||0)+(this.${b}?.x||0))/2`) as any,
+ y: ComputedField.MakeFunction(`((this.${a}?.y||0)+(this.${b}?.y||0))/2`) as any,
+ link_autoMoveAnchors: true,
+ _lockedPosition: true,
+ _layout_showCaption: '', // removed since they conflict with showing a link with a LinkBox (ie, line, not comparison box)
+ _layout_showTitle: '',
+ // _layout_showCaption: 'link_description',
+ // _layout_showTitle: 'link_relationship',
+ },
+ id
+ ),
+ showPopup
+ );
+ }
+
+ export function AssignScripts(doc: Doc, scripts?: { [key: string]: string | undefined }, funcs?: { [key: string]: string }) {
+ scripts &&
+ Object.keys(scripts).forEach(key => {
+ const script = scripts[key];
+ if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) {
+ (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = ScriptField.MakeScript(script, {
+ this: Doc.name,
+ dragData: Doc.DocDragDataName,
+ value: 'any',
+ _readOnly_: 'boolean',
+ scriptContext: 'any',
+ documentView: Doc.name,
+ heading: Doc.name,
+ checked: 'boolean',
+ containingTreeView: Doc.name,
+ altKey: 'boolean',
+ ctrlKey: 'boolean',
+ shiftKey: 'boolean',
+ });
+ }
+ });
+ funcs &&
+ Object.keys(funcs)
+ .filter(key => !key.endsWith('-setter'))
+ .forEach(key => {
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+ if (ScriptCast(cfield)?.script.originalScript !== funcs[key]) {
+ const setFunc = Cast(funcs[key + '-setter'], 'string', null);
+ (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = funcs[key] ? ComputedField.MakeFunction(funcs[key], { dragData: Doc.DocDragDataName }, { _readOnly_: true }, setFunc) : undefined;
+ }
+ });
+ return doc;
+ }
+ export function AssignOpts(doc: Doc | undefined, reqdOpts: DocumentOptions, items?: Doc[]) {
+ if (doc) {
+ const compareValues = (val1: any, val2: any) => {
+ if (val1 instanceof List && val2 instanceof List && val1.length === val2.length) {
+ return !val1.some(v => !val2.includes(v)) || !val2.some(v => val1.includes(v));
+ }
+ return val1 === val2;
+ };
+ Object.entries(reqdOpts).forEach(pair => {
+ const targetDoc = pair[0].startsWith('_') ? doc : Doc.GetProto(doc as Doc);
+ if (!Object.getOwnPropertyNames(targetDoc).includes(pair[0].replace(/^_/, '')) || !compareValues(pair[1], targetDoc[pair[0]])) {
+ targetDoc[pair[0]] = pair[1];
+ }
+ });
+ items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), 'data', item));
+ items && DocListCast(doc.data).forEach(item => Doc.IsSystem(item) && !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), 'data', item));
+ }
+ return doc;
+ }
+ export function AssignDocField(doc: Doc, field: string, creator: (reqdOpts: DocumentOptions, items?: Doc[]) => Doc, reqdOpts: DocumentOptions, items?: Doc[], scripts?: { [key: string]: string }, funcs?: { [key: string]: string }) {
+ // eslint-disable-next-line no-return-assign
+ return DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? (doc[field] = creator(reqdOpts, items)), scripts, funcs);
+ }
+
+ /**
+ *
+ * @param type the type of file.
+ * @param path the path to the file.
+ * @param options the document options.
+ * @param overwriteDoc the placeholder loading doc.
+ * @returns
+ */
+ export async function DocumentFromType(type: string, path: string, options: DocumentOptions, overwriteDoc?: Doc): Promise<Opt<Doc>> {
+ let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise<Doc | undefined>) | undefined;
+ if (type.indexOf('image') !== -1) {
+ ctor = Docs.Create.ImageDocument;
+ if (!options._width) options._width = 300;
+ }
+ if (type.indexOf('video') !== -1) {
+ ctor = Docs.Create.VideoDocument;
+ if (!options._width) options._width = 600;
+ if (!options._height) options._height = ((options._width as number) * 2) / 3;
+ }
+ if (type.indexOf('audio') !== -1) {
+ ctor = Docs.Create.AudioDocument;
+ }
+ if (type.indexOf('pdf') !== -1) {
+ ctor = Docs.Create.PdfDocument;
+ if (!options._width) options._width = 400;
+ if (!options._height) options._height = ((options._width as number) * 1200) / 927;
+ }
+ if (type.indexOf('csv') !== -1) {
+ ctor = Docs.Create.DataVizDocument;
+ if (!options._width) options._width = 400;
+ if (!options._height) options._height = ((options._width as number) * 1200) / 927;
+ }
+ // TODO:al+glr
+ // if (type.indexOf("map") !== -1) {
+ // ctor = Docs.Create.MapDocument;
+ // if (!options._width) options._width = 800;
+ // if (!options._height) options._height = (options._width as number) * 3 / 4;
+ // }
+ if (type.indexOf('html') !== -1) {
+ if (path.includes(window.location.hostname)) {
+ const s = path.split('/');
+ const id = s[s.length - 1];
+ return DocServer.GetRefField(id).then(field => {
+ if (field instanceof Doc) {
+ const embedding = Doc.MakeEmbedding(field);
+ embedding.x = (options.x as number) || 0;
+ embedding.y = (options.y as number) || 0;
+ embedding._width = (options._width as number) || 300;
+ embedding._height = (options._height as number) || (options._width as number) || 300;
+ return embedding;
+ }
+ return undefined;
+ });
+ }
+ ctor = Docs.Create.WebDocument;
+ // eslint-disable-next-line no-param-reassign
+ options = { ...options, _width: 400, _height: 512, title: path };
+ }
+
+ 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 {
+ const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data)
+ .filter(btnDoc => !btnDoc.hidden)
+ .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
+ .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc.title)
+ .map(dragDoc => ({
+ description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
+ event: undoable(() => {
+ const newDoc = DocUtils.copyDragFactory(dragDoc);
+ if (newDoc) {
+ newDoc.author = ClientUtils.CurrentUserEmail();
+ newDoc.x = x;
+ newDoc.y = y;
+ Doc.SetSelectOnLoad(newDoc);
+ if (pivotField) {
+ newDoc[pivotField] = pivotValue;
+ }
+ docAdder?.(newDoc);
+ }
+ }, StrCast(dragDoc.title)),
+ icon: Doc.toIcon(dragDoc),
+ })) as ContextMenuProps[];
+ ContextMenu.Instance.addItem({
+ description: 'Create document',
+ subitems: documentList,
+ icon: 'file',
+ });
+ !simpleMenu &&
+ ContextMenu.Instance.addItem({
+ description: 'Styled Notes',
+ subitems: DocListCast((Doc.UserDoc().template_notes as Doc).data).map(note => ({
+ description: ':' + StrCast(note.title),
+ event: undoable(() => {
+ const textDoc = Docs.Create.TextDocument('', {
+ _width: 200,
+ x,
+ y,
+ _layout_autoHeight: note._layout_autoHeight !== false,
+ title: StrCast(note.title) + '#' + (note.embeddingCount = NumCast(note.embeddingCount) + 1),
+ });
+ textDoc.layout_fieldKey = 'layout_' + note.title;
+ textDoc[textDoc.layout_fieldKey] = note;
+ if (pivotField) {
+ textDoc[pivotField] = pivotValue;
+ }
+ docTextAdder(textDoc);
+ }, 'create quick note'),
+ icon: StrCast(note.icon) as IconProp,
+ })) as ContextMenuProps[],
+ icon: 'sticky-note',
+ });
+ const userDocList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[1]?.data)
+ .filter(btnDoc => !btnDoc.hidden)
+ .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
+ .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyNote && doc.title)
+ .map(dragDoc => ({
+ description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
+ event: undoable(() => {
+ const newDoc = DocUtils.delegateDragFactory(dragDoc);
+ if (newDoc) {
+ newDoc.author = ClientUtils.CurrentUserEmail();
+ newDoc.x = x;
+ newDoc.y = y;
+ Doc.SetSelectOnLoad(newDoc);
+ if (pivotField) {
+ newDoc[pivotField] = pivotValue;
+ }
+ docAdder?.(newDoc);
+ }
+ }, StrCast(dragDoc.title)),
+ icon: Doc.toIcon(dragDoc),
+ })) as ContextMenuProps[];
+ ContextMenu.Instance.addItem({
+ description: 'User Templates',
+ subitems: userDocList,
+ icon: 'file',
+ });
+ }
+
+ // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView)
+
+ /**
+ * Applies a template to a Doc and logs the action with the UndoManager
+ * If the template already exists and has been registered, it can be specified by it's signature name (e.g., 'icon' not 'layout_icon').
+ * Alternatively, the signature can be omitted and the template can be provided.
+ * @param doc the Doc to apply the template to.
+ * @param creator a function that will create the template if it doesn't exist
+ * @param templateSignature the signature name for a template that has already been created and registered on the userDoc. (can be "" if template is provide)
+ * @param template the template to use (optional if templateSignature is provided)
+ * @returns doc
+ */
+ export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', template?: Doc) {
+ const batch = UndoManager.StartBatch('makeCustomViewClicked');
+ createCustomView(doc, creator, templateSignature || StrCast(template?.title), template);
+ batch.end();
+ return doc;
+ }
+ export function findTemplate(templateName: string, type: string) {
+ let docLayoutTemplate: Opt<Doc>;
+ const iconViews = DocListCast(Cast(Doc.UserDoc().template_icons, Doc, null)?.data);
+ const templBtns = DocListCast(Cast(Doc.UserDoc().template_buttons, Doc, null)?.data);
+ const noteTypes = DocListCast(Cast(Doc.UserDoc().template_notes, Doc, null)?.data);
+ const userTypes = DocListCast(Cast(Doc.UserDoc().template_user, Doc, null)?.data);
+ const clickFuncs = DocListCast(Cast(Doc.UserDoc().template_clickFuncs, Doc, null)?.data);
+ const allTemplates = iconViews
+ .concat(templBtns)
+ .concat(noteTypes)
+ .concat(userTypes)
+ .concat(clickFuncs)
+ .map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc)
+ .filter(doc => doc.isTemplateDoc);
+ // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized
+ // first try to find a template that matches the specific document type (<typeName>_<templateName>). otherwise, fallback to a general match on <templateName>
+ !docLayoutTemplate &&
+ allTemplates.forEach(tempDoc => {
+ StrCast(tempDoc.title) === templateName + '_' + type && (docLayoutTemplate = tempDoc);
+ });
+ !docLayoutTemplate &&
+ allTemplates.forEach(tempDoc => {
+ StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc);
+ });
+ return docLayoutTemplate;
+ }
+ export function createCustomView(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) {
+ const templateName = templateSignature.replace(/\(.*\)/, '');
+ doc.layout_fieldKey = 'layout_' + (templateSignature || (docLayoutTemplate?.title ?? ''));
+ // eslint-disable-next-line no-param-reassign
+ docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type));
+
+ const customName = 'layout_' + templateSignature;
+ const _width = NumCast(doc._width);
+ const _height = NumCast(doc._height);
+ const options = { title: 'data', backgroundColor: StrCast(doc.backgroundColor), _layout_autoHeight: true, _width, x: -_width / 2, y: -_height / 2, _layout_showSidebar: false };
+
+ if (docLayoutTemplate) {
+ if (docLayoutTemplate !== doc[customName]) {
+ Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined);
+ }
+ } else {
+ let fieldTemplate: Opt<Doc>;
+ if (doc.data instanceof RichTextField || typeof doc.data === 'string') {
+ fieldTemplate = Docs.Create.TextDocument('', options);
+ } else if (doc.data instanceof PdfField) {
+ fieldTemplate = Docs.Create.PdfDocument('http://www.msn.com', options);
+ } else if (doc.data instanceof VideoField) {
+ fieldTemplate = Docs.Create.VideoDocument('http://www.cs.brown.edu', options);
+ } else if (doc.data instanceof AudioField) {
+ fieldTemplate = Docs.Create.AudioDocument('http://www.cs.brown.edu', options);
+ } else if (doc.data instanceof ImageField) {
+ fieldTemplate = Docs.Create.ImageDocument('http://www.cs.brown.edu', options);
+ }
+ const docTemplate = creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + '(' + doc.title + ')', isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
+ fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
+ docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
+ }
+ }
+ export function makeCustomView(doc: Doc, custom: boolean, layout: string) {
+ Doc.setNativeView(doc);
+ if (custom) {
+ makeCustomViewClicked(doc, Docs.Create.StackingDocument, layout, undefined);
+ }
+ }
+ export function iconify(doc: Doc) {
+ const layoutFieldKey = Cast(doc.layout_fieldKey, 'string', null);
+ DocUtils.makeCustomViewClicked(doc, Docs.Create.StackingDocument, 'icon', undefined);
+ if (layoutFieldKey && layoutFieldKey !== 'layout' && layoutFieldKey !== 'layout_icon') doc.deiconifyLayout = layoutFieldKey.replace('layout_', '');
+ }
+
+ export function pileup(docList: Doc[], x?: number, y?: number, size: number = 55, create: boolean = true) {
+ runInAction(() => {
+ docList.forEach((doc, i) => {
+ const d = doc;
+ DocUtils.iconify(d);
+ d.x = Math.cos((Math.PI * 2 * i) / docList.length) * size - size;
+ d.y = Math.sin((Math.PI * 2 * i) / docList.length) * size - size;
+ d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ });
+ });
+ if (create) {
+ const newCollection = Docs.Create.PileDocument(docList, { title: 'pileup', _freeform_noZoom: true, x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, dragWhenActive: true, _layout_fitWidth: false });
+ newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - size;
+ newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - size;
+ newCollection._width = newCollection._height = size * 2;
+ return newCollection;
+ }
+ return undefined;
+ }
+ export function makeIntoPortal(doc: Doc, layoutDoc: Doc, allLinks: Doc[]) {
+ const portalLink = allLinks.find(d => d.link_anchor_1 === doc && d.link_relationship === 'portal to:portal from');
+ if (!portalLink) {
+ DocUtils.MakeLink(
+ doc,
+ Docs.Create.FreeformDocument([], {
+ _width: NumCast(layoutDoc._width) + 10,
+ _height: Math.max(NumCast(layoutDoc._height), NumCast(layoutDoc._width) + 10),
+ _isLightbox: true,
+ _layout_fitWidth: true,
+ title: StrCast(doc.title) + ' [Portal]',
+ }),
+ { link_relationship: 'portal to:portal from' }
+ );
+ }
+ doc.followLinkLocation = OpenWhere.lightbox;
+ doc.onClick = FollowLinkScript();
+ }
+
+ export function LeavePushpin(doc: Doc, annotationField: string) {
+ if (doc.followLinkToggle) return undefined;
+ const context = Cast(doc.embedContainer, Doc, null) ?? Cast(doc.annotationOn, Doc, null);
+ const hasContextAnchor = Doc.Links(doc).some(l => (l.link_anchor_2 === doc && Cast(l.link_anchor_1, Doc, null)?.annotationOn === context) || (l.link_anchor_1 === doc && Cast(l.link_anchor_2, Doc, null)?.annotationOn === context));
+ if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
+ const pushpin = Docs.Create.FontIconDocument({
+ title: '',
+ annotationOn: Cast(doc.annotationOn, Doc, null),
+ followLinkToggle: true,
+ icon: 'map-pin',
+ x: Cast(doc.x, 'number', null),
+ y: Cast(doc.y, 'number', null),
+ backgroundColor: '#ACCEF7',
+ layout_hideAllLinks: true,
+ _width: 15,
+ _height: 15,
+ _xPadding: 0,
+ onClick: FollowLinkScript(),
+ _timecodeToShow: Cast(doc._timecodeToShow, 'number', null),
+ });
+ Doc.AddDocToList(context, annotationField, pushpin);
+ DocUtils.MakeLink(pushpin, doc, { link_relationship: 'pushpin' }, '');
+ doc._timecodeToShow = undefined;
+ return pushpin;
+ }
+ return undefined;
+ }
+
+ // /**
+ // *
+ // * @param dms Degree Minute Second format exif gps data
+ // * @param ref ref that determines negativity of decimal coordinates
+ // * @returns a decimal format of gps latitude / longitude
+ // */
+ // function getDecimalfromDMS(dms?: number[], ref?: string) {
+ // if (dms && ref) {
+ // let degrees = dms[0] / dms[1];
+ // let minutes = dms[2] / dms[3] / 60.0;
+ // let seconds = dms[4] / dms[5] / 3600.0;
+
+ // if (['S', 'W'].includes(ref)) {
+ // degrees = -degrees; minutes = -minutes; seconds = -seconds
+ // }
+ // return (degrees + minutes + seconds).toFixed(5);
+ // }
+ // }
+
+ function ConvertDMSToDD(degrees: number, minutes: number, seconds: number, direction: string) {
+ let dd = degrees + minutes / 60 + seconds / (60 * 60);
+ if (direction === 'S' || direction === 'W') {
+ dd *= -1;
+ } // Don't do anything for N or E
+ return dd;
+ }
+
+ export function assignImageInfo(result: Upload.FileInformation, protoIn: Doc) {
+ const proto = protoIn;
+ if (Upload.isImageInformation(result)) {
+ const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
+ const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase();
+ proto.data_nativeOrientation = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined);
+ proto.data_nativeWidth = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+ proto.data_nativeHeight = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+ if (NumCast(proto.data_nativeOrientation) >= 5) {
+ proto.data_nativeHeight = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+ proto.data_nativeWidth = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+ }
+ proto.data_exif = JSON.stringify(result.exifData?.data);
+ proto.data_contentSize = result.contentSize;
+ // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates
+ const latitude = result.exifData?.data?.GPSLatitude;
+ const latitudeDirection = result.exifData?.data?.GPSLatitudeRef;
+ const longitude = result.exifData?.data?.GPSLongitude;
+ const longitudeDirection = result.exifData?.data?.GPSLongitudeRef;
+ if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) {
+ proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection);
+ proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection);
+ }
+ }
+ }
+
+ async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, overwriteDoc?: Doc) {
+ if (result instanceof Error) {
+ alert(`Upload failed: ${result.message}`);
+ return;
+ }
+ const full = { ...options, _width: 400, title: name };
+ const pathname = result.accessPaths.agnostic.client;
+ const doc = await DocUtils.DocumentFromType(type, pathname, full, overwriteDoc);
+ if (doc) {
+ const proto = Doc.GetProto(doc);
+ proto.text = result.rawText;
+ !(result instanceof Error) && DocUtils.assignImageInfo(result, proto);
+ if (Upload.isVideoInformation(result)) {
+ proto.data_duration = result.duration;
+ }
+ if (overwriteDoc) {
+ Doc.removeCurrentlyLoading(overwriteDoc);
+ }
+ generatedDocuments.push(doc);
+ }
+ }
+
+ export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, annotationOn?: Doc, backgroundColor?: string) {
+ const defaultTextTemplate = DocCast(Doc.UserDoc().defaultTextLayout);
+ const tbox = Docs.Create.TextDocument('', {
+ annotationOn,
+ backgroundColor,
+ x,
+ y,
+ title,
+ ...(defaultTextTemplate
+ ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance
+ : {
+ _width: width || 200,
+ _height: 35,
+ _layout_centered: BoolCast(Doc.UserDoc()._layout_centered),
+ _layout_fitWidth: true,
+ _layout_autoHeight: true,
+ }),
+ });
+
+ if (defaultTextTemplate) {
+ tbox.layout_fieldKey = 'layout_' + StrCast(defaultTextTemplate.title);
+ Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = defaultTextTemplate; // set the text doc's layout to render with the text template
+ tbox[DocData].proto = defaultTextTemplate; // and also set the text doc to inherit from the template (this allows the template to specify default field values)
+ }
+ return tbox;
+ }
+
+ export function uploadYoutubeVideoLoading(videoId: string, options: DocumentOptions, overwriteDoc?: Doc) {
+ const generatedDocuments: Doc[] = [];
+ Networking.UploadYoutubeToServer(videoId, overwriteDoc?.[Id]).then(upfiles => {
+ const {
+ source: { newFilename, mimetype },
+ result,
+ } = upfiles.lastElement();
+ if ((result as any).message) {
+ if (overwriteDoc) {
+ overwriteDoc.isLoading = false;
+ overwriteDoc.loadingError = (result as any).message;
+ Doc.removeCurrentlyLoading(overwriteDoc);
+ }
+ } else newFilename && processFileupload(generatedDocuments, newFilename, mimetype ?? '', result, options, overwriteDoc);
+ });
+ }
+
+ /**
+ * uploadFilesToDocs will take in an array of Files, and creates documents for the
+ * new files.
+ *
+ * @param files an array of files that will be uploaded
+ * @param options options to use while uploading
+ * @returns
+ */
+ export async function uploadFilesToDocs(files: File[], options: DocumentOptions) {
+ const generatedDocuments: Doc[] = [];
+
+ // These files do not have overwriteDocs, so we do not set the guid and let the client generate one.
+ const fileNoGuidPairs: Networking.FileGuidPair[] = files.map(file => ({ file }));
+
+ const upfiles = await Networking.UploadFilesToServer(fileNoGuidPairs);
+ upfiles.forEach(({ source: { newFilename, mimetype }, result }) => {
+ newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options);
+ });
+ return generatedDocuments;
+ }
+
+ export function uploadFileToDoc(file: File, options: DocumentOptions, overwriteDoc: Doc) {
+ const generatedDocuments: Doc[] = [];
+ // 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: { newFilename, mimetype },
+ result,
+ } = upfiles.lastElement() ?? { source: { newFilename: '', mimetype: '' }, result: { message: 'upload failed' } };
+ if ((result as any).message) {
+ if (overwriteDoc) {
+ overwriteDoc.loadingError = (result as any).message;
+ Doc.removeCurrentlyLoading(overwriteDoc);
+ }
+ } else newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options, overwriteDoc);
+ });
+ }
+
+ // copies the specified drag factory document
+ export function copyDragFactory(dragFactory: Doc) {
+ if (!dragFactory) return undefined;
+ const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true);
+ if (ndoc && dragFactory.dragFactory_count !== undefined) {
+ dragFactory.dragFactory_count = NumCast(dragFactory.dragFactory_count) + 1;
+ Doc.SetInPlace(ndoc, 'title', ndoc.title + ' ' + NumCast(dragFactory.dragFactory_count).toString(), true);
+ }
+
+ return ndoc;
+ }
+ export function delegateDragFactory(dragFactory: Doc) {
+ const ndoc = Doc.MakeDelegateWithProto(dragFactory);
+ if (ndoc && dragFactory.dragFactory_count !== undefined) {
+ dragFactory.dragFactory_count = NumCast(dragFactory.dragFactory_count) + 1;
+ Doc.GetProto(ndoc).title = ndoc.title + ' ' + NumCast(dragFactory.dragFactory_count).toString();
+ }
+ return ndoc;
+ }
+
+ export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') {
+ const { clone, map, linkMap } = await Doc.MakeClone(doc);
+ const proms = new Set<string>();
+ function replacer(key: any, value: any) {
+ if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined;
+ if (value?.__type === 'image') {
+ const extension = value.url.replace(/.*\./, '');
+ proms.add(value.url.replace('.' + extension, '_o.' + extension));
+ return SerializationHelper.Serialize(new ImageField(value.url));
+ }
+ if (value?.__type === 'pdf') {
+ proms.add(value.url);
+ return SerializationHelper.Serialize(new PdfField(value.url));
+ }
+ if (value?.__type === 'audio') {
+ proms.add(value.url);
+ return SerializationHelper.Serialize(new AudioField(value.url));
+ }
+ if (value?.__type === 'video') {
+ proms.add(value.url);
+ return SerializationHelper.Serialize(new VideoField(value.url));
+ }
+ if (
+ value instanceof Doc ||
+ value instanceof ScriptField ||
+ value instanceof RichTextField ||
+ value instanceof InkField ||
+ value instanceof CsvField ||
+ value instanceof WebField ||
+ value instanceof DateField ||
+ value instanceof ProxyField ||
+ value instanceof ComputedField
+ ) {
+ return SerializationHelper.Serialize(value);
+ }
+ if (value instanceof Array && key !== ListFieldName && key !== InkDataFieldName) return { fields: value, __type: 'list' };
+ return value;
+ }
+
+ const docs: { [id: string]: any } = {};
+ const links: { [id: string]: any } = {};
+ Array.from(map.entries()).forEach(f => {
+ docs[f[0]] = f[1];
+ });
+ Array.from(linkMap.entries()).forEach(l => {
+ links[l[0]] = l[1];
+ });
+ const jsonDocs = JSON.stringify({ id: clone[Id], docs, links }, decycle(replacer));
+
+ const zip = new JSZip();
+ let count = 0;
+ const promArr = Array.from(proms)
+ .filter(url => url?.startsWith('/files'))
+ .map(url => url.replace('/', '')); // window.location.origin));
+ console.log(promArr.length);
+ if (!promArr.length) {
+ zip.file('docs.json', jsonDocs);
+ zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
+ } else
+ promArr.forEach((url, i) => {
+ // loading a file and add it in a zip file
+ JSZipUtils.getBinaryContent(window.location.origin + '/' + url, (err: any, data: any) => {
+ if (err) throw err; // or handle the error
+ // // Generate a directory within the Zip file structure
+ // const assets = zip.folder("assets");
+ // assets.file(filename, data, {binary: true});
+ const assetPathOnServer = promArr[i].replace(window.location.origin + '/', '').replace(/\//g, '%%%');
+ zip.file(assetPathOnServer, data, { binary: true });
+ console.log(' => ' + url);
+ if (++count === promArr.length) {
+ zip.file('docs.json', jsonDocs);
+ zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
+ // const a = document.createElement("a");
+ // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
+ // a.href = url;
+ // a.download = `DocExport-${this.props.Document[Id]}.zip`;
+ // a.click();
+ }
+ });
+ });
+ }
+}
+
+export function FollowLinkScript() {
+ return ScriptField.MakeScript('return followLink(this,altKey)', { altKey: 'boolean' });
+}
+
+export function IsFollowLinkScript(field: FieldResult<FieldType>) {
+ return ScriptCast(field)?.script.originalScript.includes('return followLink(');
+}
+
+ScriptingGlobals.add('Docs', Docs);
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc, asDelegate?: boolean) {
+ return dragFactory instanceof Doc ? (asDelegate ? DocUtils.delegateDragFactory(dragFactory) : DocUtils.copyDragFactory(dragFactory)) : dragFactory;
+});
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function makeDelegate(proto: any) {
+ const d = Docs.Create.DelegateDocument(proto, { title: 'child of ' + proto.title });
+ return d;
+});
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function generateLinkTitle(link: Doc) {
+ const linkAnchor1title = link.link_anchor_1 && link.link_anchor_1 !== link ? Cast(link.link_anchor_1, Doc, null)?.title : '<?>';
+ const linkAnchor2title = link.link_anchor_2 && link.link_anchor_2 !== link ? Cast(link.link_anchor_2, Doc, null)?.title : '<?>';
+ const relation = link.link_relationship || 'to';
+ return `${linkAnchor1title} (${relation}) ${linkAnchor2title}`;
+});
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 1123bcac9..b4ad9c17d 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -25,16 +25,14 @@ export enum DocumentType {
MAP = 'map',
DATAVIZ = 'dataviz',
LOADING = 'loading',
- SIMULATION = 'simulation', //physics simulation
+ SIMULATION = 'simulation', // physics simulation
// special purpose wrappers that either take no data or are compositions of lower level types
LINK = 'link',
IMPORT = 'import',
PRES = 'presentation',
PRESELEMENT = 'preselement',
- YOUTUBE = 'youtube',
COMPARISON = 'comparison',
- GROUP = 'group',
PUSHPIN = 'pushpin',
MAPROUTE = 'maproute',
CALENDAR = 'calendar',
@@ -56,11 +54,10 @@ export enum CollectionViewType {
Carousel = 'carousel',
Carousel3D = '3D Carousel',
Linear = 'linear',
- //Staff = "staff",
Map = 'map',
Grid = 'grid',
Pile = 'pileup',
StackedTimeline = 'stacked timeline',
NoteTaking = 'notetaking',
- Calendar = 'calendar'
+ Calendar = 'calendar',
}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b160379df..1c18c0d84 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,68 +1,26 @@
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { action, reaction, runInAction } from 'mobx';
+/* eslint-disable prefer-destructuring */
+/* eslint-disable default-param-last */
+/* eslint-disable no-use-before-define */
+import { reaction } from 'mobx';
import { basename } from 'path';
-import { OmitKeys, Utils } from '../../Utils';
+import { ClientUtils, OmitKeys } from '../../ClientUtils';
import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc';
-import { DocData, Initializing } from '../../fields/DocSymbols';
-import { Id } from '../../fields/FieldSymbols';
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, CreateLinkToActiveAudio, Doc, FieldType, Opt, updateCachedAcls } from '../../fields/Doc';
+import { Initializing } from '../../fields/DocSymbols';
import { HtmlField } from '../../fields/HtmlField';
-import { InkField, PointData } from '../../fields/InkField';
+import { InkField } from '../../fields/InkField';
import { List } from '../../fields/List';
import { RichTextField } from '../../fields/RichTextField';
import { SchemaHeaderField } from '../../fields/SchemaHeaderField';
import { ComputedField, ScriptField } from '../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types';
-import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField, YoutubeField } from '../../fields/URLField';
-import { SharingPermissions, inheritParentAcls } from '../../fields/util';
-import { Upload } from '../../server/SharedMediaTypes';
+import { ScriptCast, StrCast } from '../../fields/Types';
+import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from '../../fields/URLField';
+import { SharingPermissions } from '../../fields/util';
+import { PointData } from '../../pen-gestures/GestureTypes';
import { DocServer } from '../DocServer';
-import { Networking } from '../Network';
-import { YoutubeBox } from '../apis/youtube/YoutubeBox';
-import { DragManager, dropActionType } from '../util/DragManager';
-import { FollowLinkScript } from '../util/LinkFollower';
-import { LinkManager } from '../util/LinkManager';
-import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { UndoManager, undoable } from '../util/UndoManager';
-import { ContextMenu } from '../views/ContextMenu';
-import { ContextMenuProps } from '../views/ContextMenuItem';
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke';
-import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { CollectionView } from '../views/collections/CollectionView';
-import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView';
-import { AudioBox, media_state } from '../views/nodes/AudioBox';
-import { ComparisonBox } from '../views/nodes/ComparisonBox';
-import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox';
-import { OpenWhere } from '../views/nodes/DocumentView';
-import { EquationBox } from '../views/nodes/EquationBox';
-import { FieldViewProps } from '../views/nodes/FieldView';
-import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
-import { FunctionPlotBox } from '../views/nodes/FunctionPlotBox';
-import { ImageBox } from '../views/nodes/ImageBox';
-import { KeyValueBox } from '../views/nodes/KeyValueBox';
-import { LabelBox } from '../views/nodes/LabelBox';
-import { LinkBox } from '../views/nodes/LinkBox';
-import { LinkDescriptionPopup } from '../views/nodes/LinkDescriptionPopup';
-import { LoadingBox } from '../views/nodes/LoadingBox';
-import { MapBox } from '../views/nodes/MapBox/MapBox';
-import { MapPushpinBox } from '../views/nodes/MapBox/MapPushpinBox';
-import { PDFBox } from '../views/nodes/PDFBox';
-import { PhysicsSimulationBox } from '../views/nodes/PhysicsBox/PhysicsSimulationBox';
-import { RecordingBox } from '../views/nodes/RecordingBox/RecordingBox';
-import { ScreenshotBox } from '../views/nodes/ScreenshotBox';
-import { ScriptingBox } from '../views/nodes/ScriptingBox';
-import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
-import { VideoBox } from '../views/nodes/VideoBox';
-import { WebBox } from '../views/nodes/WebBox';
-import { CalendarBox } from '../views/nodes/calendarBox/CalendarBox';
-import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
-import { PresBox } from '../views/nodes/trails/PresBox';
-import { PresElementBox } from '../views/nodes/trails/PresElementBox';
-import { SearchBox } from '../views/search/SearchBox';
+import { dropActionType } from '../util/DropActionTypes';
import { CollectionViewType, DocumentType } from './DocumentTypes';
-const { DFLT_IMAGE_NATIVE_DIM } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
-const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', ''));
class EmptyBox {
public static LayoutString() {
return '';
@@ -73,6 +31,7 @@ export enum FInfoFieldType {
string = 'string',
boolean = 'boolean',
number = 'number',
+ // eslint-disable-next-line @typescript-eslint/no-shadow
Doc = 'Doc',
enumeration = 'enum',
date = 'date',
@@ -83,7 +42,7 @@ export class FInfo {
description: string = '';
readOnly: boolean = false;
fieldType?: FInfoFieldType;
- values?: Field[];
+ values?: FieldType[];
filterable?: boolean = true; // can be used as a Filter in FilterPanel
// format?: string; // format to display values (e.g, decimal places, $, etc)
@@ -134,7 +93,7 @@ class DocInfo extends FInfo {
}
class DimInfo extends FInfo {
fieldType? = FInfoFieldType.enumeration;
- values? = [DimUnit.Pixel, DimUnit.Ratio];
+ values? = []; // DimUnit.Pixel, DimUnit.Ratio];
readOnly = false;
filterable = false;
override searchable = () => false;
@@ -190,7 +149,7 @@ type STRt = StrInfo | string;
type LISTt = ListInfo | List<any>;
type DOCt = DocInfo | Doc;
type RTFt = RtfInfo | RichTextField;
-type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio;
+type DIMt = DimInfo; // | typeof DimUnit.Pixel | typeof DimUnit.Ratio;
type PEVt = PEInfo | 'none' | 'all';
type COLLt = CTypeInfo | CollectionViewType;
type DROPt = DAInfo | dropActionType;
@@ -233,14 +192,16 @@ export class DocumentOptions {
_nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)', false);
_nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)', false);
- 'acl-Guest'?: STRt = new StrInfo("permissions granted to users logged in as 'guest' (either view, or private)"); // public permissions
- '_acl-Guest'?: string; // public permissions
+ acl?: STRt = new StrInfo('unused except as a display category in KeyValueBox');
+ acl_Guest?: STRt = new StrInfo("permissions granted to users logged in as 'guest' (either view, or private)"); // public permissions
+ _acl_Guest?: string; // public permissions
type?: DTYPEt = new DTypeInfo('type of document', true);
type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection
_type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection
title?: STRt = new StrInfo('title of document', true);
title_custom?: BOOLt = new BoolInfo('whether title is a default or has been intentionally set');
caption?: RichTextField;
+ systemIcon?: STRt = new StrInfo("name of icon to use to represent document's type");
author?: string; // STRt = new StrInfo('creator of document'); // bcz: don't change this. Otherwise, the userDoc's field Infos will have a FieldInfo assigned to its author field which will render it unreadable
author_date?: DATEt = new DateInfo('date the document was created', true);
annotationOn?: DOCt = new DocInfo('document annotated by this document', false);
@@ -252,14 +213,20 @@ export class DocumentOptions {
opacity?: NUMt = new NumInfo('document opacity', false);
viewTransitionTime?: NUMt = new NumInfo('transition duration for view parameters', false);
dontRegisterView?: BOOLt = new BoolInfo('are views of this document registered so that they can be found when following links, etc', false);
- _undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
- undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
+ _undoIgnoreFields?: List<string>; // 'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
+ undoIgnoreFields?: List<string>; // 'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
_header_height?: NUMt = new NumInfo('height of document header used for displaying title', false);
_header_fontSize?: NUMt = new NumInfo('font size of header of custom notes', false);
_header_pointerEvents?: PEVt = new PEInfo('types of events the header of a custom text document can consume');
_lockedPosition?: BOOLt = new BoolInfo("lock the x,y coordinates of the document so that it can't be dragged");
_lockedTransform?: BOOLt = new BoolInfo('lock the freeform_panx,freeform_pany and scale parameters of the document so that it be panned/zoomed');
+ dataViz_title?: string;
+ dataViz_line?: string;
+ dataViz_pie?: string;
+ dataViz_histogram?: string;
+ dataViz?: string;
+
layout?: string | Doc; // default layout string or template document
layout_isSvg?: BOOLt = new BoolInfo('whether document decorations and other selections should handle pointerEvents for svg content or use doc bounding box');
layout_keyValue?: STRt = new StrInfo('layout definition for showing keyValue view of document', false);
@@ -313,6 +280,7 @@ export class DocumentOptions {
_text_fontSize?: string;
_text_fontFamily?: string;
_text_fontWeight?: string;
+ fontSize?: string;
_pivotField?: string; // field key used to determine headings for sections in stacking, masonry, pivot views
infoWindowOpen?: BOOLt = new BoolInfo('whether info window corresponding to pin is open (on MapDocuments)');
@@ -321,7 +289,7 @@ export class DocumentOptions {
_label_maxFontSize?: NUMt = new NumInfo('maximum font size for labelBoxes', false);
stroke_width?: NUMt = new NumInfo('width of an ink stroke', false);
stroke_showLabel?: BOOLt = new BoolInfo('show label inside of stroke');
- mediaState?: STRt = new StrInfo(`status of audio/video media document: ${media_state.PendingRecording}, ${media_state.Recording}, ${media_state.Paused}, ${media_state.Playing}`, false);
+ mediaState?: STRt = new StrInfo(`status of audio/video media document:`); // ${mediaState.PendingRecording}, ${mediaState.Recording}, ${mediaState.Paused}, ${mediaState.Playing}`, false);
recording?: BOOLt = new BoolInfo('whether WebCam is recording or not');
slides?: DOCt = new DocInfo('presentation slide associated with video recording (bcz: should be renamed!!)');
autoPlayAnchors?: BOOLt = new BoolInfo('whether to play audio/video when an anchor is clicked in a stackedTimeline.');
@@ -391,6 +359,7 @@ export class DocumentOptions {
// STOPPING HERE
// freeform properties
+ freeform?: STRt = new StrInfo('');
_freeform_backgroundGrid?: BOOLt = new BoolInfo('whether background grid is shown on freeform collections');
_freeform_scale_min?: NUMt = new NumInfo('how far out a view can zoom (used by image/videoBoxes that are clipped');
_freeform_scale_max?: NUMt = new NumInfo('how far in a view can zoom (used by sidebar freeform views');
@@ -401,7 +370,7 @@ export class DocumentOptions {
_freeform_noZoom?: BOOLt = new BoolInfo('disables zooming (used by Pile docs)');
_freeform_fitContentsToBox?: BOOLt = new BoolInfo('whether a freeformview should zoom/scale to create a shrinkwrapped view of its content');
- //BUTTONS
+ // BUTTONS
buttonText?: string;
btnType?: string;
btnList?: List<string>;
@@ -413,7 +382,7 @@ export class DocumentOptions {
switchToggle?: boolean;
badgeValue?: ScriptField;
- //LINEAR VIEW
+ // LINEAR VIEW
linearView_IsOpen?: BOOLt = new BoolInfo('is linear view open');
linearView_Expandable?: BOOLt = new BoolInfo('can linear view be expanded');
linearView_Dropdown?: BOOLt = new BoolInfo('can linear view be opened as a dropdown');
@@ -421,9 +390,9 @@ export class DocumentOptions {
flexGap?: NUMt = new NumInfo('Linear view flex gap');
flexDirection?: 'unset' | 'row' | 'column' | 'row-reverse' | 'column-reverse';
+ link?: string;
link_description?: string; // added for links
link_relationship?: string; // type of relatinoship a link represents
- link_displayLine?: BOOLt = new BoolInfo('whether a link line should be dipslayed between the two link anchors');
link_displayArrow?: BOOLt = new BoolInfo("whether to display link's directional arrowhead");
link_anchor_1?: DOCt = new DocInfo('start anchor of a link');
link_anchor_2?: DOCt = new DocInfo('end anchor of a link');
@@ -431,7 +400,7 @@ export class DocumentOptions {
link_anchor_1_useSmallAnchor?: BOOLt = new BoolInfo('whether link_anchor_1 of a link should use a miniature anchor dot (as when the anchor is a text selection)');
link_anchor_2_useSmallAnchor?: BOOLt = new BoolInfo('whether link_anchor_1 of a link should use a miniature anchor dot (as when the anchor is a text selection)');
link_relationshipList?: List<string>; // for storing different link relationships (when set by user in the link editor)
- link_relationshipSizes?: List<number>; //stores number of links contained in each relationship
+ link_relationshipSizes?: List<number>; // stores number of links contained in each relationship
link_colorList?: List<string>; // colors of links corresponding to specific link relationships
followLinkZoom?: BOOLt = new BoolInfo('whether to zoom to the target of a link');
followLinkToggle?: BOOLt = new BoolInfo('whether target of link should be toggled on and off when following a link to it');
@@ -463,7 +432,7 @@ export class DocumentOptions {
dragFactory_count?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)', false, true);
dragFactory?: DOCt = new DocInfo('document to create when dragging with a suitable onDragStart script', false);
clickFactory?: DOCt = new DocInfo('document to create when clicking on a button with a suitable onClick script', false);
- onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop
+ onDragStart?: ScriptField; // script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop
target?: Doc; // available for use in scripts. used to provide a document parameter to the script (Note, this is a convenience entry since any field could be used for parameterizing a script)
tags?: LISTt = new ListInfo('hashtags added to document, typically using a text view', true);
treeView_HideTitle?: BOOLt = new BoolInfo('whether to hide the top document title of a tree view');
@@ -502,8 +471,6 @@ export class DocumentOptions {
export const DocOptions = new DocumentOptions();
export namespace Docs {
- export let newAccount: boolean = false;
-
export namespace Prototypes {
type LayoutSource = { LayoutString: (key: string) => string };
type PrototypeTemplate = {
@@ -518,117 +485,12 @@ export namespace Docs {
type PrototypeMap = Map<DocumentType, Doc>;
const defaultDataKey = 'data';
- const TemplateMap: TemplateMap = new Map([
- [
- DocumentType.RTF,
- {
- layout: { view: FormattedTextBox, dataField: 'text' },
- options: {
- _height: 35,
- _xMargin: 10,
- _yMargin: 10,
- layout_nativeDimEditable: true,
- layout_reflowVertical: true,
- layout_reflowHorizontal: true,
- defaultDoubleClick: 'ignore',
- systemIcon: 'BsFileEarmarkTextFill',
- },
- },
- ],
- [
- DocumentType.SEARCH,
- {
- layout: { view: SearchBox, dataField: defaultDataKey },
- options: { _width: 400 },
- },
- ],
- [
- DocumentType.IMG,
- {
- layout: { view: ImageBox, dataField: defaultDataKey },
- options: { freeform: '', systemIcon: 'BsFileEarmarkImageFill' },
- },
- ],
- [
- DocumentType.WEB,
- {
- layout: { view: WebBox, dataField: defaultDataKey },
- options: { _height: 300, _layout_fitWidth: true, layout_nativeDimEditable: true, layout_reflowVertical: true, waitForDoubleClickToClick: 'always', systemIcon: 'BsGlobe' },
- },
- ],
+ export const TemplateMap: TemplateMap = new Map([
[
- DocumentType.COL,
- {
- layout: { view: CollectionView, dataField: defaultDataKey },
- options: {
- _layout_fitWidth: true,
- freeform: '',
- _freeform_panX: 0,
- _freeform_panY: 0,
- _freeform_scale: 1,
- layout_nativeDimEditable: true,
- layout_reflowHorizontal: true,
- layout_reflowVertical: true,
- systemIcon: 'BsFillCollectionFill',
- },
- },
- ],
- [
- DocumentType.KVP,
- {
- layout: { view: KeyValueBox, dataField: defaultDataKey },
- options: { _layout_fitWidth: true, _height: 150 },
- },
- ],
- [
- DocumentType.VID,
- {
- layout: { view: VideoBox, dataField: defaultDataKey },
- options: { _layout_currentTimecode: 0, systemIcon: 'BsFileEarmarkPlayFill' },
- },
- ],
- [
- DocumentType.AUDIO,
- {
- layout: { view: AudioBox, dataField: defaultDataKey },
- options: { _height: 100, layout_fitWidth: true, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsFillVolumeUpFill' },
- },
- ],
- [
- DocumentType.REC,
- {
- layout: { view: VideoBox, dataField: defaultDataKey },
- options: { _height: 100, backgroundColor: 'pink', systemIcon: 'BsFillMicFill' },
- },
- ],
- [
- DocumentType.PDF,
- {
- layout: { view: PDFBox, dataField: defaultDataKey },
- options: { _layout_curPage: 1, _layout_fitWidth: true, layout_nativeDimEditable: true, layout_reflowVertical: true, systemIcon: 'BsFileEarmarkPdfFill' },
- },
- ],
- [
- DocumentType.MAP,
- {
- layout: { view: MapBox, dataField: defaultDataKey },
- options: { map: '', _height: 600, _width: 800, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsFillPinMapFill' },
- },
- ],
- [
- DocumentType.LINK,
+ DocumentType.GROUPDB,
{
- layout: { view: LinkBox, dataField: 'link' },
- options: {
- childDontRegisterViews: true,
- layout_hideLinkAnchors: true,
- _height: 1,
- _width: 1,
- link: '',
- link_description: '',
- color: 'lightBlue', // lightblue is default color for linking dot and link documents text comment area
- _dropPropertiesToRemove: new List(['onClick']),
- },
+ layout: { view: EmptyBox, dataField: defaultDataKey },
+ options: { acl: '', title: 'Global Group Database' },
},
],
[
@@ -636,195 +498,22 @@ export namespace Docs {
{
data: new List<Doc>(),
layout: { view: EmptyBox, dataField: defaultDataKey },
- options: { title: 'Global Script Database' },
- },
- ],
- [
- DocumentType.SCRIPTING,
- {
- layout: { view: ScriptingBox, dataField: defaultDataKey },
- options: { systemIcon: 'BsFileEarmarkCodeFill' },
- },
- ],
- [
- DocumentType.YOUTUBE,
- {
- layout: { view: YoutubeBox, dataField: defaultDataKey },
- },
- ],
- [
- DocumentType.LABEL,
- {
- layout: { view: LabelBox, dataField: 'title' },
- options: { _singleLine: true, layout_nativeDimEditable: true, layout_reflowHorizontal: true, layout_reflowVertical: true },
- },
- ],
- [
- DocumentType.EQUATION,
- {
- layout: { view: EquationBox, dataField: 'text' },
- options: {
- fontSize: '14px',
- layout_reflowHorizontal: true,
- layout_reflowVertical: true,
- layout_nativeDimEditable: true,
- layout_hideDecorationTitle: true,
- systemIcon: 'BsCalculatorFill',
- }, ///systemIcon: 'BsSuperscript' + BsSubscript
- },
- ],
- [
- DocumentType.FUNCPLOT,
- {
- layout: { view: FunctionPlotBox, dataField: defaultDataKey },
- options: {
- layout_reflowHorizontal: true,
- layout_reflowVertical: true,
- layout_nativeDimEditable: true,
- },
- },
- ],
- [
- DocumentType.BUTTON,
- {
- layout: { view: LabelBox, dataField: 'title' },
- options: { layout_nativeDimEditable: true, layout_reflowHorizontal: true, layout_reflowVertical: true },
- },
- ],
- [
- DocumentType.PRES,
- {
- layout: { view: PresBox, dataField: defaultDataKey },
- options: { defaultDoubleClick: 'ignore', hideClickBehaviors: true, layout_hideLinkAnchors: true },
- },
- ],
- [
- DocumentType.FONTICON,
- {
- layout: { view: FontIconBox, dataField: 'icon' },
- options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', layout_hideContextMenu: true, layout_hideLinkButton: true, _width: 40, _height: 40 },
- },
- ],
- [
- DocumentType.WEBCAM,
- {
- layout: { view: RecordingBox, dataField: defaultDataKey },
- options: { systemIcon: 'BsFillCameraVideoFill' },
- },
- ],
- [
- DocumentType.PRESELEMENT,
- {
- layout: { view: PresElementBox, dataField: defaultDataKey },
- options: { title: 'pres element template', _layout_fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: 'data' },
+ options: { acl: '', title: 'Global Script Database' },
},
],
+
[
DocumentType.CONFIG,
{
- layout: { view: CollectionView, dataField: defaultDataKey },
- options: { config: '', layout_hideLinkButton: true, layout_unrendered: true },
- },
- ],
- [
- DocumentType.INK,
- {
- // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method
- layout: { view: InkingStroke, dataField: 'stroke' },
- options: {
- systemIcon: 'BsFillPencilFill', //
- layout_nativeDimEditable: true,
- layout_reflowVertical: true,
- layout_reflowHorizontal: true,
- layout_hideDecorationTitle: true, // don't show title when selected
- fitWidth: false,
- layout_isSvg: true,
- },
- },
- ],
- [
- DocumentType.SCREENSHOT,
- {
- layout: { view: ScreenshotBox, dataField: defaultDataKey },
- options: { layout_nativeDimEditable: true, systemIcon: 'BsCameraFill' },
- },
- ],
- [
- DocumentType.COMPARISON,
- {
- data: '',
- layout: { view: ComparisonBox, dataField: defaultDataKey },
- options: { backgroundColor: 'gray', dropAction: dropActionType.move, waitForDoubleClickToClick: 'always', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsLayoutSplit' },
- },
- ],
- [
- DocumentType.GROUPDB,
- {
layout: { view: EmptyBox, dataField: defaultDataKey },
- options: { title: 'Global Group Database' },
- },
- ],
- [
- DocumentType.GROUP,
- {
- layout: { view: EmptyBox, dataField: defaultDataKey },
- options: {},
- },
- ],
- [
- DocumentType.DATAVIZ,
- {
- layout: { view: DataVizBox, dataField: defaultDataKey },
- options: { dataViz_title: '', dataViz_line: '', dataViz_pie: '', dataViz_histogram: '', dataViz: 'table', _layout_fitWidth: true, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true },
- },
- ],
- [
- DocumentType.LOADING,
- {
- layout: { view: LoadingBox, dataField: '' },
- options: { _layout_fitWidth: true, _fitHeight: true, layout_nativeDimEditable: true },
- },
- ],
- [
- DocumentType.SIMULATION,
- {
- data: '',
- layout: { view: PhysicsSimulationBox, dataField: defaultDataKey, _width: 1000, _height: 800 },
- options: {
- _height: 100,
- mass1: '',
- mass2: '',
- layout_nativeDimEditable: true,
- position: '',
- acceleration: '',
- pendulum: '',
- spring: '',
- wedge: '',
- simulation: '',
- review: '',
- systemIcon: 'BsShareFill',
- },
- },
- ],
- [
- DocumentType.PUSHPIN,
- {
- layout: { view: MapPushpinBox, dataField: defaultDataKey },
- options: {},
+ options: { acl: '', config: '', layout_hideLinkButton: true, layout_unrendered: true },
},
],
[
DocumentType.MAPROUTE,
{
- layout: { view: CollectionView, dataField: defaultDataKey },
- options: {},
- },
- ],
- [
- DocumentType.CALENDAR,
- {
- layout: { view: CalendarBox, dataField: defaultDataKey },
- options: {},
+ layout: { view: EmptyBox, dataField: defaultDataKey },
+ options: { acl: '' },
},
],
]);
@@ -850,7 +539,7 @@ export namespace Docs {
// fetch the actual prototype documents from the server
const actualProtos = await DocServer.GetRefFields(prototypeIds);
// update this object to include any default values: DocumentOptions for all prototypes
- prototypeIds.map(id => {
+ prototypeIds.forEach(id => {
const existing = actualProtos[id] as Doc;
const type = id.replace(suffix, '') as DocumentType;
// get or create prototype of the specified type...
@@ -908,7 +597,7 @@ export namespace Docs {
if (!template) {
return undefined;
}
- const layout = template.layout;
+ const { layout } = template;
// create title
const upper = suffix.toUpperCase();
const title = prototypeId.toUpperCase().replace(upper, `_${upper}`);
@@ -921,14 +610,14 @@ export namespace Docs {
type,
isBaseProto: true,
_width: 300,
- 'acl-Guest': SharingPermissions.View,
+ acl_Guest: SharingPermissions.View,
...(template.options || {}),
layout: layout.view?.LayoutString(layout.dataField),
data: template.data,
};
Object.entries(options)
.filter(pair => typeof pair[1] === 'string' && pair[1].startsWith('@'))
- .map(pair => {
+ .forEach(pair => {
if (!existing || ScriptCast(existing[pair[0]])?.script.originalScript !== pair[1].substring(1)) {
(options as any)[pair[0]] = ComputedField.MakeFunction(pair[1].substring(1));
}
@@ -960,15 +649,17 @@ export namespace Docs {
* only when creating a DockDocument from the current user's already existing
* main document.
*/
- function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string, placeholderDoc?: Doc, noView?: boolean) {
+ // eslint-disable-next-line default-param-last
+ function InstanceFromProto(proto: Doc, data: FieldType | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string, placeholderDocIn?: Doc, noView?: boolean) {
+ const placeholderDoc = placeholderDocIn;
const viewKeys = ['x', 'y', 'isSystem']; // keys that should be addded to the view document even though they don't begin with an "_"
const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, '^_');
- // dataProps['acl-Override'] = SharingPermissions.Unset;
- dataProps['acl-Guest'] = options['acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View);
+ // dataProps.acl_Override = SharingPermissions.Unset;
+ dataProps.acl_Guest = options.acl_Guest ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View);
dataProps.isSystem = viewProps.isSystem;
dataProps.isDataDoc = true;
- dataProps.author = Doc.CurrentUserEmail;
+ dataProps.author = ClientUtils.CurrentUserEmail();
dataProps.author_date = new DateField();
if (fieldKey) {
dataProps[`${fieldKey}_modificationDate`] = new DateField();
@@ -988,8 +679,8 @@ export namespace Docs {
}
if (!noView) {
- const viewFirstProps: { [id: string]: any } = { author: Doc.CurrentUserEmail };
- viewFirstProps['acl-Guest'] = options['_acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View);
+ const viewFirstProps: { [id: string]: any } = { author: ClientUtils.CurrentUserEmail() };
+ viewFirstProps.acl_Guest = options._acl_Guest ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View);
let viewDoc: Doc;
// determines whether viewDoc should be created using placeholder Doc or default
if (placeholderDoc) {
@@ -997,14 +688,16 @@ export namespace Docs {
placeholderDoc._width = options._width !== undefined ? Number(options._width) : undefined;
viewDoc = Doc.assign(placeholderDoc, viewFirstProps, true, true);
Array.from(Object.keys(placeholderDoc))
- .filter(key => key.startsWith('acl'))
- .forEach(key => (dataDoc[key] = viewDoc[key] = placeholderDoc[key]));
+ .filter(key => key.startsWith('acl_'))
+ .forEach(key => {
+ dataDoc[key] = viewDoc[key] = placeholderDoc[key];
+ });
} else {
viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true);
}
Doc.assign(viewDoc, viewProps, true, true);
if (![DocumentType.LINK, DocumentType.CONFIG, DocumentType.LABEL].includes(viewDoc.type as any)) {
- DocUtils.MakeLinkToActiveAudio(() => viewDoc);
+ CreateLinkToActiveAudio(() => viewDoc);
}
updateCachedAcls(dataDoc);
updateCachedAcls(viewDoc);
@@ -1017,6 +710,7 @@ export namespace Docs {
return dataDoc;
}
+ // eslint-disable-next-line default-param-last
export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) {
const imgField = url instanceof ImageField ? url : url ? new ImageField(url) : undefined;
return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField?.url.href ?? '-no image-'), ...options }, undefined, undefined, undefined, overwriteDoc);
@@ -1035,18 +729,16 @@ export namespace Docs {
* @param fieldKey the field that the compiled script is written into.
* @returns the Scripting Doc
*/
+ // eslint-disable-next-line default-param-last
export function ScriptingDocument(script: Opt<ScriptField> | null, options: DocumentOptions = {}, fieldKey?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script ? script : undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined });
+ return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script || undefined, { ...options, layout: fieldKey ? `<ScriptingBox {...props} fieldKey={'${fieldKey}'}/>` /* ScriptingBox.LayoutString(fieldKey) */ : undefined });
}
+ // eslint-disable-next-line default-param-last
export function VideoDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) {
return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(url), options, undefined, undefined, undefined, overwriteDoc);
}
- export function YoutubeDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) {
- return InstanceFromProto(Prototypes.get(DocumentType.YOUTUBE), new YoutubeField(url), options, undefined, undefined, undefined, overwriteDoc);
- }
-
export function WebCamDocument(url: string, options: DocumentOptions = {}) {
return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), '', options);
}
@@ -1059,6 +751,7 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), undefined, options);
}
+ // eslint-disable-next-line default-param-last
export function AudioDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) {
return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(url), options, undefined, undefined, undefined, overwriteDoc);
}
@@ -1072,7 +765,7 @@ export namespace Docs {
}
export function LoadingDocument(file: File | string, options: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.LOADING), undefined, { _height: 150, _width: 200, title: typeof file == 'string' ? file : file.name, ...options }, undefined, '');
+ return InstanceFromProto(Prototypes.get(DocumentType.LOADING), undefined, { _height: 150, _width: 200, title: typeof file === 'string' ? file : file.name, ...options }, undefined, '');
}
export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') {
@@ -1101,6 +794,7 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey);
}
+ // eslint-disable-next-line default-param-last
export function LinkDocument(source: Doc, target: Doc, options: DocumentOptions = {}, id?: string) {
const linkDoc = InstanceFromProto(
Prototypes.get(DocumentType.LINK),
@@ -1114,7 +808,7 @@ export namespace Docs {
'link'
);
- LinkManager.Instance.addLink(linkDoc);
+ Doc.AddLink(linkDoc);
return linkDoc;
}
@@ -1124,7 +818,7 @@ export namespace Docs {
options: DocumentOptions = {},
strokeWidth = ActiveInkWidth(),
color = ActiveInkColor(),
- stroke_bezier = ActiveInkBezierApprox(),
+ strokeBezier = ActiveInkBezierApprox(),
fillColor = ActiveFillColor(),
arrowStart = ActiveArrowStart(),
arrowEnd = ActiveArrowEnd(),
@@ -1138,7 +832,7 @@ export namespace Docs {
I.fillColor = fillColor;
I.stroke = new InkField(points);
I.stroke_width = strokeWidth;
- I.stroke_bezier = stroke_bezier;
+ I.stroke_bezier = strokeBezier;
I.stroke_startMarker = arrowStart;
I.stroke_endMarker = arrowEnd;
I.stroke_dash = dash;
@@ -1147,13 +841,14 @@ export namespace Docs {
I.rotation = 0;
I.defaultDoubleClick = 'ignore';
I.author_date = new DateField();
- I['acl-Guest'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View;
- //I['acl-Override'] = SharingPermissions.Unset;
+ I.acl_Guest = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View;
+ // I.acl_Override = SharingPermissions.Unset;
I[Initializing] = false;
return ink;
}
+ // eslint-disable-next-line default-param-last
export function PdfDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) {
const width = options._width || undefined;
const height = options._height || undefined;
@@ -1169,7 +864,7 @@ export namespace Docs {
const nwid = options._nativeWidth || undefined;
const nhght = options._nativeHeight || undefined;
if (!nhght && width && height && nwid) options._nativeHeight = (Number(nwid) * Number(height)) / Number(width);
- return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url ? url : 'https://www.wikipedia.org/'), options);
+ return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url || 'https://www.wikipedia.org/'), options);
}
export function HtmlDocument(html: string, options: DocumentOptions = {}) {
@@ -1306,34 +1001,6 @@ export namespace Docs {
return ret;
}
- export type DocConfig = {
- doc: Doc;
- initialWidth?: number;
- path?: Doc[];
- };
-
- export function StandardCollectionDockingDocument(configs: Array<DocConfig>, options: DocumentOptions, id?: string, type: string = 'row') {
- const layoutConfig = {
- content: [
- {
- type: type,
- content: [...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, undefined, config.initialWidth))],
- },
- ],
- };
- const doc = DockDocument(
- configs.map(c => c.doc),
- JSON.stringify(layoutConfig),
- Doc.CurrentUserEmail === 'guest' ? options : { 'acl-Guest': SharingPermissions.View, ...options },
- id
- );
- configs.map(c => {
- Doc.SetContainer(c.doc, doc);
- inheritParentAcls(doc, c.doc, false);
- });
- return doc;
- }
-
export function DelegateDocument(proto: Doc, options: DocumentOptions = {}) {
return InstanceFromProto(proto, undefined, options);
}
@@ -1343,782 +1010,3 @@ export namespace Docs {
}
}
}
-
-export namespace DocUtils {
- function matchFieldValue(doc: Doc, key: string, value: any): boolean {
- const hasFunctionFilter = Utils.HasFunctionFilter(value);
- if (hasFunctionFilter) {
- return hasFunctionFilter(StrCast(doc[key]));
- }
- if (key === LinkedTo) {
- // links are not a field value, so handled here. value is an expression of form ([field=]idToDoc("..."))
- const allLinks = LinkManager.Instance.getAllRelatedLinks(doc);
- const matchLink = (value: string, anchor: Doc) => {
- const linkedToExp = (value ?? '').split('=');
- if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value;
- return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1];
- };
- // prettier-ignore
- return (value === Doc.FilterNone && !allLinks.length) ||
- (value === Doc.FilterAny && !!allLinks.length) ||
- (allLinks.some(link => matchLink(value,DocCast(link.link_anchor_1)) ||
- matchLink(value,DocCast(link.link_anchor_2)) ));
- }
- if (typeof value === 'string') {
- value = value.replace(`,${Utils.noRecursionHack}`, '');
- }
- const fieldVal = doc[key];
- // prettier-ignore
- if ((value === Doc.FilterAny && fieldVal !== undefined) ||
- (value === Doc.FilterNone && fieldVal === undefined)) {
- return true;
- }
- const vals = StrListCast(fieldVal); // list typing is very imperfect. casting to a string list doesn't mean that the entries will actually be strings
- if (vals.length) {
- return vals.some(v => typeof v === 'string' && v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
- }
- return Field.toString(fieldVal as Field).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
- }
- /**
- * @param docs
- * @param childFilters
- * @param childFiltersByRanges
- * @param parentCollection
- * Given a list of docs and childFilters, @returns the list of Docs that match those filters
- */
- export function FilterDocs(childDocs: Doc[], childFilters: string[], childFiltersByRanges: string[], parentCollection?: Doc) {
- if (!childFilters?.length && !childFiltersByRanges?.length) {
- return childDocs.filter(d => !d.cookies); // remove documents that need a cookie if there are no filters to provide one
- }
-
- const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
- childFilters.forEach(filter => {
- const fields = filter.split(Doc.FilterSep);
- const key = fields[0];
- const value = fields[1];
- const modifiers = fields[2];
- if (!filterFacets[key]) {
- filterFacets[key] = {};
- }
- filterFacets[key][value] = modifiers;
- });
-
- const filteredDocs = childFilters.length
- ? childDocs.filter(d => {
- if (d.z) return true;
- // if the document needs a cookie but no filter provides the cookie, then the document does not pass the filter
- if (d.cookies && (!filterFacets.cookies || !Object.keys(filterFacets.cookies).some(key => d.cookies === key))) {
- return false;
- }
- for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragDocsFilter.split(Doc.FilterSep)[0])) {
- const facet = filterFacets[facetKey];
-
- // facets that match some value in the field of the document (e.g. some text field)
- const matches = Object.keys(facet).filter(value => value !== 'cookies' && facet[value] === 'match');
-
- // facets that have a check next to them
- const checks = Object.keys(facet).filter(value => facet[value] === 'check');
-
- // metadata facets that exist
- const exists = Object.keys(facet).filter(value => facet[value] === 'exists');
-
- // facets that unset metadata (a hack for making cookies work)
- const unsets = Object.keys(facet).filter(value => facet[value] === 'unset');
-
- // facets that specify that a field must not match a specific value
- const xs = Object.keys(facet).filter(value => facet[value] === 'x');
-
- if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true;
- const failsNotEqualFacets = !xs.length ? false : xs.some(value => matchFieldValue(d, facetKey, value));
- const satisfiesCheckFacets = !checks.length ? true : checks.some(value => matchFieldValue(d, facetKey, value));
- const satisfiesExistsFacets = !exists.length ? true : exists.some(value => (facetKey !== LinkedTo ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length));
- const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined);
- const satisfiesMatchFacets = !matches.length
- ? true
- : matches.some(value => {
- if (facetKey.startsWith('*')) {
- // fields starting with a '*' are used to match families of related fields. ie, *modificationDate will match text_modificationDate, data_modificationDate, etc
- const allKeys = Array.from(Object.keys(d));
- allKeys.push(...Object.keys(Doc.GetProto(d)));
- const keys = allKeys.filter(key => key.includes(facetKey.substring(1)));
- return keys.some(key => Field.toString(d[key] as Field).includes(value));
- }
- return Field.toString(d[facetKey] as Field).includes(value);
- });
- // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria
- if (parentCollection?.childFilters_boolean === 'OR') {
- if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true;
- }
- // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria
- else {
- if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
- }
- }
- return parentCollection?.childFilters_boolean === 'OR' ? false : true;
- })
- : childDocs;
- const rangeFilteredDocs = filteredDocs.filter(d => {
- for (let i = 0; i < childFiltersByRanges.length; i += 3) {
- const key = childFiltersByRanges[i];
- const min = Number(childFiltersByRanges[i + 1]);
- const max = Number(childFiltersByRanges[i + 2]);
- const val = typeof d[key] === 'string' ? (Number(StrCast(d[key])).toString() === StrCast(d[key]) ? Number(StrCast(d[key])) : undefined) : Cast(d[key], 'number', null);
- if (val === undefined) {
- //console.log("Should 'undefined' pass range filter or not?")
- } else if (val < min || val > max) return false;
- }
- return true;
- });
- return rangeFilteredDocs;
- }
-
- export let ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = [];
-
- export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) {
- broadcastEvent && runInAction(() => (Doc.RecordingEvent = Doc.RecordingEvent + 1));
- return DocUtils.ActiveRecordings.map(audio => {
- const sourceDoc = getSourceDoc();
- return sourceDoc && DocUtils.MakeLink(sourceDoc, audio.getAnchor(true) || audio.props.Document, { link_displayLine: false, link_relationship: 'recording annotation:linked recording', link_description: 'recording timeline' });
- });
- }
-
- export function MakeLink(source: Doc, target: Doc, linkSettings: { link_relationship?: string; link_description?: string; link_displayLine?: boolean }, id?: string, showPopup?: number[]) {
- if (!linkSettings.link_relationship) linkSettings.link_relationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link';
- if (target.doc === Doc.UserDoc()) return undefined;
-
- const makeLink = action((linkDoc: Doc, showPopup?: number[]) => {
- if (showPopup) {
- LinkManager.Instance.currentLink = linkDoc;
-
- TaskCompletionBox.textDisplayed = 'Link Created';
- TaskCompletionBox.popupX = showPopup[0];
- TaskCompletionBox.popupY = showPopup[1] - 33;
- TaskCompletionBox.taskCompleted = true;
-
- LinkDescriptionPopup.Instance.popupX = showPopup[0];
- LinkDescriptionPopup.Instance.popupY = showPopup[1];
- LinkDescriptionPopup.Instance.display = true;
-
- const rect = document.body.getBoundingClientRect();
- if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) {
- LinkDescriptionPopup.Instance.popupX -= 190;
- TaskCompletionBox.popupX -= 40;
- }
- if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) {
- LinkDescriptionPopup.Instance.popupY -= 40;
- TaskCompletionBox.popupY -= 40;
- }
-
- setTimeout(
- action(() => (TaskCompletionBox.taskCompleted = false)),
- 2500
- );
- }
- return linkDoc;
- });
-
- const a = source.layout_unrendered ? 'link_anchor_1?.annotationOn' : 'link_anchor_1';
- const b = target.layout_unrendered ? 'link_anchor_2?.annotationOn' : 'link_anchor_2';
-
- return makeLink(
- Docs.Create.LinkDocument(
- source,
- target,
- {
- 'acl-Guest': SharingPermissions.Augment,
- '_acl-Guest': SharingPermissions.Augment,
- 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,
- link_relationship: linkSettings.link_relationship,
- link_description: linkSettings.link_description,
- x: ComputedField.MakeFunction(`((this.${a}?.x||0)+(this.${b}?.x||0))/2`) as any,
- y: ComputedField.MakeFunction(`((this.${a}?.y||0)+(this.${b}?.y||0))/2`) as any,
- link_autoMoveAnchors: true,
- _lockedPosition: true,
- _layout_showCaption: '', // removed since they conflict with showing a link with a LinkBox (ie, line, not comparison box)
- _layout_showTitle: '',
- // _layout_showCaption: 'link_description',
- // _layout_showTitle: 'link_relationship',
- },
- id
- ),
- showPopup
- );
- }
-
- export function AssignScripts(doc: Doc, scripts?: { [key: string]: string | undefined }, funcs?: { [key: string]: string }) {
- scripts &&
- Object.keys(scripts).map(key => {
- const script = scripts[key];
- if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) {
- (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = ScriptField.MakeScript(script, {
- self: Doc.name,
- this: Doc.name,
- dragData: DragManager.DocumentDragData.name,
- value: 'any',
- _readOnly_: 'boolean',
- scriptContext: 'any',
- documentView: Doc.name,
- heading: Doc.name,
- checked: 'boolean',
- containingTreeView: Doc.name,
- altKey: 'boolean',
- ctrlKey: 'boolean',
- shiftKey: 'boolean',
- });
- }
- });
- funcs &&
- Object.keys(funcs)
- .filter(key => !key.endsWith('-setter'))
- .map(key => {
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- if (ScriptCast(cfield)?.script.originalScript !== funcs[key]) {
- const setFunc = Cast(funcs[key + '-setter'], 'string', null);
- (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = funcs[key] ? ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, { _readOnly_: true }, setFunc) : undefined;
- }
- });
- return doc;
- }
- export function AssignOpts(doc: Doc | undefined, reqdOpts: DocumentOptions, items?: Doc[]) {
- if (doc) {
- const compareValues = (val1: any, val2: any) => {
- if (val1 instanceof List && val2 instanceof List && val1.length === val2.length) {
- return !val1.some(v => !val2.includes(v)) || !val2.some(v => val1.includes(v));
- }
- return val1 === val2;
- };
- Object.entries(reqdOpts).forEach(pair => {
- const targetDoc = pair[0].startsWith('_') ? doc : Doc.GetProto(doc as Doc);
- if (!Object.getOwnPropertyNames(targetDoc).includes(pair[0].replace(/^_/, '')) || !compareValues(pair[1], targetDoc[pair[0]])) {
- targetDoc[pair[0]] = pair[1];
- }
- });
- items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), 'data', item));
- items && DocListCast(doc.data).forEach(item => Doc.IsSystem(item) && !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), 'data', item));
- }
- return doc;
- }
- export function AssignDocField(doc: Doc, field: string, creator: (reqdOpts: DocumentOptions, items?: Doc[]) => Doc, reqdOpts: DocumentOptions, items?: Doc[], scripts?: { [key: string]: string }, funcs?: { [key: string]: string }) {
- return DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? (doc[field] = creator(reqdOpts, items)), scripts, funcs);
- }
-
- export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined {
- let created: Doc | undefined;
- const field = target[fieldKey];
- const resolved = options ?? {};
- if (field instanceof ImageField) {
- created = Docs.Create.ImageDocument(field.url.href, resolved);
- created.layout = ImageBox.LayoutString(fieldKey);
- } else if (field instanceof Doc) {
- created = field;
- } else if (field instanceof VideoField) {
- created = Docs.Create.VideoDocument(field.url.href, resolved);
- created.layout = VideoBox.LayoutString(fieldKey);
- } else if (field instanceof PdfField) {
- created = Docs.Create.PdfDocument(field.url.href, resolved);
- created.layout = PDFBox.LayoutString(fieldKey);
- } else if (field instanceof AudioField) {
- created = Docs.Create.AudioDocument(field.url.href, resolved);
- created.layout = AudioBox.LayoutString(fieldKey);
- } else if (field instanceof InkField) {
- created = Docs.Create.InkDocument(field.inkData, resolved);
- created.layout = InkingStroke.LayoutString(fieldKey);
- } else if (field instanceof List && field[0] instanceof Doc) {
- created = Docs.Create.StackingDocument(DocListCast(field), resolved);
- created.layout = CollectionView.LayoutString(fieldKey);
- } else {
- created = Docs.Create.TextDocument('', { ...{ _width: 200, _height: 25, _layout_autoHeight: true }, ...resolved });
- created.layout = FormattedTextBox.LayoutString(fieldKey);
- }
- if (created) {
- created.title = fieldKey;
- proto && created.proto && (created.proto = Doc.GetProto(proto));
- }
- return created;
- }
-
- /**
- *
- * @param type the type of file.
- * @param path the path to the file.
- * @param options the document options.
- * @param overwriteDoc the placeholder loading doc.
- * @returns
- */
- export async function DocumentFromType(type: string, path: string, options: DocumentOptions, overwriteDoc?: Doc): Promise<Opt<Doc>> {
- let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise<Doc | undefined>) | undefined = undefined;
- if (type.indexOf('image') !== -1) {
- ctor = Docs.Create.ImageDocument;
- if (!options._width) options._width = 300;
- }
- if (type.indexOf('video') !== -1) {
- ctor = Docs.Create.VideoDocument;
- if (!options._width) options._width = 600;
- if (!options._height) options._height = ((options._width as number) * 2) / 3;
- }
- if (type.indexOf('audio') !== -1) {
- ctor = Docs.Create.AudioDocument;
- }
- if (type.indexOf('pdf') !== -1) {
- ctor = Docs.Create.PdfDocument;
- if (!options._width) options._width = 400;
- if (!options._height) options._height = ((options._width as number) * 1200) / 927;
- }
- if (type.indexOf('csv') !== -1) {
- ctor = Docs.Create.DataVizDocument;
- if (!options._width) options._width = 400;
- if (!options._height) options._height = ((options._width as number) * 1200) / 927;
- }
- //TODO:al+glr
- // if (type.indexOf("map") !== -1) {
- // ctor = Docs.Create.MapDocument;
- // if (!options._width) options._width = 800;
- // if (!options._height) options._height = (options._width as number) * 3 / 4;
- // }
- if (type.indexOf('html') !== -1) {
- if (path.includes(window.location.hostname)) {
- const s = path.split('/');
- const id = s[s.length - 1];
- return DocServer.GetRefField(id).then(field => {
- if (field instanceof Doc) {
- const embedding = Doc.MakeEmbedding(field);
- embedding.x = (options.x as number) || 0;
- embedding.y = (options.y as number) || 0;
- embedding._width = (options._width as number) || 300;
- embedding._height = (options._height as number) || (options._width as number) || 300;
- return embedding;
- }
- return undefined;
- });
- }
- ctor = Docs.Create.WebDocument;
- options = { ...options, _width: 400, _height: 512, title: path };
- }
-
- 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 {
- const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data)
- .filter(btnDoc => !btnDoc.hidden)
- .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
- .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc.title)
- .map((dragDoc, i) => ({
- description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
- event: undoable((args: { x: number; y: number }) => {
- const newDoc = DocUtils.copyDragFactory(dragDoc);
- if (newDoc) {
- newDoc.author = Doc.CurrentUserEmail;
- newDoc.x = x;
- newDoc.y = y;
- EquationBox.SelectOnLoad = newDoc[Id];
- if (newDoc.type === DocumentType.RTF) FormattedTextBox.SetSelectOnLoad(newDoc);
- if (pivotField) {
- newDoc[pivotField] = pivotValue;
- }
- docAdder?.(newDoc);
- }
- }, StrCast(dragDoc.title)),
- icon: Doc.toIcon(dragDoc),
- })) as ContextMenuProps[];
- ContextMenu.Instance.addItem({
- description: 'Create document',
- subitems: documentList,
- icon: 'file',
- });
- !simpleMenu &&
- ContextMenu.Instance.addItem({
- description: 'Styled Notes',
- subitems: DocListCast((Doc.UserDoc().template_notes as Doc).data).map((note, i) => ({
- description: ':' + StrCast(note.title),
- event: undoable((args: { x: number; y: number }) => {
- const textDoc = Docs.Create.TextDocument('', {
- _width: 200,
- x,
- y,
- _layout_autoHeight: note._layout_autoHeight !== false,
- title: StrCast(note.title) + '#' + (note.embeddingCount = NumCast(note.embeddingCount) + 1),
- });
- textDoc.layout_fieldKey = 'layout_' + note.title;
- textDoc[textDoc.layout_fieldKey] = note;
- if (pivotField) {
- textDoc[pivotField] = pivotValue;
- }
- docTextAdder(textDoc);
- }, 'create quick note'),
- icon: StrCast(note.icon) as IconProp,
- })) as ContextMenuProps[],
- icon: 'sticky-note',
- });
- const userDocList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[1]?.data)
- .filter(btnDoc => !btnDoc.hidden)
- .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
- .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyNote && doc.title)
- .map((dragDoc, i) => ({
- description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
- event: undoable((args: { x: number; y: number }) => {
- const newDoc = DocUtils.delegateDragFactory(dragDoc);
- if (newDoc) {
- newDoc.author = Doc.CurrentUserEmail;
- newDoc.x = x;
- newDoc.y = y;
- EquationBox.SelectOnLoad = newDoc[Id];
- if (newDoc.type === DocumentType.RTF) FormattedTextBox.SetSelectOnLoad(newDoc);
- if (pivotField) {
- newDoc[pivotField] = pivotValue;
- }
- docAdder?.(newDoc);
- }
- }, StrCast(dragDoc.title)),
- icon: Doc.toIcon(dragDoc),
- })) as ContextMenuProps[];
- ContextMenu.Instance.addItem({
- description: 'User Templates',
- subitems: userDocList,
- icon: 'file',
- });
- }
-
- // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView)
-
- /**
- * Applies a template to a Doc and logs the action with the UndoManager
- * If the template already exists and has been registered, it can be specified by it's signature name (e.g., 'icon' not 'layout_icon').
- * Alternatively, the signature can be omitted and the template can be provided.
- * @param doc the Doc to apply the template to.
- * @param creator a function that will create the template if it doesn't exist
- * @param templateSignature the signature name for a template that has already been created and registered on the userDoc. (can be "" if template is provide)
- * @param template the template to use (optional if templateSignature is provided)
- * @returns doc
- */
- export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', template?: Doc) {
- const batch = UndoManager.StartBatch('makeCustomViewClicked');
- createCustomView(doc, creator, templateSignature || StrCast(template?.title), template);
- batch.end();
- return doc;
- }
- export function findTemplate(templateName: string, type: string, signature: string) {
- let docLayoutTemplate: Opt<Doc>;
- const iconViews = DocListCast(Cast(Doc.UserDoc()['template_icons'], Doc, null)?.data);
- const templBtns = DocListCast(Cast(Doc.UserDoc()['template_buttons'], Doc, null)?.data);
- const noteTypes = DocListCast(Cast(Doc.UserDoc()['template_notes'], Doc, null)?.data);
- const userTypes = DocListCast(Cast(Doc.UserDoc()['template_user'], Doc, null)?.data);
- const clickFuncs = DocListCast(Cast(Doc.UserDoc()['template_clickFuncs'], Doc, null)?.data);
- const allTemplates = iconViews
- .concat(templBtns)
- .concat(noteTypes)
- .concat(userTypes)
- .concat(clickFuncs)
- .map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc)
- .filter(doc => doc.isTemplateDoc);
- // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized
- // first try to find a template that matches the specific document type (<typeName>_<templateName>). otherwise, fallback to a general match on <templateName>
- !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName + '_' + type && (docLayoutTemplate = tempDoc));
- !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc));
- return docLayoutTemplate;
- }
- export function createCustomView(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) {
- const templateName = templateSignature.replace(/\(.*\)/, '');
- doc.layout_fieldKey = 'layout_' + (templateSignature || docLayoutTemplate?.title);
- docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type), templateSignature);
-
- const customName = 'layout_' + templateSignature;
- const _width = NumCast(doc._width);
- const _height = NumCast(doc._height);
- const options = { title: 'data', backgroundColor: StrCast(doc.backgroundColor), _layout_autoHeight: true, _width, x: -_width / 2, y: -_height / 2, _layout_showSidebar: false };
-
- if (docLayoutTemplate) {
- if (docLayoutTemplate !== doc[customName]) {
- Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined);
- }
- } else {
- let fieldTemplate: Opt<Doc>;
- if (doc.data instanceof RichTextField || typeof doc.data === 'string') {
- fieldTemplate = Docs.Create.TextDocument('', options);
- } else if (doc.data instanceof PdfField) {
- fieldTemplate = Docs.Create.PdfDocument('http://www.msn.com', options);
- } else if (doc.data instanceof VideoField) {
- fieldTemplate = Docs.Create.VideoDocument('http://www.cs.brown.edu', options);
- } else if (doc.data instanceof AudioField) {
- fieldTemplate = Docs.Create.AudioDocument('http://www.cs.brown.edu', options);
- } else if (doc.data instanceof ImageField) {
- fieldTemplate = Docs.Create.ImageDocument('http://www.cs.brown.edu', options);
- }
- const docTemplate = creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + '(' + doc.title + ')', isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
- fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
- docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
- }
- }
- export function makeCustomView(doc: Doc, custom: boolean, layout: string) {
- Doc.setNativeView(doc);
- if (custom) {
- makeCustomViewClicked(doc, Docs.Create.StackingDocument, layout, undefined);
- }
- }
- export function iconify(doc: Doc) {
- const layout_fieldKey = Cast(doc.layout_fieldKey, 'string', null);
- DocUtils.makeCustomViewClicked(doc, Docs.Create.StackingDocument, 'icon', undefined);
- if (layout_fieldKey && layout_fieldKey !== 'layout' && layout_fieldKey !== 'layout_icon') doc.deiconifyLayout = layout_fieldKey.replace('layout_', '');
- }
-
- export function pileup(docList: Doc[], x?: number, y?: number, size: number = 55, create: boolean = true) {
- runInAction(() => {
- docList.forEach((d, i) => {
- DocUtils.iconify(d);
- d.x = Math.cos((Math.PI * 2 * i) / docList.length) * size - size;
- d.y = Math.sin((Math.PI * 2 * i) / docList.length) * size - size;
- d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
- });
- });
- if (create) {
- const newCollection = Docs.Create.PileDocument(docList, { title: 'pileup', _freeform_noZoom: true, x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, dragWhenActive: true, _layout_fitWidth: false });
- newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - size;
- newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - size;
- newCollection._width = newCollection._height = size * 2;
- return newCollection;
- }
- }
- export function makeIntoPortal(doc: Doc, layoutDoc: Doc, allLinks: Doc[]) {
- const portalLink = allLinks.find(d => d.link_anchor_1 === doc && d.link_relationship === 'portal to:portal from');
- if (!portalLink) {
- DocUtils.MakeLink(
- doc,
- Docs.Create.FreeformDocument([], {
- _width: NumCast(layoutDoc._width) + 10,
- _height: Math.max(NumCast(layoutDoc._height), NumCast(layoutDoc._width) + 10),
- _isLightbox: true,
- _layout_fitWidth: true,
- title: StrCast(doc.title) + ' [Portal]',
- }),
- { link_relationship: 'portal to:portal from' }
- );
- }
- doc.followLinkLocation = OpenWhere.lightbox;
- doc.onClick = FollowLinkScript();
- }
-
- export function LeavePushpin(doc: Doc, annotationField: string) {
- if (doc.followLinkToggle) return undefined;
- const context = Cast(doc.embedContainer, Doc, null) ?? Cast(doc.annotationOn, Doc, null);
- const hasContextAnchor = LinkManager.Links(doc).some(l => (l.link_anchor_2 === doc && Cast(l.link_anchor_1, Doc, null)?.annotationOn === context) || (l.link_anchor_1 === doc && Cast(l.link_anchor_2, Doc, null)?.annotationOn === context));
- if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
- const pushpin = Docs.Create.FontIconDocument({
- title: '',
- annotationOn: Cast(doc.annotationOn, Doc, null),
- followLinkToggle: true,
- icon: 'map-pin',
- x: Cast(doc.x, 'number', null),
- y: Cast(doc.y, 'number', null),
- backgroundColor: '#ACCEF7',
- layout_hideAllLinks: true,
- _width: 15,
- _height: 15,
- _xPadding: 0,
- onClick: FollowLinkScript(),
- _timecodeToShow: Cast(doc._timecodeToShow, 'number', null),
- });
- Doc.AddDocToList(context, annotationField, pushpin);
- const pushpinLink = DocUtils.MakeLink(pushpin, doc, { link_relationship: 'pushpin' }, '');
- doc._timecodeToShow = undefined;
- return pushpin;
- }
- return undefined;
- }
-
- // /**
- // *
- // * @param dms Degree Minute Second format exif gps data
- // * @param ref ref that determines negativity of decimal coordinates
- // * @returns a decimal format of gps latitude / longitude
- // */
- // function getDecimalfromDMS(dms?: number[], ref?: string) {
- // if (dms && ref) {
- // let degrees = dms[0] / dms[1];
- // let minutes = dms[2] / dms[3] / 60.0;
- // let seconds = dms[4] / dms[5] / 3600.0;
-
- // if (['S', 'W'].includes(ref)) {
- // degrees = -degrees; minutes = -minutes; seconds = -seconds
- // }
- // return (degrees + minutes + seconds).toFixed(5);
- // }
- // }
-
- function ConvertDMSToDD(degrees: number, minutes: number, seconds: number, direction: string) {
- var dd = degrees + minutes / 60 + seconds / (60 * 60);
- if (direction === 'S' || direction === 'W') {
- dd = dd * -1;
- } // Don't do anything for N or E
- return dd;
- }
-
- export function assignImageInfo(result: Upload.FileInformation, proto: Doc) {
- if (Upload.isImageInformation(result)) {
- const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
- const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase();
- proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined);
- proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
- proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
- if (NumCast(proto['data-nativeOrientation']) >= 5) {
- proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
- proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
- }
- proto.data_exif = JSON.stringify(result.exifData?.data);
- proto.data_contentSize = result.contentSize;
- // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates
- const latitude = result.exifData?.data?.GPSLatitude;
- const latitudeDirection = result.exifData?.data?.GPSLatitudeRef;
- const longitude = result.exifData?.data?.GPSLongitude;
- const longitudeDirection = result.exifData?.data?.GPSLongitudeRef;
- if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) {
- proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection);
- proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection);
- }
- }
- }
-
- async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, overwriteDoc?: Doc) {
- if (result instanceof Error) {
- alert(`Upload failed: ${result.message}`);
- return;
- }
- const full = { ...options, _width: 400, title: name };
- const pathname = result.accessPaths.agnostic.client;
- const doc = await DocUtils.DocumentFromType(type, pathname, full, overwriteDoc);
- if (doc) {
- const proto = Doc.GetProto(doc);
- proto.text = result.rawText;
- !(result instanceof Error) && DocUtils.assignImageInfo(result, proto);
- if (Upload.isVideoInformation(result)) {
- proto.data_duration = result.duration;
- }
- if (overwriteDoc) {
- LoadingBox.removeCurrentlyLoading(overwriteDoc);
- }
- generatedDocuments.push(doc);
- }
- }
-
- export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, annotationOn?: Doc, backgroundColor?: string) {
- const defaultTextTemplate = DocCast(Doc.UserDoc().defaultTextLayout);
- const tbox = Docs.Create.TextDocument('', {
- annotationOn,
- backgroundColor,
- x,
- y,
- title,
- ...(defaultTextTemplate
- ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance
- : {
- _width: width || 200,
- _height: 35,
- _layout_centered: BoolCast(Doc.UserDoc()._layout_centered),
- _layout_fitWidth: true,
- _layout_autoHeight: true,
- }),
- });
-
- if (defaultTextTemplate) {
- tbox.layout_fieldKey = 'layout_' + StrCast(defaultTextTemplate.title);
- Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = defaultTextTemplate; // set the text doc's layout to render with the text template
- tbox[DocData].proto = defaultTextTemplate; // and also set the text doc to inherit from the template (this allows the template to specify default field values)
- }
- return tbox;
- }
-
- export function uploadYoutubeVideoLoading(videoId: string, options: DocumentOptions, overwriteDoc?: Doc) {
- const generatedDocuments: Doc[] = [];
- Networking.UploadYoutubeToServer(videoId, overwriteDoc?.[Id]).then(upfiles => {
- const {
- source: { newFilename, originalFilename, mimetype },
- result,
- } = upfiles.lastElement();
- if ((result as any).message) {
- if (overwriteDoc) {
- overwriteDoc.isLoading = false;
- overwriteDoc.loadingError = (result as any).message;
- LoadingBox.removeCurrentlyLoading(overwriteDoc);
- }
- } else newFilename && processFileupload(generatedDocuments, newFilename, mimetype ?? '', result, options, overwriteDoc);
- });
- }
-
- /**
- * uploadFilesToDocs will take in an array of Files, and creates documents for the
- * new files.
- *
- * @param files an array of files that will be uploaded
- * @param options options to use while uploading
- * @returns
- */
- export async function uploadFilesToDocs(files: File[], options: DocumentOptions) {
- const generatedDocuments: Doc[] = [];
-
- // These files do not have overwriteDocs, so we do not set the guid and let the client generate one.
- const fileNoGuidPairs: Networking.FileGuidPair[] = files.map(file => ({ file }));
-
- const upfiles = await Networking.UploadFilesToServer(fileNoGuidPairs);
- for (const {
- source: { newFilename, mimetype },
- result,
- } of upfiles) {
- newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options);
- }
- return generatedDocuments;
- }
-
- export function uploadFileToDoc(file: File, options: DocumentOptions, overwriteDoc: Doc) {
- const generatedDocuments: Doc[] = [];
- // 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: { newFilename, mimetype },
- result,
- } = 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 newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options, overwriteDoc);
- });
- }
-
- // copies the specified drag factory document
- export function copyDragFactory(dragFactory: Doc) {
- if (!dragFactory) return undefined;
- const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true);
- if (ndoc && dragFactory['dragFactory_count'] !== undefined) {
- dragFactory['dragFactory_count'] = NumCast(dragFactory['dragFactory_count']) + 1;
- Doc.SetInPlace(ndoc, 'title', ndoc.title + ' ' + NumCast(dragFactory['dragFactory_count']).toString(), true);
- }
-
- return ndoc;
- }
- export function delegateDragFactory(dragFactory: Doc) {
- const ndoc = Doc.MakeDelegateWithProto(dragFactory);
- if (ndoc && dragFactory['dragFactory_count'] !== undefined) {
- dragFactory['dragFactory_count'] = NumCast(dragFactory['dragFactory_count']) + 1;
- Doc.GetProto(ndoc).title = ndoc.title + ' ' + NumCast(dragFactory['dragFactory_count']).toString();
- }
- return ndoc;
- }
-}
-
-ScriptingGlobals.add('Docs', Docs);
-ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc, asDelegate?: boolean) {
- return dragFactory instanceof Doc ? (asDelegate ? DocUtils.delegateDragFactory(dragFactory) : DocUtils.copyDragFactory(dragFactory)) : dragFactory;
-});
-ScriptingGlobals.add(function makeDelegate(proto: any) {
- const d = Docs.Create.DelegateDocument(proto, { title: 'child of ' + proto.title });
- return d;
-});
-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}`;
-});
diff --git a/src/client/goldenLayout.d.ts b/src/client/goldenLayout.d.ts
index b50240563..26e8ba356 100644
--- a/src/client/goldenLayout.d.ts
+++ b/src/client/goldenLayout.d.ts
@@ -1,3 +1,2 @@
-
declare const GoldenLayout: any;
-export = GoldenLayout; \ No newline at end of file
+export = GoldenLayout;
diff --git a/src/client/util/BranchingTrailManager.tsx b/src/client/util/BranchingTrailManager.tsx
index 02879e3c4..119d103c5 100644
--- a/src/client/util/BranchingTrailManager.tsx
+++ b/src/client/util/BranchingTrailManager.tsx
@@ -1,18 +1,31 @@
+/* eslint-disable react/no-unused-class-component-methods */
+/* eslint-disable react/no-array-index-key */
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
-import { PresBox } from '../views/nodes/trails';
import { OverlayView } from '../views/OverlayView';
-import { DocumentManager } from './DocumentManager';
-import { Docs } from '../documents/Documents';
-import { nullAudio } from '../../fields/URLField';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { PresBox } from '../views/nodes/trails';
@observer
export class BranchingTrailManager extends React.Component {
+ // eslint-disable-next-line no-use-before-define
public static Instance: BranchingTrailManager;
+ // stack of the history
+ @observable private slideHistoryStack: String[] = [];
+ @observable private containsSet: Set<String> = new Set<String>();
+ // docId to Doc map
+ @observable private docIdToDocMap: Map<String, Doc> = new Map<String, Doc>();
+
+ // prev pres to copmare with
+ @observable private prevPresId: String | null = null;
+ @action setPrevPres = action((newId: String | null) => {
+ this.prevPresId = newId;
+ });
+
constructor(props: any) {
super(props);
makeObservable(this);
@@ -22,7 +35,7 @@ export class BranchingTrailManager extends React.Component {
}
setupUi = () => {
- OverlayView.Instance.addWindow(<BranchingTrailManager></BranchingTrailManager>, { x: 100, y: 150, width: 1000, title: 'Branching Trail' });
+ OverlayView.Instance.addWindow(<BranchingTrailManager />, { x: 100, y: 150, width: 1000, title: 'Branching Trail' });
// OverlayView.Instance.forceUpdate();
console.log(OverlayView.Instance);
// let hi = Docs.Create.TextDocument("beee", {
@@ -33,26 +46,13 @@ export class BranchingTrailManager extends React.Component {
// hi.overlayY = 100;
// Doc.AddToMyOverlay(hi);
- console.log(DocumentManager._overlayViews);
};
- // stack of the history
- @observable private slideHistoryStack: String[] = [];
@action setSlideHistoryStack = action((newArr: String[]) => {
this.slideHistoryStack = newArr;
});
- @observable private containsSet: Set<String> = new Set<String>();
-
- // prev pres to copmare with
- @observable private prevPresId: String | null = null;
- @action setPrevPres = action((newId: String | null) => {
- this.prevPresId = newId;
- });
-
- // docId to Doc map
- @observable private docIdToDocMap: Map<String, Doc> = new Map<String, Doc>();
-
+ // eslint-disable-next-line react/sort-comp
observeDocumentChange = (targetDoc: Doc, pres: PresBox) => {
const presId = pres.Document[Id];
if (this.prevPresId === presId) {
@@ -102,11 +102,11 @@ export class BranchingTrailManager extends React.Component {
this.setSlideHistoryStack(newStack);
removed.forEach(info => this.containsSet.delete(info.toString()));
- DocumentManager.Instance.showDocument(targetDoc, { willZoomCentered: true });
+ DocumentView.showDocument(targetDoc, { willZoomCentered: true });
if (this.slideHistoryStack.length === 0) {
Doc.UserDoc().isBranchingMode = false;
}
- //PresBox.NavigateToTarget(targetDoc, targetDoc);
+ // PresBox.NavigateToTarget(targetDoc, targetDoc);
};
@computed get trailBreadcrumbs() {
@@ -116,11 +116,11 @@ export class BranchingTrailManager extends React.Component {
const [presId, targetDocId] = info.split(',');
const doc = this.docIdToDocMap.get(targetDocId);
if (!doc) {
- return <></>;
+ return null;
}
return (
<span key={targetDocId}>
- <button key={index} onPointerDown={e => this.clickHandler(e, targetDocId, index)}>
+ <button type="button" key={index} onPointerDown={e => this.clickHandler(e, targetDocId, index)}>
{presId.slice(0, 3) + ':' + doc.title}
</button>
-{'>'}
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index 6e9094b3a..77cf80151 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -1,4 +1,10 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+import { DateRangePicker, Provider, defaultTheme } from '@adobe/react-spectrum';
+import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { TextField } from '@mui/material';
+import { Button } from 'browndash-components';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -6,21 +12,13 @@ import Select from 'react-select';
import { Doc, DocListCast } from '../../fields/Doc';
import { DocData } from '../../fields/DocSymbols';
import { StrCast } from '../../fields/Types';
-import { DictationOverlay } from '../views/DictationOverlay';
+import { Docs } from '../documents/Documents';
import { MainViewModal } from '../views/MainViewModal';
+import { ObservableReactComponent } from '../views/ObservableReactComponent';
import { DocumentView } from '../views/nodes/DocumentView';
import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
import './CalendarManager.scss';
-import { DocumentManager } from './DocumentManager';
-import { SelectionManager } from './SelectionManager';
-import { SettingsManager } from './SettingsManager';
-// import { DateRange, Range, RangeKeyDict } from 'react-date-range';
-import { DateRangePicker, Provider, defaultTheme } from '@adobe/react-spectrum';
-import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button } from 'browndash-components';
-import { Docs } from '../documents/Documents';
-import { ObservableReactComponent } from '../views/ObservableReactComponent';
+import { SnappingManager } from './SnappingManager';
// import 'react-date-range/dist/styles.css';
// import 'react-date-range/dist/theme/default.css';
@@ -47,12 +45,12 @@ const formatCalendarDateToString = (calendarDate: any) => {
@observer
export class CalendarManager extends ObservableReactComponent<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: CalendarManager;
@observable private isOpen = false;
@observable private targetDoc: Doc | undefined = undefined; // the target document
@observable private targetDocView: DocumentView | undefined = undefined; // the DocumentView of the target doc
@observable private dialogueBoxOpacity = 1; // for the modal
- @observable private overlayOpacity = 0.4; // for the modal
@observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used
@@ -83,12 +81,10 @@ export class CalendarManager extends ObservableReactComponent<{}> {
this.creationType = type;
};
- public open = (target?: DocumentView, target_doc?: Doc) => {
- console.log('hi');
+ public open = (target?: DocumentView, targetDoc?: Doc) => {
runInAction(() => {
- this.targetDoc = target_doc || target?.Document;
+ this.targetDoc = targetDoc || target?.Document;
this.targetDocView = target;
- DictationOverlay.Instance.hasActiveModal = true;
this.isOpen = this.targetDoc !== undefined;
});
};
@@ -98,7 +94,6 @@ export class CalendarManager extends ObservableReactComponent<{}> {
TaskCompletionBox.taskCompleted = false;
setTimeout(
action(() => {
- DictationOverlay.Instance.hasActiveModal = false;
this.targetDoc = undefined;
}),
500
@@ -117,7 +112,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
@action
handleSelectChange = (option: any) => {
if (option) {
- let selectOpt = option as CalendarSelectOptions;
+ const selectOpt = option as CalendarSelectOptions;
this.selectedExistingCalendarOption = selectOpt;
this.calendarName = selectOpt.value; // or label
}
@@ -136,7 +131,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
// TODO: Make undoable
private addToCalendar = () => {
- let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
+ const docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().map(docView => docView.Document);
const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; // doc to add to calendar
console.log(targetDoc);
@@ -159,7 +154,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
}
} else {
// find existing calendar based on selected name (should technically always find one)
- const existingCalendar = this.existingCalendars.find(calendar => StrCast(calendar.title) === this.calendarName);
+ const existingCalendar = this.existingCalendars.find(findCal => StrCast(findCal.title) === this.calendarName);
if (existingCalendar) calendar = existingCalendar;
else {
this.errorMessage = 'Must select an existing calendar';
@@ -189,28 +184,26 @@ export class CalendarManager extends ObservableReactComponent<{}> {
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];
+ const docs = DocumentView.Selected().length > 1 ? DocumentView.Selected().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 });
+ DocumentView.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}
@@ -252,26 +245,24 @@ export class CalendarManager extends ObservableReactComponent<{}> {
@computed
get calendarInterface() {
- let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
+ const docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().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,
+ background: SnappingManager.userBackgroundColor,
color: StrCast(Doc.UserDoc().userColor),
}}>
- <p className="selected-doc-title" style={{ color: SettingsManager.userColor }}>
+ <p className="selected-doc-title" style={{ color: SnappingManager.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')}>
+ <div className={`calendar-creation ${this.creationType === 'new-calendar' ? 'calendar-creation-selected' : ''}`} onClick={() => this.setInterationType('new-calendar')}>
Add to New Calendar
</div>
- <div className={`calendar-creation ${this.creationType === 'existing-calendar' ? 'calendar-creation-selected' : ''}`} onClick={e => this.setInterationType('existing-calendar')}>
+ <div className={`calendar-creation ${this.creationType === 'existing-calendar' ? 'calendar-creation-selected' : ''}`} onClick={() => this.setInterationType('existing-calendar')}>
Add to Existing calendar
</div>
</div>
@@ -317,7 +308,8 @@ export class CalendarManager extends ObservableReactComponent<{}> {
color: StrCast(Doc.UserDoc().userColor),
width: '100%',
}),
- }}></Select>
+ }}
+ />
)}
</div>
<div className="description-container">
@@ -351,6 +343,6 @@ export class CalendarManager extends ObservableReactComponent<{}> {
}
render() {
- return <MainViewModal contents={this.calendarInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
+ return <MainViewModal contents={this.calendarInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} closeOnExternalClick={this.close} />;
}
}
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index 8451357ef..4fd934774 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -1,30 +1,35 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { addStyleSheet } from '../../Utils';
+import { addStyleSheet } from '../../ClientUtils';
import { Doc } from '../../fields/Doc';
import { DocCast, StrCast } from '../../fields/Types';
import { LightboxView } from '../views/LightboxView';
import { MainViewModal } from '../views/MainViewModal';
import './CaptureManager.scss';
-import { LinkManager } from './LinkManager';
-import { SelectionManager } from './SelectionManager';
+import { DocumentView } from '../views/nodes/DocumentView';
@observer
export class CaptureManager extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: CaptureManager;
static _settingsStyle = addStyleSheet();
@observable _document: any = undefined;
@observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not.
+ // eslint-disable-next-line react/sort-comp
constructor(props: {}) {
super(props);
makeObservable(this);
CaptureManager.Instance = this;
}
- public close = action(() => (this.isOpen = false));
+ public close = action(() => {
+ this.isOpen = false;
+ });
public open = action((doc: Doc) => {
this.isOpen = true;
this._document = doc;
@@ -50,7 +55,7 @@ export class CaptureManager extends React.Component<{}> {
const doc = this._document;
const order: JSX.Element[] = [];
if (doc) {
- LinkManager.Links(doc).forEach((l, i) =>
+ Doc.Links(doc).forEach((l, i) =>
order.push(
<div className="list-item">
<div className="number">{i}</div>
@@ -82,8 +87,8 @@ export class CaptureManager extends React.Component<{}> {
<div
className="cancel"
onClick={() => {
- const selected = SelectionManager.Views.slice();
- SelectionManager.DeselectAll();
+ const selected = DocumentView.Selected();
+ DocumentView.DeselectAll();
selected.map(dv => dv.props.removeDocument?.(dv.Document));
this.close();
}}>
@@ -99,15 +104,15 @@ export class CaptureManager extends React.Component<{}> {
<div className="capture-interface">
<div className="capture-t1">
<div className="recordButtonOutline" style={{}}>
- <div className="recordButtonInner" style={{}}></div>
+ <div className="recordButtonInner" style={{}} />
</div>
Conversation Capture
</div>
- <div className="capture-t2"></div>
+ <div className="capture-t2" />
{this.visibilityContent}
{this.linksContent}
<div className="close-button" onClick={this.close}>
- <FontAwesomeIcon icon={'times'} color="black" size={'lg'} />
+ <FontAwesomeIcon icon="times" color="black" size="lg" />
</div>
{this.closeButtons}
</div>
@@ -119,11 +124,10 @@ export class CaptureManager extends React.Component<{}> {
<MainViewModal
contents={this.captureInterface}
isDisplayed={this.isOpen}
- interactive={true}
+ interactive
closeOnExternalClick={this.close}
dialogueBoxStyle={{ width: '500px', height: '350px', border: 'none', background: 'whitesmoke' }}
overlayStyle={{ background: 'black' }}
- overlayDisplayedOpacity={0.6}
/>
);
}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 081115879..ffc1304bf 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,6 +1,6 @@
-import { observable, reaction, runInAction } from "mobx";
+import { reaction, runInAction } from "mobx";
import * as rp from 'request-promise';
-import { OmitKeys, Utils } from "../../Utils";
+import { ClientUtils, OmitKeys } from "../../ClientUtils";
import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
import { DocData } from "../../fields/DocSymbols";
import { InkTool } from "../../fields/InkField";
@@ -12,28 +12,31 @@ import { ScriptField } from "../../fields/ScriptField";
import { Cast, DateCast, DocCast, StrCast } from "../../fields/Types";
import { WebField, nullAudio } from "../../fields/URLField";
import { SetCachedGroups, SharingPermissions } from "../../fields/util";
-import { GestureUtils } from "../../pen-gestures/GestureUtils";
+import { Gestures } from "../../pen-gestures/GestureTypes";
import { DocServer } from "../DocServer";
+import { DocUtils, FollowLinkScript } from '../documents/DocUtils';
import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
-import { DocUtils, Docs, DocumentOptions, FInfo, FInfoFieldType } from "../documents/Documents";
+import { Docs, DocumentOptions, FInfo, FInfoFieldType } from "../documents/Documents";
import { DashboardView } from "../views/DashboardView";
import { OverlayView } from "../views/OverlayView";
-import { CollectionTreeView, TreeViewType } from "../views/collections/CollectionTreeView";
+import { CollectionTreeView } from "../views/collections/CollectionTreeView";
+import { TreeViewType } from "../views/collections/CollectionTreeViewType";
import { Colors } from "../views/global/globalEnums";
-import { media_state } from "../views/nodes/AudioBox";
-import { OpenWhere } from "../views/nodes/DocumentView";
+import { mediaState } from "../views/nodes/AudioBox";
import { ButtonType, FontIconBox } from "../views/nodes/FontIconBox/FontIconBox";
+import { ImageBox } from "../views/nodes/ImageBox";
+import { LabelBox } from "../views/nodes/LabelBox";
+import { OpenWhere } from "../views/nodes/OpenWhere";
import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox";
-import { DragManager, dropActionType } from "./DragManager";
+import { DragManager } from "./DragManager";
+import { dropActionType } from "./DropActionTypes";
import { MakeTemplate } from "./DropConverter";
-import { FollowLinkScript } from "./LinkFollower";
-import { LinkManager } from "./LinkManager";
+import { UPDATE_SERVER_CACHE } from "./LinkManager";
import { ScriptingGlobals } from "./ScriptingGlobals";
+import { SelectionManager } from "./SelectionManager";
import { ColorScheme } from "./SettingsManager";
import { SnappingManager } from "./SnappingManager";
import { UndoManager } from "./UndoManager";
-import { LabelBox } from "../views/nodes/LabelBox";
-import { ImageBox } from "../views/nodes/ImageBox";
interface Button {
// DocumentOptions fields a button can set
@@ -60,12 +63,13 @@ interface Button {
subMenu?: Button[];
}
+// eslint-disable-next-line import/no-mutable-exports
export let resolvedPorts: { server: number, socket: number };
export class CurrentUserUtils {
// initializes experimental advanced template views - slideView, headerView
static setupUserDocumentCreatorButtons(doc: Doc, userDocTemplates: Opt<Doc>) {
- const userTemplates = DocListCast(userDocTemplates?.data).filter(doc => !Doc.IsSystem(doc));
+ const userTemplates = DocListCast(userDocTemplates?.data).filter(fdoc => !Doc.IsSystem(fdoc));
const reqdOpts:DocumentOptions = {
title: "User Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true, hidden: false,
_dragOnlyWithinContainer: true, layout_hideContextMenu: true, isSystem: true, _forceActive: true,
@@ -85,11 +89,12 @@ export class CurrentUserUtils {
{ opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: `openDoc(this.doubleClickView.${OpenWhere.addRight})`}];
const reqdClickList = reqdTempOpts.map(opts => {
const allOpts = {...reqdClickOpts, ...opts.opts};
- const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined;
+ const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(fdoc => fdoc.title === opts.opts.title): undefined;
return DocUtils.AssignOpts(clickDoc, allOpts) ?? Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script, allOpts),allOpts);
});
const reqdOpts:DocumentOptions = { title: "child click editors", _height:75, isSystem: true};
+ // eslint-disable-next-line no-return-assign
return DocUtils.AssignOpts(tempClicks, reqdOpts, reqdClickList) ?? (doc[field] = Docs.Create.TreeDocument(reqdClickList, reqdOpts));
}
@@ -107,13 +112,14 @@ export class CurrentUserUtils {
const reqdClickList = reqdTempOpts.map(opts => {
const title = opts.opts.title?.toString();
const allOpts = {...reqdClickOpts, ...opts.opts};
- const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === title): undefined;
+ const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(fdoc => fdoc.title === title): undefined;
const script = ScriptField.MakeScript(opts.script, {heading:Doc.name, checked:"boolean", containingTreeView:Doc.name});
const scriptDoc = Docs.Create.ScriptingDocument(script, allOpts, title)
return DocUtils.AssignOpts(clickDoc, allOpts) ?? MakeTemplate(scriptDoc);
});
const reqdOpts:DocumentOptions = {title: "click editor templates", _height:75, isSystem: true};
+ // eslint-disable-next-line no-return-assign
return DocUtils.AssignOpts(tempClicks, reqdOpts, reqdClickList) ?? (doc[field] = Docs.Create.TreeDocument(reqdClickList, reqdOpts));
}
@@ -126,11 +132,12 @@ export class CurrentUserUtils {
{ title: "Topic", backgroundColor: "lightblue", icon: "book-open" , _layout_showTitle: "title"}];
const reqdNoteList = [...reqdTempOpts.map(opts => {
const reqdOpts = {...opts, isSystem:true, width:200, layout_autoHeight: true, layout_fitWidth: true};
- const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.title === opts.title): undefined;
+ const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(fdoc => fdoc.title === opts.title): undefined;
return DocUtils.AssignOpts(noteTemp, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts));
}), ... DocListCast(tempNotes?.data).filter(note => !reqdTempOpts.find(reqd => reqd.title === note.title))];
const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, isSystem: true };
+ // eslint-disable-next-line no-return-assign
return DocUtils.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts));
}
static setupUserTemplates(doc: Doc, field="template_user") {
@@ -138,6 +145,7 @@ export class CurrentUserUtils {
const reqdUserList = DocListCast(tempUsers?.data);
const reqdOpts:DocumentOptions = { title: "User Layouts", _height: 75, isSystem: true };
+ // eslint-disable-next-line no-return-assign
return DocUtils.AssignOpts(tempUsers, reqdOpts, reqdUserList) ?? (doc[field] = Docs.Create.TreeDocument(reqdUserList, reqdOpts));
}
@@ -159,24 +167,26 @@ export class CurrentUserUtils {
const reqdOpts = { title: "icon templates", _height: 75, isSystem: true };
const templateIconsDoc = DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts));
+ const labelBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.LabelDocument({
+ layout: LabelBox.LayoutString(fieldKey), textTransform: "unset", letterSpacing: "unset", _singleLine: false, _label_minFontSize: 14, _label_maxFontSize: 14, layout_borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
+ });
+ const imageBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.ImageDocument( "http://www.cs.brown.edu/~bcz/noImage.png", { layout:ImageBox.LayoutString(fieldKey), "icon_nativeWidth": 360 / 4, "icon_nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG, _width: 360 / 4, _height: 270 / 4, _layout_showTitle: "title", ...opts });
+ const fontBox = (opts:DocumentOptions, fieldKey:string) => Docs.Create.FontIconDocument({ layout:FontIconBox.LayoutString(fieldKey), _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts });
+
const makeIconTemplate = (type: DocumentType | undefined, templateField: string, opts:DocumentOptions) => {
const title = "icon" + (type ? "_" + type : "");
const curIcon = DocCast(templateIconsDoc[title]);
- let creator = labelBox;
- switch (opts.iconTemplate) {
- case DocumentType.IMG : creator = imageBox; break;
- case DocumentType.FONTICON: creator = fontBox; break;
- }
+ const creator = (() => { switch (opts.iconTemplate) {
+ case DocumentType.IMG : return imageBox;
+ case DocumentType.FONTICON: return fontBox;
+ default: return labelBox;
+ }})();
const allopts = {isSystem: true, onClickScriptDisable: "never", ...opts, title};
+ // eslint-disable-next-line no-return-assign
return DocUtils.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ?
DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[title] = MakeTemplate(creator(allopts, templateField)))),
{onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)", });
};
- const labelBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.LabelDocument({
- layout: LabelBox.LayoutString(fieldKey), textTransform: "unset", letterSpacing: "unset", _singleLine: false, _label_minFontSize: 14, _label_maxFontSize: 14, layout_borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
- });
- const imageBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.ImageDocument( "http://www.cs.brown.edu/~bcz/noImage.png", { layout:ImageBox.LayoutString(fieldKey), "icon_nativeWidth": 360 / 4, "icon_nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG, _width: 360 / 4, _height: 270 / 4, _layout_showTitle: "title", ...opts });
- const fontBox = (opts:DocumentOptions, fieldKey:string) => Docs.Create.FontIconDocument({ layout:FontIconBox.LayoutString(fieldKey), _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts });
const iconTemplates = [
makeIconTemplate(undefined, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "dimgray"}),
makeIconTemplate(DocumentType.AUDIO, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "lightgreen"}),
@@ -188,9 +198,9 @@ export class CurrentUserUtils {
makeIconTemplate(DocumentType.COL, "icon", { iconTemplate:DocumentType.IMG}),
makeIconTemplate(DocumentType.VID, "icon", { iconTemplate:DocumentType.IMG}),
makeIconTemplate(DocumentType.BUTTON,"title", { iconTemplate:DocumentType.FONTICON}),
- //nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now
+ // nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now
makeIconTemplate("transcription" as any, "transcription", { iconTemplate:DocumentType.LABEL, backgroundColor: "orange" }),
- //makeIconTemplate(DocumentType.PDF, "icon", {iconTemplate:DocumentType.IMG}, (opts) => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", opts))
+ // makeIconTemplate(DocumentType.PDF, "icon", {iconTemplate:DocumentType.IMG}, (opts) => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", opts))
].filter(d => d).map(d => d!);
DocUtils.AssignOpts(DocCast(doc[field]), {}, iconTemplates);
}
@@ -241,14 +251,15 @@ export class CurrentUserUtils {
Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true, _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) })
], {...opts, title: "Slide View Template"}));
const plotlyApi = () => {
- var plotly = Doc.MyPublishedDocs.find(doc => doc.title === "@plotly");
+ let plotly = Doc.MyPublishedDocs.find(fdoc => fdoc.title === "@plotly");
if (!plotly) {
- const plotly = Docs.Create.TextDocument(
+ plotly = Docs.Create.TextDocument(
`await import("https://cdn.plot.ly/plotly-2.27.0.min.js");
Plotly.newPlot(dashDiv.id, [ --DOCDATA-- ])`
, {title: "@plotly", title_custom: true, _layout_showTitle:"title", _width:300,_height:400});
Doc.AddToMyPublished(plotly);
}
+ return plotly;
}
const plotlyView = (opts:DocumentOptions) => {
const rtfield = new RichTextField(JSON.stringify(
@@ -280,10 +291,10 @@ export class CurrentUserUtils {
return slide;
}
const mermaidsApi = () => {
- var mermaids = Doc.MyPublishedDocs.find(doc => doc.title === "@mermaids");
+ let mermaids = Doc.MyPublishedDocs.find(fdoc => fdoc.title === "@mermaids");
if (!mermaids) {
- const mermaids = Docs.Create.TextDocument(
- `const mdef = (await import("https://cdn.jsdelivr.net/npm/mermaid\@10.8.0/dist/mermaid.esm.min.mjs")).default;
+ mermaids = Docs.Create.TextDocument(
+ `const mdef = (await import("https://cdn.jsdelivr.net/npm/mermaid@10.8.0/dist/mermaid.esm.min.mjs")).default;
window["callb"] = (x) => {
alert(x);
}
@@ -301,6 +312,7 @@ export class CurrentUserUtils {
, {title: "@mermaids", title_custom: true, _layout_showTitle:"title", _width:300,_height:400});
Doc.AddToMyPublished(mermaids);
}
+ return mermaids;
}
const mermaidsView = (opts:DocumentOptions) => {
const rtfield = new RichTextField(JSON.stringify(
@@ -333,7 +345,7 @@ pie title Minerals in my tap water
slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, {documentView:"any"});
return slide;
}
- const apis = [plotlyApi(), mermaidsApi()]
+ plotlyApi(); mermaidsApi();
const emptyThings:{key:string, // the field name where the empty thing will be stored
opts:DocumentOptions, // the document options that are required for the empty thing
funcs?:{[key:string]: any}, // computed fields that are rquired for the empth thing
@@ -406,7 +418,7 @@ pie title Minerals in my tap water
/// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
static setupCreatorButtons(doc: Doc, dragCreatorDoc?:Doc):Doc {
const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => {
- const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined;
+ const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(fdoc => fdoc.title === reqdOpts.title): undefined;
const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit,
_width: 60, _height: 60, _dragOnlyWithinContainer: true,
btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, isSystem: true,
@@ -450,7 +462,7 @@ pie title Minerals in my tap water
this.setupLeftSidebarPanel(doc);
const myLeftSidebarMenu = DocCast(doc[field]);
const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, toolTip, hidden, scripts, funcs }) => {
- const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined;
+ const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(fdoc => fdoc.title === title) : undefined;
const reqdBtnOpts:DocumentOptions = {
title, icon, target, toolTip, hidden, btnType: ButtonType.MenuButton, isSystem: true, undoIgnoreFields: new List<string>(['height', 'data_columnHeaders']), dontRegisterView: true,
_width: 60, _height: 60, _dragOnlyWithinContainer: true,
@@ -465,83 +477,9 @@ pie title Minerals in my tap water
return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" });
}
- // Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu
- static setupActiveMobileMenu(doc: Doc, field="activeMobileMenu") {
- const reqdOpts = { _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, isSystem: true, _chromeHidden: true,};
- DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(this.setupMobileButtons(), opts), reqdOpts);
- }
-
- // Sets up mobile buttons for inside mobile menu
- static setupMobileButtons(doc?: Doc, buttons?: string[]) {
- return [];
- const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [
- { title: "DASHBOARDS", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Dashboards from your mobile, and navigate through all of your documents. " },
- { title: "UPLOAD", icon: "upload", click: 'openMobileUploads()', backgroundColor: "lightgrey", info: "Upload files from your mobile device so they can be accessed on Dash Web." },
- { title: "MOBILE UPLOAD", icon: "mobile", click: 'switchToMobileUploadCollection()', backgroundColor: "lightgrey", info: "Access the collection of your mobile uploads." },
- { title: "RECORD", icon: "microphone", click: 'openMobileAudio()', backgroundColor: "lightgrey", info: "Use your phone to record, dictate and then upload audio onto Dash Web." },
- { title: "PRESENTATION", icon: "desktop", click: 'switchToMobilePresentation()', backgroundColor: "lightgrey", info: "Use your phone as a remote for you presentation." },
- { title: "SETTINGS", icon: "cog", click: 'openMobileSettings()', backgroundColor: "lightgrey", info: "Change your password, log out, or manage your account security." }
- ];
- // returns a list of mobile buttons
- return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data =>
- this.mobileButton({
- title: data.title,
- _lockedPosition: true,
- onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
- backgroundColor: data.backgroundColor, isSystem: true
- },
- [this.createToolButton({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)", isSystem: true, btnType: ButtonType.ClickButton, }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])])
- );
- }
-
- // sets up the main document for the mobile button
- static mobileButton = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MulticolumnDocument(docs, {
- ...opts,
- _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15,
- layout_borderRounding: "5px", layout_boxShadow: "0 0", isSystem: true
- }) as any as Doc
-
- // sets up the text container for the information contained within the mobile button
- static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MultirowDocument(docs, {
- ...opts,
- _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25,
- backgroundColor: "rgba(0,0,0,0)", layout_borderRounding: "0", layout_boxShadow: "0 0", ignoreClick: true, isSystem: true
- }) as any as Doc
-
- // Sets up the title of the button
- static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, {
- ...opts,
- title: buttonTitle, _text_fontSize: "37px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", isSystem: true
- }) as any as Doc
-
- // Sets up the description of the button
- static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, {
- ...opts,
- title: "info", _text_fontSize: "25px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, isSystem: true
- }) as any as Doc
-
-
-
- static setupMobileInkingDoc(userDoc: Doc) {
- return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white", isSystem: true });
- }
-
- static setupMobileUploadDoc(userDoc: Doc) {
- // const addButton = Docs.Create.FontIconDocument({ onDragStart: ScriptField.MakeScript('addWebToMobileUpload()'), title: "Add Web Doc to Upload Collection", icon: "plus", backgroundColor: "black" })
- const webDoc = Docs.Create.WebDocument("https://www.britannica.com/biography/Miles-Davis", {
- title: "Upload Images From the Web", _lockedPosition: true, isSystem: true
- });
- const uploadDoc = Docs.Create.StackingDocument([], {
- title: "Mobile Upload Collection", backgroundColor: "white", _lockedPosition: true, isSystem: true, _chromeHidden: true,
- });
- return Docs.Create.StackingDocument([webDoc, uploadDoc], {
- _width: screen.width, _lockedPosition: true, title: "Upload", _layout_autoHeight: true, _yMargin: 80, backgroundColor: "lightgray", isSystem: true, _chromeHidden: true,
- });
- }
-
/// Search option on the left side button panel
static setupSearcher(doc: Doc, field:string) {
- return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.SearchDocument(opts), {
+ return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.SearchDocument(opts), {
dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", isSystem: true, childDragAction: dropActionType.embed,
_lockedPosition: true, _type_collection: CollectionViewType.Schema });
}
@@ -552,7 +490,7 @@ pie title Minerals in my tap water
const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, allTools?.length ? allTools[0]:undefined);
const userTools = allTools && allTools?.length > 1 ? allTools[1]:undefined;
const userBtns = CurrentUserUtils.setupUserDocumentCreatorButtons(doc, userTools);
- //doc.myUserBtns = new PrefetchProxy(userBtns);
+ // doc.myUserBtns = new PrefetchProxy(userBtns);
const reqdToolOps:DocumentOptions = {
title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0",
layout_explainer: "This is a palette of documents that can be created.",
@@ -563,7 +501,7 @@ pie title Minerals in my tap water
/// initializes the left sidebar dashboard pane
static setupDashboards(doc: Doc, field:string) {
- var myDashboards = DocCast(doc[field]);
+ let myDashboards = DocCast(doc[field]);
const newDashboard = `createNewDashboard()`;
@@ -572,9 +510,9 @@ pie title Minerals in my tap water
const reqdBtnScript = {onClick: newDashboard,}
const newDashboardButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myDashboards?.layout_headerButton), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript);
- const contextMenuScripts = [/*newDashboard*/] as string[];
- const contextMenuLabels = [/*"Create New Dashboard"*/] as string[];
- const contextMenuIcons = [/*"plus"*/] as string[];
+ const contextMenuScripts = [/* newDashboard */] as string[];
+ const contextMenuLabels = [/* "Create New Dashboard" */] as string[];
+ const contextMenuIcons = [/* "plus" */] as string[];
const childContextMenuScripts = [`toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(this)`, 'removeDashboard(this)', 'resetDashboard(this)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters
const childContextMenuFilters = ['!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any, '!IsNoviceMode()'];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts
const childContextMenuLabels = ["Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard", "Reset Dashboard"];// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters
@@ -605,7 +543,7 @@ pie title Minerals in my tap water
/// initializes the left sidebar File system pane
static setupFilesystem(doc: Doc, field:string) {
- var myFilesystem = DocCast(doc[field]);
+ const myFilesystem = DocCast(doc[field]);
const newFolderOpts: DocumentOptions = {
_forceActive: true, _dragOnlyWithinContainer: true, _embedContainer: Doc.MyFilesystem, _width: 30, _height: 30, undoIgnoreFields:new List<string>(['treeView_SortCriterion']),
@@ -650,7 +588,7 @@ pie title Minerals in my tap water
/// initializes the left sidebar panel view of the UserDoc
static setupUserDocView(doc: Doc, field:string) {
const reqdOpts:DocumentOptions = {
- _lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view",
+ _lockedPosition: true, _gridGap: 5, _forceActive: true, title: ClientUtils.CurrentUserEmail() +"-view",
layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: dropActionType.same, ignoreClick: true, isSystem: true,
treeView_HideTitle: true, treeView_TruncateTitleWidth: 350
};
@@ -671,14 +609,14 @@ pie title Minerals in my tap water
static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({
btnType: ButtonType.ToolButton, _dropPropertiesToRemove: new List<string>([ "layout_hideContextMenu"]),
- /*_nativeWidth: 40, _nativeHeight: 40, */ _width: 40, _height: 40, isSystem: true, ...opts,
+ /* _nativeWidth: 40, _nativeHeight: 40, */ _width: 40, _height: 40, isSystem: true, ...opts,
})
/// initializes the required buttons in the expanding button menu at the bottom of the Dash window
static setupDockedButtons(doc: Doc, field="myDockedBtns") {
const dockedBtns = DocCast(doc[field]);
const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:string}) =>
- DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ??
+ DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(fdoc => fdoc.title === opts.title), opts) ??
CurrentUserUtils.createToolButton(opts), scripts, funcs);
const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
@@ -694,8 +632,8 @@ pie title Minerals in my tap water
title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: dropActionType.move,
childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: true, ignoreClick: true
};
- reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "Redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
- reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "Undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true });
+ reaction(() => UndoManager.redoStack.slice(), () => { Doc.GetProto(btns.find(btn => btn.title === "Redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4; }, { fireImmediately: true });
+ reaction(() => UndoManager.undoStack.slice(), () => { Doc.GetProto(btns.find(btn => btn.title === "Undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4; }, { fireImmediately: true });
return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
}
@@ -752,9 +690,9 @@ pie title Minerals in my tap water
{ title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: "pen", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }},
{ title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: "write", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
{ title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", toolType: "eraser", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
- { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType:GestureUtils.Gestures.Circle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
- { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType:GestureUtils.Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
- { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType:GestureUtils.Gestures.Line, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+ { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType: Gestures.Circle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+ { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType: Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+ { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType: Gestures.Line, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
{ title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } },
{ title: "Labels", toolTip: "Lab els", btnType: ButtonType.ToggleButton, icon: "text-width", toolType: "labels", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, },
{ title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberSliderButton, toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1},
@@ -773,7 +711,7 @@ pie title Minerals in my tap water
return [
{ title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", scripts: { onClick: '{ return webBack(); }' }},
{ title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", scripts: { onClick: '{ return webForward(); }'}},
- { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} },
+ { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} },
];
}
static videoTools() {
@@ -794,24 +732,24 @@ pie title Minerals in my tap water
CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel,
CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map,
CollectionViewType.Grid, CollectionViewType.NoteTaking]),
- title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
+ title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: '{ return setView(value, _readOnly_); }'}},
{ 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: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'} },
{ title: "Template",icon: "scroll", toolTip: "Default Note Template",btnType: ButtonType.ToggleButton, expertMode: false, toolType:DocumentType.RTF, scripts: { onClick: '{ return setDefaultTemplate(_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(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: "Video", icon: "Video", toolTip: "Video functions", subMenu: CurrentUserUtils.videoTools(), expertMode: false, toolType:DocumentType.VID, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected
- { title: "Image", icon: "Image", toolTip: "Image functions", subMenu: CurrentUserUtils.imageTools(), expertMode: false, toolType:DocumentType.IMG, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when image 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
+ { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!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: '!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: '!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: '!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: `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: `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: `!SelectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+ { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+ { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+ { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
+ { title: "Video", icon: "Video", toolTip: "Video functions", subMenu: CurrentUserUtils.videoTools(), expertMode: false, toolType:DocumentType.VID, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected
+ { title: "Image", icon: "Image", toolTip: "Image functions", subMenu: CurrentUserUtils.imageTools(), expertMode: false, toolType:DocumentType.IMG, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected
+ { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Schema is selected
];
}
@@ -835,16 +773,16 @@ pie title Minerals in my tap water
static setupContextMenuBtn(params:Button, menuDoc:Doc):Doc {
const menuBtnDoc = DocListCast(menuDoc?.data).find( doc => doc.title === params.title);
- const subMenu = params.subMenu;
+ const {subMenu} = params;
if (!subMenu) { // button does not have a sub menu
return this.setupContextMenuButton(params, menuBtnDoc, menuDoc);
}
// linear view
const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]),
- childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
+ childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: !params.scripts?.onClick,
linearView_SubMenu: true, linearView_Expandable: true, embedContainer: menuDoc};
- const items = (menuBtnDoc?:Doc) => !menuBtnDoc ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtnDoc) );
+ const items = (menutBtn?:Doc) => !menutBtn ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menutBtn) );
const creator = params.btnType === ButtonType.MultiToggleButton ? this.multiToggleList : this.linearButtonList;
const btnDoc = DocUtils.AssignScripts( DocUtils.AssignDocField(menuDoc, StrCast(params.title),
(opts) => creator(opts, items(menuBtnDoc)), reqdSubMenuOpts, items(menuBtnDoc)), params.scripts, params.funcs);
@@ -866,17 +804,17 @@ pie title Minerals in my tap water
Doc.UserDoc().workspaceReplayingState = undefined;
const dockedBtns = DocCast(doc[field]);
const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:any}) =>
- DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ??
+ DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(fdoc => fdoc.title === opts.title), opts) ??
CurrentUserUtils.createToolButton(opts), scripts, funcs);
const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
{ opts: { title: "Replicate",icon:"camera",toolTip: "Copy dashboard layout",btnType: ButtonType.ClickButton, expertMode: true}, scripts: { onClick: `snapshotDashboard()`}},
{ opts: { title: "Recordings", toolTip: "Workspace Recordings", btnType: ButtonType.DropdownList,expertMode: false, ignoreClick: true, width: 100}, funcs: {hidden: `false`, btnList:`getWorkspaceRecordings()`}, scripts: { script: `{ return replayWorkspace(value, _readOnly_); }`, onDragScript: `{ return startRecordingDrag(value); }`}},
{ opts: { title: "Stop Rec",icon: "stop", toolTip: "Stop recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `!isWorkspaceRecording()`}, scripts: { onClick: `stopWorkspaceRecording()`}},
- { opts: { title: "Play", icon: "play", toolTip: "Play recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `resumeWorkspaceReplaying(getCurrentRecording())`}},
- { opts: { title: "Pause", icon: "pause",toolTip: "Pause playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Playing}"`}, scripts: { onClick: `pauseWorkspaceReplaying(getCurrentRecording())`}},
- { opts: { title: "Stop", icon: "stop", toolTip: "Stop playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `stopWorkspaceReplaying(getCurrentRecording())`}},
- { opts: { title: "Delete", icon: "trash",toolTip: "delete selected rec", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `removeWorkspaceReplaying(getCurrentRecording())`}}
+ { opts: { title: "Play", icon: "play", toolTip: "Play recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Paused}"`}, scripts: { onClick: `resumeWorkspaceReplaying(getCurrentRecording())`}},
+ { opts: { title: "Pause", icon: "pause",toolTip: "Pause playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Playing}"`}, scripts: { onClick: `pauseWorkspaceReplaying(getCurrentRecording())`}},
+ { opts: { title: "Stop", icon: "stop", toolTip: "Stop playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Paused}"`}, scripts: { onClick: `stopWorkspaceReplaying(getCurrentRecording())`}},
+ { opts: { title: "Delete", icon: "trash",toolTip: "delete selected rec", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Paused}"`}, scripts: { onClick: `removeWorkspaceReplaying(getCurrentRecording())`}}
];
const btns = btnDescs.map(desc => dockBtn({_width: desc.opts.width??30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts, desc.funcs));
const dockBtnsReqdOpts:DocumentOptions = {
@@ -890,15 +828,17 @@ pie title Minerals in my tap water
return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), { title: "published docs", backgroundColor: "#aca3a6", isSystem: true });
}
- /// The database of all links on all documents
+ static newAccount: boolean = false;
+
+ // The database of all links on all documents
static setupLinkDocs(doc: Doc, linkDatabaseId: string) {
- if (!(Docs.newAccount ? undefined : DocCast(doc.myLinkDatabase))) {
+ if (!(CurrentUserUtils.newAccount ? undefined : DocCast(doc.myLinkDatabase))) {
const linkDocs = new Doc(linkDatabaseId, true);
- linkDocs.title = "LINK DATABASE: " + Doc.CurrentUserEmail;
- linkDocs.author = Doc.CurrentUserEmail;
+ linkDocs.title = "LINK DATABASE: " + ClientUtils.CurrentUserEmail();
+ linkDocs.author = ClientUtils.CurrentUserEmail();
linkDocs.isSystem = true;
linkDocs.data = new List<Doc>([]);
- linkDocs["acl-Guest"] = SharingPermissions.Augment;
+ linkDocs.acl_Guest = SharingPermissions.Augment;
doc.myLinkDatabase = new PrefetchProxy(linkDocs);
}
}
@@ -919,7 +859,7 @@ pie title Minerals in my tap water
// childContextMenuScripts: new List<ScriptField>([addToDashboards!,]),
// childContextMenuLabels: new List<string>(["Add to Dashboards",]),
// childContextMenuIcons: new List<string>(["user-plus",]),
- "acl-Guest": SharingPermissions.Augment, "_acl-Guest": SharingPermissions.Augment,
+ acl_Guest: SharingPermissions.Augment, _acl_Guest: SharingPermissions.Augment,
childDragAction: dropActionType.embed, isSystem: true, childContentPointerEvents: "none", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true,
// NOTE: treeView_HideTitle & _layout_showTitle is for a TreeView's editable title, _layout_showTitle is for DocumentViews title bar
_layout_showTitle: "title", treeView_HideTitle: true, ignoreClick: true, _lockedPosition: true, layout_boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true,
@@ -949,16 +889,17 @@ pie title Minerals in my tap water
/// Updates the UserDoc to have all required fields, docs, etc. No changes should need to be
/// written to the server if the code hasn't changed. However, choices need to be made for each Doc/field
/// whether to revert to "default" values, or to leave them as the user/system last set them.
- static updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
+ static updateUserDocument(docIn: Doc, sharingDocumentId: string, linkDatabaseId: string) {
+ const doc = docIn;
DocUtils.AssignDocField(doc, "globalGroupDatabase", () => Docs.Prototypes.MainGroupDocument(), {});
- reaction(() => DateCast(DocCast(doc.globalGroupDatabase)["data_modificationDate"]),
+ reaction(() => DateCast(DocCast(doc.globalGroupDatabase).data_modificationDate),
async () => {
const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data);
- const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || [];
- SetCachedGroups(["Guest", ...mygroups?.map(g => StrCast(g.title))]);
+ const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(ClientUtils.CurrentUserEmail())) || [];
+ SetCachedGroups(["Guest", ...(mygroups?.map(g => StrCast(g.title))??[])]);
}, { fireImmediately: true });
doc.isSystem ?? (doc.isSystem = true);
- doc.title ?? (doc.title = Doc.CurrentUserEmail);
+ doc.title ?? (doc.title = ClientUtils.CurrentUserEmail());
Doc.noviceMode ?? (Doc.noviceMode = true);
doc._showLabel ?? (doc._showLabel = true);
doc.textAlign ?? (doc.textAlign = "left");
@@ -971,7 +912,7 @@ pie title Minerals in my tap water
doc.activeFillColor ?? (doc.activeFillColor = "");
doc.activeArrowStart ?? (doc.activeArrowStart = "");
doc.activeArrowEnd ?? (doc.activeArrowEnd = "");
- doc.activeDash ?? (doc.activeDash == "0");
+ doc.activeDash ?? (doc.activeDash === "0");
doc.fontSize ?? (doc.fontSize = "12px");
doc.fontFamily ?? (doc.fontFamily = "Arial");
doc.fontColor ?? (doc.fontColor = "black");
@@ -988,27 +929,26 @@ pie title Minerals in my tap water
this.setupLinkDocs(doc, linkDatabaseId);
this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing
this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon
- this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile
this.setupPublished(doc); // sets up the list doc of all docs that have been published (meaning that they can be auto-linked by typing their title into another text box)
this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected
this.setupTopbarButtons(doc);
this.setupDockedButtons(doc); // the bottom bar of font icons
this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left
this.setupDocTemplates(doc); // sets up the template menu of templates
- //this.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption
- DocUtils.AssignDocField(doc, "globalScriptDatabase", (opts) => Docs.Prototypes.MainScriptDocument(), {});
+ // sthis.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption
+ DocUtils.AssignDocField(doc, "globalScriptDatabase", () => Docs.Prototypes.MainScriptDocument(), {});
DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "My Header Bar", isSystem: true, _chromeHidden:true, layout_maxShown: 10, childLayoutFitWidth:false, childDocumentsActive:false, dropAction: dropActionType.move}); // drop down panel at top of dashboard for stashing documents
- Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards)
- Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs)
- Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed)
-
- Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)).data = new WebField("https://www.wikipedia.org")
+ SelectionManager.DeselectAll(); // this forces SelectionManager implementation to copy over to DocumentView's API. This also triggers the LinkManager to be created
+
+ Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards);
+ Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs);
+ Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed);
- new LinkManager();
+ Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)).data = new WebField("https://www.wikipedia.org");
- DocServer.CacheNeedsUpdate && setTimeout(DocServer.UPDATE_SERVER_CACHE, 2500);
- setInterval(DocServer.UPDATE_SERVER_CACHE, 120000);
+ DocServer.CacheNeedsUpdate() && setTimeout(UPDATE_SERVER_CACHE, 2500);
+ setInterval(UPDATE_SERVER_CACHE, 120000);
return doc;
}
static setupFieldInfos(doc:Doc, field="fieldInfos") {
@@ -1025,18 +965,17 @@ pie title Minerals in my tap water
case FInfoFieldType.Doc: opts.fieldValues = new List<Doc>(options.values as any); break;
default: opts.fieldValues = new List<string>(options.values as any); break;// string, pointerEvents, dimUnit, dropActionType
}
- DocUtils.AssignDocField(infos, pair[0], opts => Doc.assign(new Doc(), OmitKeys(opts,["values"]).omit), opts);
+ DocUtils.AssignDocField(infos, pair[0], docOpts => Doc.assign(new Doc(), OmitKeys(docOpts,["values"]).omit), opts);
}
});
}
- @observable public static ServerVersion: string = ';'
public static async loadCurrentUser() {
- return rp.get(Utils.prepend("/getCurrentUser")).then(async response => {
+ return rp.get(ClientUtils.prepend("/getCurrentUser")).then(async response => {
if (response) {
const result: { version: string, userDocumentId: string, sharingDocumentId: string, linkDatabaseId: string, email: string, cacheDocumentIds: string, resolvedPorts: string } = JSON.parse(response);
- runInAction(() => CurrentUserUtils.ServerVersion = result.version);
- Doc.CurrentUserEmail = result.email;
+ runInAction(() => { SnappingManager.SetServerVersion(result.version); });
+ ClientUtils.SetCurrentUserEmail(result.email);
resolvedPorts = result.resolvedPorts as any;
DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts?.socket, result.email);
if (result.cacheDocumentIds)
@@ -1044,13 +983,13 @@ pie title Minerals in my tap water
const ids = result.cacheDocumentIds.split(";");
const batch = 30000;
for (let i = 0; i < ids.length; i += batch) {
+ // eslint-disable-next-line no-await-in-loop
await DocServer.GetRefFields(ids.slice(i, i+batch));
}
}
return result;
- } else {
- throw new Error("There should be a user! Why does Dash think there isn't one?");
- }
+ }
+ throw new Error("There should be a user! Why does Dash think there isn't one?");
});
}
@@ -1060,12 +999,12 @@ pie title Minerals in my tap water
linkDatabaseId: string;
}) {
return DocServer.GetRefField(info.userDocumentId).then(async field => {
- Docs.newAccount = !(field instanceof Doc);
+ CurrentUserUtils.newAccount = !(field instanceof Doc);
await Docs.Prototypes.initialize();
- const userDoc = Docs.newAccount ? new Doc(info.userDocumentId, true) : field as Doc;
+ const userDoc = CurrentUserUtils.newAccount ? new Doc(info.userDocumentId, true) : field as Doc;
this.updateUserDocument(Doc.SetUserDoc(userDoc), info.sharingDocumentId, info.linkDatabaseId);
- if (Docs.newAccount) {
- if (Doc.CurrentUserEmail === "guest") {
+ if (CurrentUserUtils.newAccount) {
+ if (ClientUtils.CurrentUserEmail() === "guest") {
DashboardView.createNewDashboard(undefined, "guest dashboard");
} else {
userDoc.activePage = "home";
@@ -1082,14 +1021,10 @@ pie title Minerals in my tap water
input.type = "file";
input.multiple = true;
input.accept = ".zip, application/pdf, video/*, image/*, audio/*";
- input.onchange = async _e => {
+ input.onchange = async () => {
const file = input.files?.[0];
if (file?.type === 'application/zip' || file?.type === 'application/x-zip-compressed') {
const doc = await Doc.importDocument(file);
- // NOT USING SOLR, so need to replace this with something else // if (doc instanceof Doc) {
- // setTimeout(() => SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs =>
- // docs.docs.forEach(d => LinkManager.Instance.addLink(d))), 2000); // need to give solr some time to update so that this query will find any link docs we've added.
- // }
const list = Cast(Doc.MyImports.data, listSpec(Doc), null);
doc instanceof Doc && list?.splice(0, 0, doc);
} else if (input.files && input.files.length !== 0) {
@@ -1109,10 +1044,17 @@ pie title Minerals in my tap water
}
}
-ScriptingGlobals.add(function MySharedDocs() { return Doc.MySharedDocs; }, "document containing all shared Docs");
-ScriptingGlobals.add(function IsExploreMode() { return SnappingManager.ExploreMode; }, "is Dash in exploration mode");
-ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode");
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function MySharedDocs() { return Doc.MySharedDocs; }, "document containing all shared Docs");
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function IsExploreMode() { return SnappingManager.ExploreMode; }, "is Dash in exploration mode");
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode");
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering");
-ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; });
-ScriptingGlobals.add(function getSharingDoc() {return Doc.SharingDoc() }); \ No newline at end of file
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function getSharingDoc() { return Doc.SharingDoc() });
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 82c63695c..bc9fe813f 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -1,18 +1,22 @@
+/* eslint-disable no-use-before-define */
import * as interpreter from 'words-to-numbers';
// @ts-ignore bcz: how are you supposed to include these definitions since dom-speech-recognition isn't a module?
import type {} from '@types/dom-speech-recognition';
+import { ClientUtils } from '../../ClientUtils';
import { Doc, Opt } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
import { RichTextField } from '../../fields/RichTextField';
import { listSpec } from '../../fields/Schema';
-import { Cast, CastCtor, DocCast } from '../../fields/Types';
+import { Cast, CastCtor } from '../../fields/Types';
import { AudioField, ImageField } from '../../fields/URLField';
-import { Utils } from '../../Utils';
-import { Docs } from '../documents/Documents';
+import { AudioAnnoState } from '../../server/SharedMediaTypes';
+import { Networking } from '../Network';
import { DocumentType } from '../documents/DocumentTypes';
+import { Docs } from '../documents/Documents';
import { DictationOverlay } from '../views/DictationOverlay';
-import { DocumentView, OpenWhere } from '../views/nodes/DocumentView';
-import { SelectionManager } from './SelectionManager';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { OpenWhere } from '../views/nodes/OpenWhere';
import { UndoManager } from './UndoManager';
/**
@@ -61,12 +65,13 @@ export namespace DictationManager {
const intraSession = '. ';
const interSession = ' ... ';
- export let isListening = false;
+ let isListening = false;
let isManuallyStopped = false;
- let current: string | undefined = undefined;
+ let current: string | undefined;
let sessionResults: string[] = [];
+ // eslint-disable-next-line new-cap
const recognizer: Opt<SpeechRecognition> = webkitSpeechRecognition ? new webkitSpeechRecognition() : undefined;
export type InterimResultHandler = (results: string) => any;
@@ -87,7 +92,7 @@ export namespace DictationManager {
let pendingListen: Promise<string> | string | undefined;
export const listen = async (options?: Partial<ListeningOptions>) => {
- if (pendingListen instanceof Promise) return pendingListen.then(pl => innerListen(options));
+ if (pendingListen instanceof Promise) return pendingListen.then(() => innerListen(options));
return innerListen(options);
};
const innerListen = async (options?: Partial<ListeningOptions>) => {
@@ -103,7 +108,7 @@ export namespace DictationManager {
results = await (pendingListen = listenImpl(options));
pendingListen = undefined;
if (results) {
- Utils.CopyText(results);
+ ClientUtils.CopyText(results);
if (overlay) {
DictationOverlay.Instance.isListening = false;
const execute = options?.tryExecute;
@@ -150,29 +155,29 @@ export namespace DictationManager {
recognizer.start();
- return new Promise<string>((resolve, reject) => {
+ return new Promise<string>(resolve => {
recognizer.onerror = (e: any) => {
// e is SpeechRecognitionError but where is that defined?
if (!(indefinite && e.error === 'no-speech')) {
recognizer.stop();
resolve(e);
- //reject(e);
}
};
recognizer.onresult = (e: SpeechRecognitionEvent) => {
current = synthesize(e, intra);
- let matchedTerminator: string | undefined;
- if (options?.terminators && (matchedTerminator = options.terminators.find(end => (current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false)))) {
+ const matchedTerminator = options?.terminators?.find(end => (current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false));
+ if (options?.terminators && matchedTerminator) {
current = matchedTerminator;
recognizer.abort();
return complete();
}
!isManuallyStopped && handler?.(current);
- //isManuallyStopped && complete();
+ // isManuallyStopped && complete()
+ return undefined;
};
- recognizer.onend = (e: Event) => {
+ recognizer.onend = () => {
if (!indefinite || isManuallyStopped) {
return complete();
}
@@ -182,6 +187,7 @@ export namespace DictationManager {
current = undefined;
}
recognizer.start();
+ return undefined;
};
const complete = () => {
@@ -202,7 +208,7 @@ export namespace DictationManager {
});
};
- export const stop = (salvageSession = true) => {
+ export const stop = (/* salvageSession = true */) => {
if (!isListening || !recognizer) {
return;
}
@@ -212,7 +218,7 @@ export namespace DictationManager {
};
const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => {
- const results = e.results;
+ const { results } = e;
const transcripts: string[] = [];
for (let i = 0; i < results.length; i++) {
transcripts.push(results.item(i).item(0).transcript.trim());
@@ -231,24 +237,30 @@ export namespace DictationManager {
export type DependentEntry = { expression: RegExp; action: DependentAction; restrictTo?: DocumentType[] };
export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value);
- export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry);
+ export const RegisterDependent = (entry: DependentEntry) => {
+ const { expression, action, restrictTo } = entry;
+ return Dependent.push({ expression, action, restrictTo: restrictTo ?? [] });
+ };
- export const execute = async (phrase: string) => {
- return UndoManager.RunInBatch(async () => {
+ export const execute = async (phrase: string) =>
+ UndoManager.RunInBatch(async () => {
console.log('PHRASE: ' + phrase);
- const targets = SelectionManager.Views;
+ const targets = DocumentView.Selected();
if (!targets || !targets.length) {
- return;
+ return undefined;
}
+ // eslint-disable-next-line no-param-reassign
phrase = phrase.toLowerCase();
const entry = Independent.get(phrase);
if (entry) {
let success = false;
- const restrictTo = entry.restrictTo;
+ const { restrictTo } = entry;
+ // eslint-disable-next-line no-restricted-syntax
for (const target of targets) {
if (!restrictTo || validate(target, restrictTo)) {
+ // eslint-disable-next-line no-await-in-loop
await entry.action(target);
success = true;
}
@@ -256,16 +268,19 @@ export namespace DictationManager {
return success;
}
- for (const entry of Dependent) {
- const regex = entry.expression;
+ // eslint-disable-next-line no-restricted-syntax
+ for (const depEntry of Dependent) {
+ const regex = depEntry.expression;
const matches = regex.exec(phrase);
regex.lastIndex = 0;
if (matches !== null) {
let success = false;
- const restrictTo = entry.restrictTo;
+ const { restrictTo } = depEntry;
+ // eslint-disable-next-line no-restricted-syntax
for (const target of targets) {
if (!restrictTo || validate(target, restrictTo)) {
- await entry.action(target, matches);
+ // eslint-disable-next-line no-await-in-loop
+ await depEntry.action(target, matches);
success = true;
}
}
@@ -275,7 +290,6 @@ export namespace DictationManager {
return false;
}, 'Execute Command');
- };
const ConstructorMap = new Map<DocumentType, CastCtor>([
[DocumentType.COL, listSpec(Doc)],
@@ -294,6 +308,7 @@ export namespace DictationManager {
};
const validate = (target: DocumentView, types: DocumentType[]) => {
+ // eslint-disable-next-line no-restricted-syntax
for (const type of types) {
if (tryCast(target, type)) {
return true;
@@ -318,7 +333,9 @@ export namespace DictationManager {
[
'clear',
{
- action: (target: DocumentView) => (Doc.GetProto(target.Document).data = new List()),
+ action: (target: DocumentView) => {
+ Doc.GetProto(target.Document).data = new List();
+ },
restrictTo: [DocumentType.COL],
},
],
@@ -328,7 +345,7 @@ export namespace DictationManager {
{
action: (target: DocumentView) => {
const newBox = Docs.Create.TextDocument('', { _width: 400, _height: 200, title: 'My Outline', _layout_autoHeight: true });
- const proto = DocCast(newBox.proto);
+ const proto = newBox[DocData];
const prompt = 'Press alt + r to start dictating here...';
const head = 3;
const anchor = head + prompt.length;
@@ -341,7 +358,7 @@ export namespace DictationManager {
],
]);
- const Dependent = new Array<DependentEntry>(
+ const Dependent = [
{
expression: /create (\w+) documents of type (image|nested collection)/g,
action: (target: DocumentView, matches: RegExpExecArray) => {
@@ -361,6 +378,7 @@ export namespace DictationManager {
case 'nested collection':
created = Docs.Create.FreeformDocument([], {});
break;
+ default:
}
created && Doc.AddDocToList(dataDoc, fieldKey, created);
}
@@ -375,7 +393,46 @@ export namespace DictationManager {
mode && (target.Document._type_collection = mode);
},
restrictTo: [DocumentType.COL],
- }
- );
+ },
+ ];
+ }
+ export function recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
+ let gumStream: any;
+ let recorder: any;
+ navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
+ let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null);
+ if (audioTextAnnos) audioTextAnnos.push('');
+ else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List<string>(['']);
+ DictationManager.Controls.listen({
+ interimHandler: value => { audioTextAnnos[audioTextAnnos.length - 1] = value; }, // prettier-ignore
+ continuous: { indefinite: false },
+ }).then(results => {
+ if (results && [DictationManager.Controls.Infringed].includes(results)) {
+ DictationManager.Controls.stop();
+ }
+ onEnd?.();
+ });
+
+ gumStream = stream;
+ recorder = new MediaRecorder(stream);
+ recorder.ondataavailable = async (e: any) => {
+ const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
+ if (!(result instanceof Error)) {
+ const audioField = new AudioField(result.accessPaths.agnostic.client);
+ const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null);
+ if (audioAnnos) audioAnnos.push(audioField);
+ else dataDoc[field + '_audioAnnotations'] = new List([audioField]);
+ }
+ };
+ recorder.start();
+ const stopFunc = () => {
+ recorder.stop();
+ DictationManager.Controls.stop(/* false */);
+ dataDoc.audioAnnoState = AudioAnnoState.stopped;
+ gumStream.getAudioTracks()[0].stop();
+ };
+ if (onRecording) onRecording(stopFunc);
+ else setTimeout(stopFunc, 5000);
+ });
}
}
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 40d28c690..5bcac7330 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,35 +1,31 @@
import { Howl } from 'howler';
import { action, computed, makeObservable, observable, ObservableSet, observe } from 'mobx';
import { Doc, Opt } from '../../fields/Doc';
-import { AclAdmin, AclEdit, Animation, DocData } from '../../fields/DocSymbols';
+import { Animation, DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { listSpec } from '../../fields/Schema';
import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
import { AudioField } from '../../fields/URLField';
-import { GetEffectiveAcl } from '../../fields/util';
import { CollectionViewType } from '../documents/DocumentTypes';
-import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { TabDocView } from '../views/collections/TabDocView';
import { LightboxView } from '../views/LightboxView';
-import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
-import { FocusViewOptions } from '../views/nodes/FieldView';
-import { KeyValueBox } from '../views/nodes/KeyValueBox';
-import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
+import { DocumentView, DocumentViewInternal } from '../views/nodes/DocumentView';
+import { FocusViewOptions } from '../views/nodes/FocusViewOptions';
+import { OpenWhere } from '../views/nodes/OpenWhere';
import { PresBox } from '../views/nodes/trails';
-import { ScriptingGlobals } from './ScriptingGlobals';
-import { SelectionManager } from './SelectionManager';
+type childIterator = { viewSpec: Opt<Doc>; childDocView: Opt<DocumentView>; focused: boolean; contextPath: Doc[] };
export class DocumentManager {
+ // eslint-disable-next-line no-use-before-define
private static _instance: DocumentManager;
public static get Instance(): DocumentManager {
+ // eslint-disable-next-line no-return-assign
return this._instance || (this._instance = new this());
}
- //global holds all of the nodes (regardless of which collection they're in)
- @observable _documentViews = new Set<DocumentView>();
- @observable.shallow public CurrentlyLoading: Doc[] = [];
+ // global holds all of the nodes (regardless of which collection they're in)
+ @observable private _documentViews = new Set<DocumentView>();
@computed public get DocumentViews() {
- return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.Contains(view)));
+ return Array.from(this._documentViews).filter(view => (!view.ComponentView?.dontRegisterView?.() && !LightboxView.LightboxDoc) || LightboxView.Contains(view));
}
public AddDocumentView(dv: DocumentView) {
this._documentViews.add(dv);
@@ -38,10 +34,22 @@ export class DocumentManager {
this._documentViews.delete(dv);
}
- //private constructor so no other class can create a nodemanager
+ // private constructor so no other class can create a nodemanager
private constructor() {
makeObservable(this);
- observe(this.CurrentlyLoading, change => {
+
+ DocumentView.allViews = () => this.DocumentViews;
+ DocumentView.addView = this.AddView;
+ DocumentView.removeView = this.RemoveView;
+ DocumentView.showDocument = this.showDocument;
+ DocumentView.showDocumentView = this.showDocumentView;
+ DocumentView.linkCommonAncestor = DocumentManager.LinkCommonAncestor;
+ DocumentView.addViewRenderedCb = this.AddViewRenderedCb;
+ DocumentView.getFirstDocumentView = this.getFirstDocumentView;
+ DocumentView.getDocumentView = this.getDocumentView;
+ DocumentView.getContextPath = DocumentManager.GetContextPath;
+ DocumentView.getLightboxDocumentView = this.getLightboxDocumentView;
+ observe(Doc.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':
@@ -52,6 +60,7 @@ export class DocumentManager {
case 'splice':
(change as any).removed.forEach((doc: Doc) => DocumentManager.Instance.getAllDocumentViews(doc).forEach(dv => StrCast(dv.Document.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify())));
break;
+ default:
}
});
}
@@ -88,20 +97,15 @@ export class DocumentManager {
@action
public AddView = (view: DocumentView) => {
- if (!view._props.LayoutTemplateString?.includes(KeyValueBox.name) &&
- !view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
- this.AddDocumentView(view);
- this.callAddViewFuncs(view);
- } // prettier-ignore
+ this.AddDocumentView(view);
+ this.callAddViewFuncs(view);
};
public RemoveView = action((view: DocumentView) => {
- if (!view._props.LayoutTemplateString?.includes(KeyValueBox.name) && !view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
- this.DeleteDocumentView(view);
- }
- SelectionManager.DeselectView(view);
+ this.DeleteDocumentView(view);
+ DocumentView.DeselectView(view);
});
- //gets all views
+ // gets all views
public getDocumentViewsById(id: string) {
const toReturn: DocumentView[] = [];
DocumentManager.Instance.DocumentViews.forEach(view => {
@@ -135,25 +139,17 @@ export class DocumentManager {
);
}
- public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
+ public getLightboxDocumentView = (toFind: Doc): DocumentView | undefined => {
const views: DocumentView[] = [];
DocumentManager.Instance.DocumentViews.forEach(view => LightboxView.Contains(view) && Doc.AreProtosEqual(view.Document, toFind) && views.push(view));
- return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /*&& view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse*/) || (views.length ? views[0] : undefined);
+ return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /* && view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse */) || (views.length ? views[0] : undefined);
};
- public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
- if (LightboxView.LightboxDoc) return DocumentManager.Instance.getLightboxDocumentView(toFind, originatingDoc);
- const views = this.getDocumentViews(toFind); //.filter(view => view.Document !== originatingDoc);
- return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /*&& view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse*/) || (views.length ? views[0] : undefined);
+ public getFirstDocumentView = (toFind: Doc): DocumentView | undefined => {
+ if (LightboxView.LightboxDoc) return DocumentManager.Instance.getLightboxDocumentView(toFind);
+ const views = this.getDocumentViews(toFind); // .filter(view => view.Document !== originatingDoc);
+ return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /* && view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse */) || (views.length ? views[0] : undefined);
};
- public getDocumentViews(toFindIn: Doc): DocumentView[] {
- const toFind =
- // Array.from(DocumentManager.Instance.DocumentViews).find(
- // dv =>
- // ((dv.Document.data as any)?.url?.href && (dv.Document.data as any)?.url?.href === (toFindIn.data as any)?.url?.href) ||
- // ((DocCast(dv.Document.annotationOn)?.data as any)?.url?.href && (DocCast(dv.Document.annotationOn)?.data as any)?.url?.href === (DocCast(toFindIn.annotationOn)?.data as any)?.url?.href)
- // )?.Document ??
- toFindIn;
-
+ public getDocumentViews(toFind: Doc): DocumentView[] {
const toReturn: DocumentView[] = [];
const docViews = DocumentManager.Instance.DocumentViews.filter(view => !LightboxView.Contains(view));
const lightViews = DocumentManager.Instance.DocumentViews.filter(view => LightboxView.Contains(view));
@@ -172,7 +168,7 @@ export class DocumentManager {
static GetContextPath(doc: Opt<Doc>, includeExistingViews?: boolean) {
if (!doc) return [];
const srcContext = DocCast(doc.annotationOn, DocCast(doc.embedContainer));
- var containerDocContext = srcContext ? [srcContext, doc] : [doc];
+ let containerDocContext = srcContext ? [srcContext, doc] : [doc];
while (
containerDocContext.length &&
DocCast(containerDocContext[0]?.embedContainer) &&
@@ -184,11 +180,13 @@ export class DocumentManager {
return containerDocContext;
}
+ static _howl: Howl;
static playAudioAnno(doc: Doc) {
const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations'], listSpec(AudioField), null)?.lastElement();
if (anno) {
+ this._howl?.stop();
if (anno instanceof AudioField) {
- new Howl({
+ this._howl = new Howl({
src: [anno.url.href],
format: ['mp3'],
autoplay: true,
@@ -204,19 +202,20 @@ export class DocumentManager {
DocumentManager._overlayViews?.clear();
}
static _overlayViews = new ObservableSet<DocumentView>();
- static addView = (doc: Doc, finished?: () => void) => {
- CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
- finished?.();
- };
+ /**
+ * Find the nearest common ancestor collection that contains a link's source and target
+ * @param linkDoc
+ * @returns common ancestor DocumentView
+ */
public static LinkCommonAncestor(linkDoc: Doc) {
- const anchor = (which: number) => {
+ const getAnchor = (which: number) => {
const anch = DocCast(linkDoc['link_anchor_' + which]);
const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch;
return DocumentManager.Instance.getDocumentView(anchor);
};
- const anchor1 = anchor(1);
- const anchor2 = anchor(2);
+ const anchor1 = getAnchor(1);
+ const anchor2 = getAnchor(2);
return anchor1
?.docViewPath()
.reverse()
@@ -228,8 +227,10 @@ export class DocumentManager {
// focusing on each context
public showDocumentView = async (targetDocView: DocumentView, options: FocusViewOptions) => {
const docViewPath = [...(targetDocView.containerViewPath?.() ?? []), targetDocView];
- let rootContextView = docViewPath.shift();
- await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined, focused: false })));
+ const rootContextView = docViewPath.shift();
+ const iterator = () => ({ childDocView: docViewPath.shift(), viewSpec: undefined, focused: false, contextPath: docViewPath.map(dv => dv.Document) });
+ options.contextPath = docViewPath.map(dv => dv.Document);
+ await (rootContextView && this.focusViewsInPath(rootContextView, options, iterator));
if (options.toggleTarget && (!options.didMove || targetDocView.Document.hidden)) targetDocView.Document.hidden = !targetDocView.Document.hidden;
else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) DocumentViewInternal.addDocTabFunc(rootContextView.Document, options.openLocation);
};
@@ -242,27 +243,26 @@ export class DocumentManager {
// and finally restoring the targetDoc to the viewSpec specified by the last document which may either be the targetDoc, or a viewSpec that describes the targetDoc configuration
public showDocument = async (
targetDoc: Doc, // document to display
- options: FocusViewOptions, // options for how to navigate to target
+ optionsIn: FocusViewOptions, // options for how to navigate to target
finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done.
) => {
+ const options = optionsIn;
Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc);
const docContextPath = DocumentManager.GetContextPath(targetDoc, true);
if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false;
- const tabView = Array.from(TabDocView._allTabs).find(view => view._document === docContextPath[0]);
- if (!tabView?._activated && tabView?._document) {
- options.toggleTarget = false;
- TabDocView.Activate(tabView?._document);
- }
- let rootContextView =
+ if (DocumentView.activateTabView(docContextPath[0])) options.toggleTarget = false;
+
+ const rootContextView =
docContextPath.length &&
(await new Promise<DocumentView>(res => {
const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc));
if (viewIndex !== -1) {
viewIndex && docContextPath.splice(0, viewIndex);
- return res(this.getDocumentView(docContextPath[0])!);
+ res(this.getDocumentView(docContextPath[0])!);
+ return;
}
options.didMove = true;
- (!LightboxView.LightboxDoc && docContextPath.some(doc => TabDocView.Activate(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
+ (!LightboxView.LightboxDoc && docContextPath.some(doc => DocumentView.activateTabView(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
}));
if (options.openLocation === OpenWhere.lightbox) {
@@ -270,48 +270,73 @@ export class DocumentManager {
const target = DocCast(targetDoc.annotationOn, targetDoc);
const contextView = this.getDocumentView(DocCast(target.embedContainer));
if (contextView?.ComponentView?.addDocTab?.(target, OpenWhere.lightbox)) {
- await new Promise<void>(waitres => setTimeout(() => waitres()));
+ await new Promise<void>(waitres => {
+ setTimeout(() => waitres());
+ });
}
}
- docContextPath.shift();
- const childViewIterator = async (docView: DocumentView) => {
- const innerDoc = docContextPath.shift();
- return { focused: false, viewSpec: innerDoc, childDocView: innerDoc && !innerDoc.layout_unrendered ? (await docView.ComponentView?.getView?.(innerDoc, options)) ?? this.getDocumentView(innerDoc) : undefined };
- };
if (rootContextView) {
+ const childViewIterator = async (docView: DocumentView): Promise<childIterator> => {
+ const innerDoc = docContextPath.shift();
+ const childDocView = innerDoc && !innerDoc.layout_unrendered
+ ? (await docView.ComponentView?.getView?.(innerDoc, options)) ?? this.getDocumentView(innerDoc):
+ undefined; // prettier-ignore
+ return { focused: false, viewSpec: innerDoc, childDocView, contextPath: docContextPath };
+ };
+ docContextPath.shift();
+ options.contextPath = docContextPath;
const target = await this.focusViewsInPath(rootContextView, options, childViewIterator);
- this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc);
- finished?.(target.focused);
- } else finished?.(false);
+ if (target) {
+ this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc);
+ finished?.(target.focused);
+ return;
+ }
+ }
+ finished?.(false);
};
focusViewsInPath = async (
- docView: DocumentView, //
- options: FocusViewOptions,
- iterator: (docView: DocumentView) => Promise<{ viewSpec: Opt<Doc>; childDocView: Opt<DocumentView>; focused: boolean }>
+ docViewIn: DocumentView, //
+ optionsIn: FocusViewOptions,
+ iterator: (docView: DocumentView) => childIterator | Promise<childIterator>
) => {
let contextView: DocumentView | undefined; // view containing context that contains target
let focused = false;
- while (true) {
+ let docView = docViewIn;
+ let anchor = docView.Document;
+ const options = optionsIn;
+ const maxFocusLength = 100; // want to keep focusing until we get to target, but avoid an infinite loop
+ for (let i = 0; i < maxFocusLength; i++) {
if (docView.Document.layout_fieldKey === 'layout_icon') {
- await new Promise<void>(res => docView.iconify(res));
+ // eslint-disable-next-line no-loop-func
+ const prom = new Promise<void>(res => {
+ docView.iconify(res);
+ });
+ // eslint-disable-next-line no-await-in-loop
+ await prom;
options.didMove = true;
}
- const nextFocus = docView._props.focus(docView.Document, options); // focus the view within its container
- focused = focused || (nextFocus === undefined ? false : true); // keep track of whether focusing on a view needed to actually change anything
- const { childDocView, viewSpec } = await iterator(docView);
- if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.Document, docView, contextView, focused };
- contextView = options.anchorDoc?.layout_unrendered && !childDocView.Document.layout_unrendered ? childDocView : docView;
+ const nextFocus = docView._props.focus(anchor, options); // focus the view within its container
+ focused = focused || nextFocus !== undefined; // keep track of whether focusing on a view needed to actually change anything
+ // eslint-disable-next-line no-await-in-loop
+ const { childDocView, viewSpec, contextPath } = await iterator(docView);
+ if (!childDocView) return { viewSpec: viewSpec ?? docView.Document, docView, contextView, focused };
+ contextView = !childDocView.Document.layout_unrendered ? childDocView : docView;
docView = childDocView;
+ anchor = viewSpec ?? docView.Document;
+ options.contextPath = contextPath;
}
+ options.contextPath = undefined;
+ return undefined;
};
@action
- restoreDocView(viewSpec: Opt<Doc>, docView: DocumentView, options: FocusViewOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
+ restoreDocView(viewSpec: Opt<Doc>, docViewIn: DocumentView, options: FocusViewOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
+ const docView = docViewIn;
if (viewSpec && docView) {
- //if (docView.ComponentView instanceof FormattedTextBox)
- //viewSpec !== docView.Document &&
+ // if (docView.ComponentView instanceof FormattedTextBox)
+ // viewSpec !== docView.Document &&
docView.ComponentView?.focus?.(viewSpec, options);
PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500);
Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect);
@@ -320,7 +345,7 @@ export class DocumentManager {
if (options.toggleTarget && (!options.didMove || docView.Document.hidden)) docView.Document.hidden = !docView.Document.hidden;
if (options.effect) docView.Document[Animation] = options.effect;
- if (options.zoomTextSelections && Doc.UnhighlightTimer && contextView && targetDoc.text_html) {
+ if (options.zoomTextSelections && Doc.IsUnhighlightTimerSet() && contextView && targetDoc.text_html) {
// if the docView is a text anchor, the contextView is the PDF/Web/Text doc
contextView.setTextHtmlOverlay(StrCast(targetDoc.text_html), options.effect);
DocumentManager._overlayViews.add(contextView);
@@ -332,30 +357,4 @@ export class DocumentManager {
}
}
}
-export function DocFocusOrOpen(doc: Doc, options: FocusViewOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) {
- const func = () => {
- const cv = DocumentManager.Instance.getDocumentView(containingDoc);
- const dv = DocumentManager.Instance.getDocumentView(doc, cv);
- if (dv && (!containingDoc || dv.containerViewPath?.().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;
- options.toggleTarget = undefined;
- DocumentManager.Instance.showDocument(showDoc, options, () => DocumentManager.Instance.showDocument(doc, { ...options, openLocation: undefined })).then(() => {
- const cv = DocumentManager.Instance.getDocumentView(containingDoc);
- const dv = DocumentManager.Instance.getDocumentView(doc, cv);
- dv && Doc.linkFollowHighlight(dv.Document);
- });
- }
- };
- if (Doc.IsDataProto(doc) && Doc.GetEmbeddings(doc).some(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))) {
- doc = Doc.GetEmbeddings(doc).find(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))!;
- }
- if (doc.hidden) {
- doc.hidden = false;
- options.toggleTarget = false;
- setTimeout(func);
- } else func();
-}
-ScriptingGlobals.add(DocFocusOrOpen);
+setTimeout(() => DocumentManager.Instance);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 9627c5df2..fda505420 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,3 +1,5 @@
+/* eslint-disable import/no-mutable-exports */
+/* eslint-disable no-use-before-define */
/**
* The DragManager handles all dragging interactions that occur entirely within Dash (as opposed to external drag operations from the file system, etc)
*
@@ -13,32 +15,22 @@
*/
import { action, observable, runInAction } from 'mobx';
+import { ClientUtils } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
import { DateField } from '../../fields/DateField';
-import { Doc, Field, Opt, StrListCast } from '../../fields/Doc';
+import { CreateLinkToActiveAudio, Doc, FieldType, Opt, StrListCast } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
import { PrefetchProxy } from '../../fields/Proxy';
import { ScriptField } from '../../fields/ScriptField';
import { ScriptCast } from '../../fields/Types';
-import { emptyFunction, Utils } from '../../Utils';
-import { Docs, DocUtils } from '../documents/Documents';
-import { CollectionFreeFormDocumentView } from '../views/nodes/CollectionFreeFormDocumentView';
+import { Docs } from '../documents/Documents';
import { DocumentView } from '../views/nodes/DocumentView';
-import { ScriptingGlobals } from './ScriptingGlobals';
-import { SelectionManager } from './SelectionManager';
+import { dropActionType } from './DropActionTypes';
import { SnappingManager } from './SnappingManager';
import { UndoManager } from './UndoManager';
-import { DocData } from '../../fields/DocSymbols';
-const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
-export enum dropActionType {
- embed = 'embed', // create a new embedding of the dragged document for the new location
- copy = 'copy', // copy the dragged document
- move = 'move', // move the dragged document to the drop location after removing it from where it was
- add = 'add', // add the dragged document to the drop location without removing it from where it was
- same = 'same', // only allow drop within same collection (or same hierarchical tree collection)
- inPlace = 'inSame', // keep document in place (unless overridden by a drag modifier)
- proto = 'proto',
-} // undefined = move, same = move but doesn't call dropPropertiesToRemove
+const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
/**
* Initialize drag
@@ -79,10 +71,14 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
}
export namespace DragManager {
+ export const dragClassName = 'collectionFreeFormDocumentView-container';
let dragDiv: HTMLDivElement;
let dragLabel: HTMLDivElement;
export let StartWindowDrag: Opt<(e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => boolean>;
export let CompleteWindowDrag: Opt<(aborted: boolean) => void>;
+ export let AbortDrag: () => void = emptyFunction;
+ export const docsBeingDragged: Doc[] = observable([]);
+ export let DocDragData: DocumentDragData | undefined;
export function Root() {
const root = document.getElementById('root');
@@ -91,7 +87,6 @@ export namespace DragManager {
}
return root;
}
- export let AbortDrag: () => void = emptyFunction;
export type MoveFunction = (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
export type RemoveFunction = (document: Doc | Doc[]) => boolean;
@@ -106,6 +101,7 @@ export namespace DragManager {
// event called when the drag operation results in a drop action
export class DropEvent {
+ // eslint-disable-next-line no-useless-constructor
constructor(
readonly x: number,
readonly y: number,
@@ -115,7 +111,9 @@ export namespace DragManager {
readonly metaKey: boolean,
readonly ctrlKey: boolean,
readonly embedKey: boolean
- ) {}
+ ) {
+ /* empty */
+ }
}
// event called when the drag operation has completed (aborted or completed a drop) -- this will be after any drop event has been generated
@@ -139,11 +137,11 @@ export namespace DragManager {
constructor(dragDoc: Doc[], dropAction?: dropActionType) {
this.draggedDocuments = dragDoc;
this.droppedDocuments = [];
- this.draggedViews = [];
this.offset = [0, 0];
this.dropAction = dropAction;
}
- draggedViews: DocumentView[];
+ dragEnding?: () => void;
+ dragStarting?: () => void;
draggedDocuments: Doc[];
droppedDocuments: Doc[];
treeViewDoc?: Doc;
@@ -157,6 +155,7 @@ export namespace DragManager {
removeDocument?: RemoveFunction;
isDocDecorationMove?: boolean; // Flags that Document decorations are used to drag document which allows suppression of onDragStart scripts
}
+ Doc.SetDocDragDataName(DocumentDragData.name);
export class LinkDragData {
constructor(dragView: DocumentView, linkSourceGetAnchor: () => Doc) {
this.linkDragView = dragView;
@@ -194,7 +193,7 @@ export namespace DragManager {
userDropAction?: dropActionType;
}
- let defaultPreDropFunc = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
+ const defaultPreDropFunc = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
if (de.complete.docDragData) {
targetAction && (de.complete.docDragData.dropAction = targetAction);
e.stopPropagation();
@@ -224,12 +223,12 @@ export namespace DragManager {
export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, onDropCompleted?: (e?: DragCompleteEvent) => any) {
const addAudioTag = (dropDoc: any) => {
dropDoc && !dropDoc.author_date && (dropDoc.author_date = new DateField());
- dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc);
+ dropDoc instanceof Doc && CreateLinkToActiveAudio(() => dropDoc);
return dropDoc;
};
const finishDrag = async (e: DragCompleteEvent) => {
- const docDragData = e.docDragData;
- setTimeout(() => dragData.draggedViews.forEach(view => view.props.dragEnding?.()));
+ const { docDragData } = e;
+ setTimeout(() => dragData.dragEnding?.());
onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails
if (docDragData && !docDragData.droppedDocuments.length) {
docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
@@ -256,27 +255,32 @@ export namespace DragManager {
.forEach((drop: Doc, i: number) => {
const dragProps = StrListCast(dragData.draggedDocuments[i].dropPropertiesToRemove);
const remProps = (dragData?.dropPropertiesToRemove || []).concat(Array.from(dragProps));
- [...remProps, 'dropPropertiesToRemove'].map(prop => (drop[prop] = undefined));
+ [...remProps, 'dropPropertiesToRemove'].forEach(prop => {
+ drop[prop] = undefined;
+ });
});
}
return e;
};
dragData.draggedDocuments.map(d => d.dragFactory); // does this help? trying to make sure the dragFactory Doc is loaded
StartDrag(eles, dragData, downX, downY, options, finishDrag);
- dragData.draggedViews.forEach(view => view.props.dragStarting?.());
+ dragData.dragStarting?.();
return true;
}
// drag a button template and drop a new button
- export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
+ export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: FieldType }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
const finishDrag = (e: DragCompleteEvent) => {
const bd = Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) });
- params.map(p => Object.keys(vars).indexOf(p) !== -1 && (bd[DocData][p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields
+ params.forEach(p => {
+ Object.keys(vars).indexOf(p) !== -1 && (bd[DocData][p] = new PrefetchProxy(vars[p] as Doc));
+ }); // copy all "captured" arguments into document parameterfields
initialize?.(bd);
bd[DocData]['onClick-paramFieldKeys'] = new List<string>(params);
e.docDragData && (e.docDragData.droppedDocuments = [bd]);
return e;
};
+ // eslint-disable-next-line no-param-reassign
options = options ?? {};
options.noAutoscroll = true; // these buttons are being dragged on the overlay layer, so scrollin the underlay is not appropriate
StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag);
@@ -298,7 +302,7 @@ export namespace DragManager {
}
export function snapDragAspect(dragPt: number[], snapAspect: number) {
- let closest = Utils.SNAP_THRESHOLD;
+ let closest = ClientUtils.SNAP_THRESHOLD;
let near = dragPt;
const intersect = (x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, dragx: number, dragy: number) => {
if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) return undefined; // Check if none of the lines are of length 0
@@ -307,7 +311,7 @@ export namespace DragManager {
const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;
// let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator;
- //if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return undefined; // is the intersection along the segments
+ // if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return undefined; // is the intersection along the segments
// Return a object with the x and y coordinates of the intersection
const x = x1 + ua * (x2 - x1);
@@ -315,14 +319,14 @@ export namespace DragManager {
const dist = Math.sqrt((dragx - x) * (dragx - x) + (dragy - y) * (dragy - y));
return { pt: [x, y], dist };
};
- SnappingManager.VertSnapLines.forEach((xCoord, i) => {
+ SnappingManager.VertSnapLines.forEach(xCoord => {
const pt = intersect(dragPt[0], dragPt[1], dragPt[0] + snapAspect, dragPt[1] + 1, xCoord, -1, xCoord, 1, dragPt[0], dragPt[1]);
if (pt && pt.dist < closest) {
closest = pt.dist;
near = pt.pt;
}
});
- SnappingManager.HorizSnapLines.forEach((yCoord, i) => {
+ SnappingManager.HorizSnapLines.forEach(yCoord => {
const pt = intersect(dragPt[0], dragPt[1], dragPt[0] + snapAspect, dragPt[1] + 1, -1, yCoord, 1, yCoord, dragPt[0], dragPt[1]);
if (pt && pt.dist < closest) {
closest = pt.dist;
@@ -333,7 +337,7 @@ export namespace DragManager {
}
// snap to the active snap lines - if oneAxis is set (eg, for maintaining aspect ratios), then it only snaps to the nearest horizontal/vertical line
export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) {
- const snapThreshold = Utils.SNAP_THRESHOLD;
+ const snapThreshold = ClientUtils.SNAP_THRESHOLD;
const snapVal = (pts: number[], drag: number, snapLines: number[]) => {
if (snapLines.length) {
const offs = [pts[0], (pts[0] - pts[1]) / 2, -pts[1]]; // offsets from drag pt
@@ -350,14 +354,33 @@ export namespace DragManager {
y: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.HorizSnapLines),
};
}
- export let docsBeingDragged: Doc[] = observable([]);
- export let CanEmbed = false;
- export let DocDragData: DocumentDragData | undefined;
- export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void, dragUndoName?: string) {
+
+ async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) {
+ const dropArgs = {
+ cancelable: true, // allows preventDefault() to be called to cancel the drop
+ bubbles: true,
+ detail: {
+ ...pos,
+ complete,
+ shiftKey: e.shiftKey,
+ altKey: e.altKey,
+ metaKey: e.metaKey,
+ ctrlKey: e.ctrlKey,
+ embedKey: SnappingManager.CanEmbed,
+ },
+ };
+ target.dispatchEvent(new CustomEvent<DropEvent>('dashPreDrop', dropArgs));
+ UndoManager.StartTempBatch(); // run drag/drop in temp batch in case drop is not allowed (so we can undo any intermediate changes)
+ await finishDrag?.(complete);
+ UndoManager.EndTempBatch(target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs))); // event return val is true unless the event preventDefault() is called
+ options?.dragComplete?.(complete);
+ endDrag?.();
+ }
+ export function StartDrag(elesIn: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void, dragUndoName?: string) {
if (dragData.dropAction === 'none' || SnappingManager.ExploreMode) return;
DocDragData = dragData as DocumentDragData;
const batch = UndoManager.StartBatch(dragUndoName ?? 'document drag');
- eles = eles.filter(e => e);
+ const eles = elesIn.filter(e => e);
SnappingManager.SetCanEmbed(dragData.canEmbed || false);
if (!dragDiv) {
dragDiv = document.createElement('div');
@@ -375,9 +398,9 @@ export namespace DragManager {
}
Object.assign(dragDiv.style, { width: '', height: '', overflow: '' });
dragDiv.hidden = false;
- const scalings: number[] = [],
- xs: number[] = [],
- ys: number[] = [];
+ const scalings: number[] = [];
+ const xs: number[] = [];
+ const ys: number[] = [];
const elesCont = {
left: Number.MAX_SAFE_INTEGER,
@@ -385,7 +408,7 @@ export namespace DragManager {
top: Number.MAX_SAFE_INTEGER,
bottom: Number.MIN_SAFE_INTEGER,
};
- let rot: number[] = [];
+ const rot: number[] = [];
const docsToDrag = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : [];
const dragElements = eles.map(ele => {
// bcz: very hacky -- if dragged element is a freeForm view with a rotation, then extract the rotation in order to apply it to the dragged element
@@ -394,7 +417,7 @@ export namespace DragManager {
// if the parent isn't a freeform view, then the element's width and height are presumed to match the acutal doc's dimensions (eg, dragging from import sidebar menu)
let rotation: number | undefined;
for (let parEle: HTMLElement | null | undefined = ele.parentElement; parEle; parEle = parEle?.parentElement) {
- if (parEle.className === CollectionFreeFormDocumentView.CollectionFreeFormDocViewClassName) {
+ if (parEle.className === DragManager.dragClassName) {
rotation = (rotation ?? 0) + Number(parEle.style.transform.replace(/.*rotate\(([-0-9.e]*)deg\).*/, '$1') || 0);
}
parEle = parEle.parentElement;
@@ -471,16 +494,20 @@ export namespace DragManager {
.filter(pb => pb.width && pb.height)
.map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0));
}
- [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele => (ele as any).style && ((ele as any).style.pointerEvents = 'none'));
+ [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))]
+ .map(dele => (dele as any).style)
+ .forEach(style => {
+ style && (style.pointerEvents = 'none');
+ });
dragDiv.appendChild(dragElement);
if (dragElement !== ele) {
- const children = [Array.from(ele.children), Array.from(dragElement.children)];
- while (children[0].length) {
- const childs = [children[0].pop(), children[1].pop()];
+ const dragChildren = [Array.from(ele.children), Array.from(dragElement.children)];
+ while (dragChildren[0].length) {
+ const childs = [dragChildren[0].pop(), dragChildren[1].pop()];
if (childs[0]?.children) {
- children[0].push(...Array.from(childs[0].children));
- children[1].push(...Array.from(childs[1]!.children));
+ dragChildren[0].push(...Array.from(childs[0].children));
+ dragChildren[1].push(...Array.from(childs[1]!.children));
}
if (childs[0]?.scrollTop) childs[1]!.scrollTop = childs[0].scrollTop;
}
@@ -493,7 +520,11 @@ export namespace DragManager {
const hideDragShowOriginalElements = (hide: boolean) => {
dragLabel.style.display = hide && !SnappingManager.CanEmbed ? '' : 'none';
!hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
- setTimeout(() => eles.forEach(ele => (ele.hidden = hide)));
+ setTimeout(() =>
+ eles.forEach(ele => {
+ ele.hidden = hide;
+ })
+ );
};
options?.hideSource && hideDragShowOriginalElements(true);
@@ -505,22 +536,7 @@ export namespace DragManager {
const yFromBottom = elesCont.bottom - downY;
let scrollAwaiter: Opt<NodeJS.Timeout>;
- AbortDrag = () => {
- options?.dragComplete?.(new DragCompleteEvent(true, dragData));
- cleanupDrag(true);
- };
-
- const cleanupDrag = action((undo: boolean) => {
- (dragData as DocumentDragData).draggedViews?.forEach(view => view.props.dragEnding?.());
- hideDragShowOriginalElements(false);
- document.removeEventListener('pointermove', moveHandler, true);
- document.removeEventListener('pointerup', upHandler, true);
- SnappingManager.SetIsDragging(false);
- if (batch.end() && undo) UndoManager.Undo();
- docsBeingDragged.length = 0;
- SnappingManager.SetCanEmbed(false);
- });
- var startWindowDragTimer: any;
+ let startWindowDragTimer: any;
const moveHandler = (e: PointerEvent) => {
e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop
if (dragData instanceof DocumentDragData) {
@@ -580,10 +596,10 @@ export namespace DragManager {
defaultPrevented: true,
eventPhase: e.eventPhase,
isTrusted: true,
- preventDefault: () => ('not implemented for this event' ? false : false),
- isDefaultPrevented: () => ('not implemented for this event' ? false : false),
- stopPropagation: () => ('not implemented for this event' ? false : false),
- isPropagationStopped: () => ('not implemented for this event' ? false : false),
+ preventDefault: () => 'not implemented for this event' && false,
+ isDefaultPrevented: () => 'not implemented for this event' && false,
+ stopPropagation: () => 'not implemented for this event' && false,
+ isPropagationStopped: () => 'not implemented for this event' && false,
persist: emptyFunction,
timeStamp: e.timeStamp,
type: 'dashDragMovePause',
@@ -602,7 +618,9 @@ export namespace DragManager {
const moveVec = { x: x - lastPt.x, y: y - lastPt.y };
lastPt = { x, y };
- dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px) rotate(${rot[i]}deg) scale(${scalings[i]})`));
+ dragElements.forEach((dragElement, i) => {
+ dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px) rotate(${rot[i]}deg) scale(${scalings[i]})`;
+ });
dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`;
};
const upHandler = (e: PointerEvent) => {
@@ -610,36 +628,21 @@ export namespace DragManager {
startWindowDragTimer = undefined;
dispatchDrag(document.elementFromPoint(e.x, e.y) || document.body, e, new DragCompleteEvent(false, dragData), snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom), finishDrag, options, () => cleanupDrag(false));
};
+ const cleanupDrag = action((undo: boolean) => {
+ (dragData as DocumentDragData).dragEnding?.();
+ hideDragShowOriginalElements(false);
+ document.removeEventListener('pointermove', moveHandler, true);
+ document.removeEventListener('pointerup', upHandler, true);
+ SnappingManager.SetIsDragging(false);
+ if (batch.end() && undo) UndoManager.Undo();
+ docsBeingDragged.length = 0;
+ SnappingManager.SetCanEmbed(false);
+ });
+ AbortDrag = () => {
+ options?.dragComplete?.(new DragCompleteEvent(true, dragData));
+ cleanupDrag(true);
+ };
document.addEventListener('pointermove', moveHandler, true);
document.addEventListener('pointerup', upHandler, true);
}
-
- async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) {
- const dropArgs = {
- cancelable: true, // allows preventDefault() to be called to cancel the drop
- bubbles: true,
- detail: {
- ...pos,
- complete,
- shiftKey: e.shiftKey,
- altKey: e.altKey,
- metaKey: e.metaKey,
- ctrlKey: e.ctrlKey,
- embedKey: SnappingManager.CanEmbed,
- },
- };
- target.dispatchEvent(new CustomEvent<DropEvent>('dashPreDrop', dropArgs));
- UndoManager.StartTempBatch(); // run drag/drop in temp batch in case drop is not allowed (so we can undo any intermediate changes)
- await finishDrag?.(complete);
- UndoManager.EndTempBatch(target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs))); // event return val is true unless the event preventDefault() is called
- options?.dragComplete?.(complete);
- endDrag?.();
- }
}
-
-ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) {
- if (readOnly) {
- return SelectionManager.Views.some(dv => dv.Document.keepZWhenDragged);
- }
- SelectionManager.Views.map(dv => (dv.Document.keepZWhenDragged = !dv.Document.keepZWhenDragged));
-});
diff --git a/src/client/util/DropActionTypes.ts b/src/client/util/DropActionTypes.ts
new file mode 100644
index 000000000..45b294d97
--- /dev/null
+++ b/src/client/util/DropActionTypes.ts
@@ -0,0 +1,9 @@
+export enum dropActionType {
+ embed = 'embed', // create a new embedding of the dragged document for the new location
+ copy = 'copy', // copy the dragged document
+ move = 'move', // move the dragged document to the drop location after removing it from where it was
+ add = 'add', // add the dragged document to the drop location without removing it from where it was
+ same = 'same', // only allow drop within same collection (or same hierarchical tree collection)
+ inPlace = 'inSame', // keep document in place (unless overridden by a drag modifier)
+ proto = 'proto',
+} // undefined = move, same = move but doesn't call dropPropertiesToRemove
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index ed5749d06..0314af06b 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -1,10 +1,9 @@
-import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, StrListCast } from '../../fields/Doc';
import { DocData } from '../../fields/DocSymbols';
import { ObjectField } from '../../fields/ObjectField';
import { RichTextField } from '../../fields/RichTextField';
-import { listSpec } from '../../fields/Schema';
import { ComputedField, ScriptField } from '../../fields/ScriptField';
-import { Cast, StrCast } from '../../fields/Types';
+import { StrCast } from '../../fields/Types';
import { ImageField } from '../../fields/URLField';
import { Docs } from '../documents/Documents';
import { DocumentType } from '../documents/DocumentTypes';
@@ -13,21 +12,6 @@ import { DragManager } from './DragManager';
import { ScriptingGlobals } from './ScriptingGlobals';
/**
- * Converts a Doc to a render template that can be applied to other Docs to customize how they render while
- * still using the other Doc as the backing data store (ie, dataDoc). During rendering, if a layout Doc is provided
- * with 'isTemplateDoc' set, then the layout Doc is treated as a template for the rendered Doc. The template Doc is
- * "expanded" to create an template instance for the rendered Doc.
- *
- *
- * @param doc the doc to convert to a template
- * @returns 'doc'
- */
-export function MakeTemplate(doc: Doc) {
- doc.isTemplateDoc = makeTemplate(doc, true);
- return doc;
-}
-
-/**
*
* Recursively converts 'doc' into a template that can be used to render other documents.
*
@@ -63,26 +47,22 @@ function makeTemplate(doc: Doc, first: boolean = true): boolean {
}
return isTemplate;
}
-export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
- data?.draggedDocuments.map((doc, i) => {
- let dbox = doc;
- // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant
- if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes(FontIconBox.name)) {
- if (data.dropPropertiesToRemove || dbox.dropPropertiesToRemove) {
- //dbox = Doc.MakeEmbedding(doc); // don't need to do anything if dropping an icon doc onto an icon bar since there should be no layout data for an icon
- dbox = Doc.MakeEmbedding(dbox);
- const dragProps = Cast(dbox.dropPropertiesToRemove, listSpec('string'), []);
- const remProps = (data.dropPropertiesToRemove || []).concat(Array.from(dragProps));
- remProps.map(prop => (dbox[prop] = undefined));
- }
- } else if (!doc.onDragStart && !doc.isButtonBar) {
- dbox = makeUserTemplateButton(doc);
- } else if (doc.isButtonBar) {
- dbox.ignoreClick = true;
- }
- data.droppedDocuments[i] = dbox;
- });
+
+/**
+ * Converts a Doc to a render template that can be applied to other Docs to customize how they render while
+ * still using the other Doc as the backing data store (ie, dataDoc). During rendering, if a layout Doc is provided
+ * with 'isTemplateDoc' set, then the layout Doc is treated as a template for the rendered Doc. The template Doc is
+ * "expanded" to create an template instance for the rendered Doc.
+ *
+ *
+ * @param doc the doc to convert to a template
+ * @returns 'doc'
+ */
+export function MakeTemplate(doc: Doc) {
+ doc.isTemplateDoc = makeTemplate(doc, true);
+ return doc;
}
+
export function makeUserTemplateButton(doc: Doc) {
const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
if (layoutDoc.type !== DocumentType.FONTICON) {
@@ -106,7 +86,30 @@ export function makeUserTemplateButton(doc: Doc) {
dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory)');
return dbox;
}
+export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
+ data?.draggedDocuments.forEach((doc, i) => {
+ let dbox = doc;
+ // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant
+ if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes(FontIconBox.name)) {
+ if (data.dropPropertiesToRemove || dbox.dropPropertiesToRemove) {
+ // dbox = Doc.MakeEmbedding(doc); // don't need to do anything if dropping an icon doc onto an icon bar since there should be no layout data for an icon
+ dbox = Doc.MakeEmbedding(dbox);
+ const dragProps = StrListCast(dbox.dropPropertiesToRemove);
+ const remProps = (data.dropPropertiesToRemove || []).concat(Array.from(dragProps));
+ remProps.forEach(prop => {
+ dbox[prop] = undefined;
+ });
+ }
+ } else if (!doc.onDragStart && !doc.isButtonBar) {
+ dbox = makeUserTemplateButton(doc);
+ } else if (doc.isButtonBar) {
+ dbox.ignoreClick = true;
+ }
+ data.droppedDocuments[i] = dbox;
+ });
+}
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function convertToButtons(dragData: any) {
convertDropDataToButtons(dragData as DragManager.DocumentDragData);
},
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index f4f879208..5701a22c0 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, IconButton, Size, Type } from 'browndash-components';
import { action, computed, makeObservable, observable } from 'mobx';
@@ -5,19 +7,20 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import Select from 'react-select';
import * as RequestPromise from 'request-promise';
+import { ClientUtils } from '../../ClientUtils';
+import { Utils } from '../../Utils';
import { DateField } from '../../fields/DateField';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { listSpec } from '../../fields/Schema';
import { Cast, StrCast } from '../../fields/Types';
-import { Utils } from '../../Utils';
import { MainViewModal } from '../views/MainViewModal';
+import { ObservableReactComponent } from '../views/ObservableReactComponent';
import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
import './GroupManager.scss';
import { GroupMemberView } from './GroupMemberView';
-import { SettingsManager } from './SettingsManager';
import { SharingManager, User } from './SharingManager';
-import { ObservableReactComponent } from '../views/ObservableReactComponent';
+import { SnappingManager } from './SnappingManager';
/**
* Interface for options for the react-select component
@@ -29,6 +32,7 @@ export interface UserOptions {
@observer
export class GroupManager extends ObservableReactComponent<{}> {
+ // eslint-disable-next-line no-use-before-define
static Instance: GroupManager;
@observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not.
@observable private users: string[] = []; // list of users populated from the database.
@@ -55,7 +59,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
*/
populateUsers = async () => {
if (Doc.UserDoc()[Id] !== Utils.GuestID()) {
- const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
+ const userList = await RequestPromise.get(ClientUtils.prepend('/getUsers'));
const raw = JSON.parse(userList) as User[];
raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email)));
}
@@ -73,7 +77,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
*/
@action
open = () => {
- // SelectionManager.DeselectAll();
+ // DocumentView.DeselectAll();
this.isOpen = true;
this.populateUsers();
};
@@ -135,7 +139,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
hasEditAccess(groupDoc: Doc): boolean {
if (!groupDoc) return false;
const accessList: string[] = JSON.parse(StrCast(groupDoc.owners));
- return accessList.includes(Doc.CurrentUserEmail) || this.adminGroupMembers?.includes(Doc.CurrentUserEmail);
+ return accessList.includes(ClientUtils.CurrentUserEmail()) || this.adminGroupMembers?.includes(ClientUtils.CurrentUserEmail());
}
/**
@@ -147,7 +151,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
const name = groupName.toLowerCase() === 'admin' ? 'Admin' : groupName;
const groupDoc = new Doc('GROUP:' + name, true);
groupDoc.title = name;
- groupDoc.owners = JSON.stringify([Doc.CurrentUserEmail]);
+ groupDoc.owners = JSON.stringify([ClientUtils.CurrentUserEmail()]);
groupDoc.members = JSON.stringify(memberEmails);
this.addGroup(groupDoc);
}
@@ -159,7 +163,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
addGroup(groupDoc: Doc): boolean {
if (this.GroupManagerDoc) {
Doc.AddDocToList(this.GroupManagerDoc, 'data', groupDoc);
- this.GroupManagerDoc['data_modificationDate'] = new DateField();
+ this.GroupManagerDoc.data_modificationDate = new DateField();
return true;
}
return false;
@@ -176,11 +180,11 @@ export class GroupManager extends ObservableReactComponent<{}> {
Doc.RemoveDocFromList(this.GroupManagerDoc, 'data', group);
SharingManager.Instance.removeGroup(group);
const members = JSON.parse(StrCast(group.members));
- if (members.includes(Doc.CurrentUserEmail)) {
+ if (members.includes(ClientUtils.CurrentUserEmail())) {
const index = DocListCast(this.GroupManagerDoc.data).findIndex(grp => grp === group);
index !== -1 && Cast(this.GroupManagerDoc.data, listSpec(Doc), [])?.splice(index, 1);
}
- this.GroupManagerDoc['data_modificationDate'] = new DateField();
+ this.GroupManagerDoc.data_modificationDate = new DateField();
if (group === this.currentGroup) {
this.currentGroup = undefined;
}
@@ -201,7 +205,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
!memberList.includes(email) && memberList.push(email);
groupDoc.members = JSON.stringify(memberList);
SharingManager.Instance.shareWithAddedMember(groupDoc, email);
- this.GroupManagerDoc && (this.GroupManagerDoc['data_modificationDate'] = new DateField());
+ this.GroupManagerDoc && (this.GroupManagerDoc.data_modificationDate = new DateField());
}
}
@@ -215,10 +219,9 @@ export class GroupManager extends ObservableReactComponent<{}> {
const memberList = JSON.parse(StrCast(groupDoc.members));
const index = memberList.indexOf(email);
if (index !== -1) {
- const user = memberList.splice(index, 1)[0];
groupDoc.members = JSON.stringify(memberList);
SharingManager.Instance.removeMember(groupDoc, email);
- this.GroupManagerDoc && (this.GroupManagerDoc['data_modificationDate'] = new DateField());
+ this.GroupManagerDoc && (this.GroupManagerDoc.data_modificationDate = new DateField());
}
}
}
@@ -260,7 +263,10 @@ export class GroupManager extends ObservableReactComponent<{}> {
alert('Please select a unique group name');
return;
}
- this.createGroupDoc(value, this.selectedUsers?.map(user => user.value));
+ this.createGroupDoc(
+ value,
+ this.selectedUsers?.map(user => user.value)
+ );
this.selectedUsers = null;
this.inputRef.current!.value = '';
this.buttonColour = '#979797';
@@ -271,7 +277,9 @@ export class GroupManager extends ObservableReactComponent<{}> {
TaskCompletionBox.textDisplayed = 'Group created!';
TaskCompletionBox.taskCompleted = true;
setTimeout(
- action(() => (TaskCompletionBox.taskCompleted = false)),
+ action(() => {
+ TaskCompletionBox.taskCompleted = false;
+ }),
2000
);
};
@@ -281,14 +289,14 @@ export class GroupManager extends ObservableReactComponent<{}> {
*/
private get groupCreationModal() {
const contents = (
- <div className="group-create" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
+ <div className="group-create" style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor }}>
<div className="group-heading" style={{ marginBottom: 0 }}>
<p>
<b>New Group</b>
</p>
<div className="close-button">
<Button
- icon={<FontAwesomeIcon icon={'times'} size={'lg'} />}
+ icon={<FontAwesomeIcon icon="times" size="lg" />}
onClick={action(() => {
this.createGroupModalOpen = false;
TaskCompletionBox.taskCompleted = false;
@@ -298,7 +306,17 @@ export class GroupManager extends ObservableReactComponent<{}> {
</div>
</div>
<div className="group-input" style={{ border: StrCast(Doc.UserDoc().userColor) }}>
- <input ref={this.inputRef} onKeyDown={this.handleKeyDown} autoFocus type="text" placeholder="Group name" onChange={action(() => (this.buttonColour = this.inputRef.current?.value ? 'black' : '#979797'))} />
+ <input
+ ref={this.inputRef}
+ onKeyDown={this.handleKeyDown}
+ // eslint-disable-next-line jsx-a11y/no-autofocus
+ autoFocus
+ type="text"
+ placeholder="Group name"
+ onChange={action(() => {
+ this.buttonColour = this.inputRef.current?.value ? 'black' : '#979797';
+ })}
+ />
</div>
<div style={{ border: StrCast(Doc.UserDoc().userColor) }}>
<Select
@@ -306,7 +324,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
isMulti
options={this.options}
onChange={this.handleChange}
- placeholder={'Select users'}
+ placeholder="Select users"
value={this.selectedUsers}
closeMenuOnSelect={false}
styles={{
@@ -331,8 +349,8 @@ export class GroupManager extends ObservableReactComponent<{}> {
}}
/>
</div>
- <div className={'create-button'}>
- <Button text={'Create'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={this.createGroup} />
+ <div className="create-button">
+ <Button text="Create" type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={this.createGroup} />
</div>
</div>
);
@@ -340,7 +358,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
return (
<MainViewModal
isDisplayed={this.createGroupModalOpen}
- interactive={true}
+ interactive
contents={contents}
dialogueBoxStyle={{ width: '90%', height: '70%' }}
closeOnExternalClick={action(() => {
@@ -366,30 +384,61 @@ export class GroupManager extends ObservableReactComponent<{}> {
const groups = this.groupSort === 'ascending' ? this.allGroups.sort(sortGroups) : this.groupSort === 'descending' ? this.allGroups.sort(sortGroups).reverse() : this.allGroups;
return (
- <div className="group-interface" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
+ <div className="group-interface" style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor }}>
{this.groupCreationModal}
- {this.currentGroup ? <GroupMemberView group={this.currentGroup} onCloseButtonClick={action(() => (this.currentGroup = undefined))} /> : null}
+ {this.currentGroup ? (
+ <GroupMemberView
+ group={this.currentGroup}
+ onCloseButtonClick={action(() => {
+ this.currentGroup = undefined;
+ })}
+ />
+ ) : null}
<div className="group-heading">
<p>
<b>Manage Groups</b>
</p>
- <Button icon={<FontAwesomeIcon icon={'plus'} />} iconPlacement={'left'} text={'Create Group'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => (this.createGroupModalOpen = true))} />
- <div className={'close-button'}>
- <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={StrCast(Doc.UserDoc().userColor)} />
+ <Button
+ icon={<FontAwesomeIcon icon="plus" />}
+ iconPlacement="left"
+ text="Create Group"
+ type={Type.TERT}
+ color={StrCast(Doc.UserDoc().userColor)}
+ onClick={action(() => {
+ this.createGroupModalOpen = true;
+ })}
+ />
+ <div className="close-button">
+ <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={this.close} color={StrCast(Doc.UserDoc().userColor)} />
</div>
</div>
<div className="main-container">
- <div className="sort-groups" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}>
+ <div
+ className="sort-groups"
+ onClick={action(() => {
+ this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending';
+ })}>
Name
<IconButton icon={<FontAwesomeIcon icon={this.groupSort === 'ascending' ? 'caret-up' : this.groupSort === 'descending' ? 'caret-down' : 'caret-right'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} />
</div>
- <div className={'style-divider'} style={{ background: StrCast(Doc.UserDoc().userColor) }} />
+ <div className="style-divider" style={{ background: StrCast(Doc.UserDoc().userColor) }} />
<div className="group-body" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor) }}>
{groups.map(group => (
<div className="group-row" key={StrCast(group.title || group.groupName)}>
<div className="group-name">{StrCast(group.title || group.groupName)}</div>
- <div className="group-info" onClick={action(() => (this.currentGroup = group))}>
- <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => (this.currentGroup = group))} />
+ <div
+ className="group-info"
+ onClick={action(() => {
+ this.currentGroup = group;
+ })}>
+ <IconButton
+ icon={<FontAwesomeIcon icon="info-circle" />}
+ size={Size.XSMALL}
+ color={StrCast(Doc.UserDoc().userColor)}
+ onClick={action(() => {
+ this.currentGroup = group;
+ })}
+ />
</div>
</div>
))}
@@ -400,6 +449,6 @@ export class GroupManager extends ObservableReactComponent<{}> {
}
render() {
- return <MainViewModal contents={this.groupInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxStyle={{ zIndex: 1002 }} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={this.close} />;
+ return <MainViewModal contents={this.groupInterface} isDisplayed={this.isOpen} interactive dialogueBoxStyle={{ zIndex: 1002 }} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={this.close} />;
}
}
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index 894583711..da9e1aa28 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -1,4 +1,7 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Button, IconButton, Size, Type } from 'browndash-components';
import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -8,8 +11,7 @@ import { StrCast } from '../../fields/Types';
import { MainViewModal } from '../views/MainViewModal';
import { GroupManager, UserOptions } from './GroupManager';
import './GroupMemberView.scss';
-import { Button, IconButton, Size, Type } from 'browndash-components';
-import { SettingsManager } from './SettingsManager';
+import { SnappingManager } from './SnappingManager';
interface GroupMemberViewProps {
group: Doc;
@@ -32,26 +34,29 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
const hasEditAccess = GroupManager.Instance.hasEditAccess(this.group);
return !this.group ? null : (
- <div className="editing-interface" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
+ <div className="editing-interface" style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor }}>
<div className="editing-header">
<input
className="group-title"
style={{ marginLeft: !hasEditAccess ? '-14%' : 0 }}
value={StrCast(this.group.title || this.group.groupName)}
- onChange={e => (this.group.title = e.currentTarget.value)}
- disabled={!hasEditAccess}></input>
- <div className={'memberView-closeButton'}>
- <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={action(this.props.onCloseButtonClick)} color={StrCast(Doc.UserDoc().userColor)} />
+ onChange={e => {
+ this.group.title = e.currentTarget.value;
+ }}
+ disabled={!hasEditAccess}
+ />
+ <div className="memberView-closeButton">
+ <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={action(this.props.onCloseButtonClick)} color={StrCast(Doc.UserDoc().userColor)} />
</div>
{GroupManager.Instance.hasEditAccess(this.group) ? (
<div className="group-buttons">
<div style={{ border: StrCast(Doc.UserDoc().userColor) }}>
<Select
className="add-member-dropdown"
- isSearchable={true}
+ isSearchable
options={options}
onChange={selectedOption => GroupManager.Instance.addMemberToGroup(this.group, (selectedOption as UserOptions).value)}
- placeholder={'Add members'}
+ placeholder="Add members"
value={null}
styles={{
control: () => ({
@@ -80,7 +85,12 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
</div>
</div>
) : null}
- <div className="sort-emails" style={{ paddingTop: hasEditAccess ? 0 : 35 }} onClick={action(() => (this.memberSort = this.memberSort === 'ascending' ? 'descending' : this.memberSort === 'descending' ? 'none' : 'ascending'))}>
+ <div
+ className="sort-emails"
+ style={{ paddingTop: hasEditAccess ? 0 : 35 }}
+ onClick={action(() => {
+ this.memberSort = this.memberSort === 'ascending' ? 'descending' : this.memberSort === 'descending' ? 'none' : 'ascending';
+ })}>
Emails {this.memberSort === 'ascending' ? '↑' : this.memberSort === 'descending' ? '↓' : ''} {/* → */}
</div>
</div>
@@ -90,8 +100,8 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
<div className="editing-row" key={member}>
<div className="user-email">{member}</div>
{hasEditAccess ? (
- <div className={'remove-button'} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)}>
- <IconButton icon={<FontAwesomeIcon icon={'trash-alt'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)} />
+ <div className="remove-button" onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)}>
+ <IconButton icon={<FontAwesomeIcon icon="trash-alt" />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)} />
</div>
) : null}
</div>
@@ -102,6 +112,6 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
}
render() {
- return <MainViewModal isDisplayed={true} interactive={true} contents={this.editingInterface} dialogueBoxStyle={{ width: 400, height: 250 }} closeOnExternalClick={this.props.onCloseButtonClick} />;
+ return <MainViewModal isDisplayed interactive contents={this.editingInterface} dialogueBoxStyle={{ width: 400, height: 250 }} closeOnExternalClick={this.props.onCloseButtonClick} />;
}
}
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index 2f1a336cc..52d0223d5 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -1,6 +1,12 @@
+/* eslint-disable no-use-before-define */
+/* eslint-disable no-empty */
+/* eslint-disable no-continue */
+/* eslint-disable guard-for-in */
+/* eslint-disable no-restricted-syntax */
+/* eslint-disable no-param-reassign */
import * as qs from 'query-string';
import { Doc } from '../../fields/Doc';
-import { OmitKeys, Utils } from '../../Utils';
+import { OmitKeys, ClientUtils } from '../../ClientUtils';
import { DocServer } from '../DocServer';
import { DashboardView } from '../views/DashboardView';
@@ -32,6 +38,7 @@ export namespace HistoryUtil {
case 'doc':
onDocUrl(url);
break;
+ default:
}
}
}
@@ -124,11 +131,11 @@ export namespace HistoryUtil {
const val = customParser(pathname, opts, current);
if (val === null) {
return undefined;
- } else if (val === undefined) {
+ }
+ if (val === undefined) {
return current;
- } else {
- return val;
}
+ return val;
}
return current;
};
@@ -136,32 +143,33 @@ export namespace HistoryUtil {
function addStringifier(type: string, keys: string[], customStringifier?: (state: ParsedUrl, current: string) => string) {
stringifiers[type] = state => {
- let path = Utils.prepend(`/${type}`);
+ let path = ClientUtils.prepend(`/${type}`);
if (customStringifier) {
path = customStringifier(state, path);
}
const queryObj = OmitKeys(state, keys).extract;
const query: any = {};
- Object.keys(queryObj).forEach(key => (query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key])));
+ Object.keys(queryObj).forEach(key => {
+ query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key]);
+ });
const queryString = qs.stringify(query);
return path + (queryString ? `?${queryString}` : '');
};
}
addParser('doc', {}, { readonly: true, initializers: true, nro: true, sharing: true }, (pathname, opts, current) => {
- if (pathname.length !== 2) return undefined;
-
- current.initializers = current.initializers || {};
- const docId = pathname[1];
- current.docId = docId;
- });
- addStringifier('doc', ['initializers', 'readonly', 'nro'], (state, current) => {
- return `${current}/${state.docId}`;
+ if (pathname.length === 2) {
+ current.initializers = current.initializers || {};
+ const docId = pathname[1];
+ current.docId = docId;
+ }
+ return undefined;
});
+ addStringifier('doc', ['initializers', 'readonly', 'nro'], (state, current) => `${current}/${state.docId}`);
export function parseUrl(location: Location | URL): ParsedUrl | undefined {
const pathname = location.pathname.substring(1);
- const search = location.search;
+ const { search } = location;
const opts = search.length ? qs.parse(search, { sort: false }) : {};
const pathnameSplit = pathname.split('/');
diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts
index c5f307f44..5a6522dc8 100644
--- a/src/client/util/HypothesisUtils.ts
+++ b/src/client/util/HypothesisUtils.ts
@@ -1,15 +1,11 @@
import { action, runInAction } from 'mobx';
-import { simulateMouseClick } from '../../Utils';
+import { simulateMouseClick } from '../../ClientUtils';
import { Doc, Opt } from '../../fields/Doc';
import { Cast, StrCast } from '../../fields/Types';
import { WebField } from '../../fields/URLField';
-import { DocumentType } from '../documents/DocumentTypes';
import { Docs } from '../documents/Documents';
import { DocumentLinksButton } from '../views/nodes/DocumentLinksButton';
import { DocumentView } from '../views/nodes/DocumentView';
-import { DocumentManager } from './DocumentManager';
-import { SearchUtil } from './SearchUtil';
-import { SelectionManager } from './SelectionManager';
export namespace Hypothesis {
/**
@@ -17,6 +13,7 @@ export namespace Hypothesis {
* If none exist, create and return a new WebDocument.
*/
export const getSourceWebDoc = async (uri: string) => {
+ // eslint-disable-next-line no-use-before-define
const result = await findWebDoc(uri);
console.log(result ? 'existing doc found' : 'existing doc NOT found');
return result || Docs.Create.WebDocument(uri, { title: uri, _nativeWidth: 850, _height: 512, _width: 400, data_useCors: true }); // create and return a new Web doc with given uri if no matching docs are found
@@ -26,21 +23,21 @@ 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 = DocumentView.Selected().lastElement()?.Document;
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[] = [];
// await SearchUtil.Search('web', true).then(
// action(async (res: SearchUtil.DocSearchResult) => {
// const docs = res.docs;
- // const filteredDocs = docs.filter(doc => doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data);
+ // const filteredDocs = docs.filter(doc => doc.author === ClientUtils.CurrentUserEmail() && doc.type === DocumentType.WEB && doc.data);
// filteredDocs.forEach(doc => {
// uri === Cast(doc.data, WebField)?.url.href && results.push(doc); // TODO check visited sites history?
// });
// })
// );
- const onScreenResults = results.filter(doc => DocumentManager.Instance.getFirstDocumentView(doc));
+ const onScreenResults = results.filter(doc => DocumentView.getFirstDocumentView(doc));
return onScreenResults.length ? onScreenResults[0] : results.length ? results[0] : undefined; // prioritize results that are currently on the screen
};
@@ -66,7 +63,7 @@ export namespace Hypothesis {
DocumentLinksButton.AnnotationId = annotationId;
DocumentLinksButton.AnnotationUri = annotationUri;
});
- const endLinkView = DocumentManager.Instance.getFirstDocumentView(sourceDoc);
+ const endLinkView = DocumentView.getFirstDocumentView(sourceDoc);
const rect = document.body.getBoundingClientRect();
const x = rect.x + rect.width / 2;
const y = 250;
@@ -77,17 +74,19 @@ export namespace Hypothesis {
/**
* Send message to Hypothes.is client to edit an annotation to add a Dash hyperlink
*/
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
export const makeLink = async (title: string, url: string, annotationId: string, annotationSourceDoc: Doc) => {
// if the annotation's source webpage isn't currently loaded in Dash, we're not able to access and edit the annotation from the client
// so we're loading the webpage and its annotations invisibly in a WebBox in MainView.tsx, until the editing is done
- //!DocumentManager.Instance.getFirstDocumentView(annotationSourceDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = annotationSourceDoc);
+ //! DocumentManager.Instance.getFirstDocumentView(annotationSourceDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = annotationSourceDoc);
- var success = false;
+ let success = false;
const onSuccess = action(() => {
console.log('Edit success!!');
success = true;
+ // eslint-disable-next-line no-use-before-define
clearTimeout(interval);
- //DocumentLinksButton.invisibleWebDoc = undefined;
+ // DocumentLinksButton.invisibleWebDoc = undefined;
document.removeEventListener('editSuccess', onSuccess);
});
@@ -109,7 +108,7 @@ export namespace Hypothesis {
action(() => {
if (!success) {
clearInterval(interval);
- //DocumentLinksButton.invisibleWebDoc = undefined;
+ // DocumentLinksButton.invisibleWebDoc = undefined;
}
}),
10000
@@ -123,12 +122,13 @@ export namespace Hypothesis {
export const deleteLink = async (linkDoc: Doc, sourceDoc: Doc, destinationDoc: Doc) => {
if (Cast(destinationDoc.data, WebField)?.url.href !== StrCast(linkDoc.annotationUri)) return; // check that the destinationDoc is a WebDocument containing the target annotation
- //!DocumentManager.Instance.getFirstDocumentView(destinationDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = destinationDoc); // see note in makeLink
+ //! DocumentManager.Instance.getFirstDocumentView(destinationDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = destinationDoc); // see note in makeLink
- var success = false;
+ let success = false;
const onSuccess = action(() => {
console.log('Edit success!');
success = true;
+ // eslint-disable-next-line no-use-before-define
clearTimeout(interval);
// DocumentLinksButton.invisibleWebDoc = undefined;
document.removeEventListener('editSuccess', onSuccess);
@@ -151,7 +151,7 @@ export namespace Hypothesis {
action(() => {
if (!success) {
clearInterval(interval);
- //DocumentLinksButton.invisibleWebDoc = undefined;
+ // DocumentLinksButton.invisibleWebDoc = undefined;
}
}),
10000
@@ -163,10 +163,11 @@ export namespace Hypothesis {
* Send message to Hypothes.is client to scroll to an annotation when it loads
*/
export const scrollToAnnotation = (annotationId: string, target: Doc) => {
- var success = false;
+ let success = false;
const onSuccess = () => {
console.log('Scroll success!!');
document.removeEventListener('scrollSuccess', onSuccess);
+ // eslint-disable-next-line no-use-before-define
clearInterval(interval);
success = true;
};
@@ -179,7 +180,7 @@ export namespace Hypothesis {
bubbles: true,
})
);
- const targetView: Opt<DocumentView> = DocumentManager.Instance.getFirstDocumentView(target);
+ const targetView: Opt<DocumentView> = DocumentView.getFirstDocumentView(target);
const position = targetView?.screenToViewTransform().inverse().transformPoint(0, 0);
targetView && position && simulateMouseClick(targetView.ContentDiv!, position[0], position[1], position[0], position[1], false);
}, 300);
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 398ba3c04..baa0786b7 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -17,7 +17,6 @@
// 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';
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index d99828956..8d4eefa7e 100644
--- a/src/client/util/Import & Export/ImageUtils.ts
+++ b/src/client/util/Import & Export/ImageUtils.ts
@@ -1,10 +1,10 @@
+import { ClientUtils } from '../../../ClientUtils';
import { Doc } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
+import { Id } from '../../../fields/FieldSymbols';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
-import { Cast, StrCast, NumCast } from '../../../fields/Types';
import { Networking } from '../../Network';
-import { Id } from '../../../fields/FieldSymbols';
-import { Utils } from '../../../Utils';
-import { DocData } from '../../../fields/DocSymbols';
export namespace ImageUtils {
export type imgInfo = {
@@ -16,7 +16,7 @@ export namespace ImageUtils {
};
export const ExtractImgInfo = async (document: Doc): Promise<imgInfo | undefined> => {
const field = Cast(document.data, ImageField);
- return field ? await Networking.PostToServer('/inspectImage', { source: field.url.href }) : undefined;
+ return field ? Networking.PostToServer('/inspectImage', { source: field.url.href }) : undefined;
};
export const AssignImgInfo = (document: Doc, data?: imgInfo) => {
@@ -35,7 +35,7 @@ export namespace ImageUtils {
export const ExportHierarchyToFileSystem = async (collection: Doc): Promise<void> => {
const a = document.createElement('a');
- a.href = Utils.prepend(`/imageHierarchyExport/${collection[Id]}`);
+ a.href = ClientUtils.prepend(`/imageHierarchyExport/${collection[Id]}`);
a.download = `Dash Export [${StrCast(collection.title)}].zip`;
a.click();
};
diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx
index 58a09b9c9..db1e3d6cd 100644
--- a/src/client/util/Import & Export/ImportMetadataEntry.tsx
+++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx
@@ -1,10 +1,13 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { EditableView } from '../../views/EditableView';
-import { action, computed } from 'mobx';
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable no-use-before-define */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
import { Doc } from '../../../fields/Doc';
-import { StrCast, BoolCast } from '../../../fields/Types';
+import { BoolCast, StrCast } from '../../../fields/Types';
+import { EditableView } from '../../views/EditableView';
interface KeyValueProps {
Document: Doc;
@@ -93,18 +96,27 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps>
alignItems: 'center',
alignContent: 'center',
}}>
- <input onChange={e => (this.onDataDoc = e.target.checked)} ref={this.checkRef} style={{ margin: '0 10px 0 15px' }} type="checkbox" title={'Add to Data Document?'} checked={this.onDataDoc} />
- <div className={'key_container'} style={keyValueStyle}>
- <EditableView ref={this.keyRef} contents={this.key} SetValue={this.updateKey} GetValue={() => ''} oneLine={true} />
+ <input
+ onChange={e => {
+ this.onDataDoc = e.target.checked;
+ }}
+ ref={this.checkRef}
+ style={{ margin: '0 10px 0 15px' }}
+ type="checkbox"
+ title="Add to Data Document?"
+ checked={this.onDataDoc}
+ />
+ <div className="key_container" style={keyValueStyle}>
+ <EditableView ref={this.keyRef} contents={this.key} SetValue={this.updateKey} GetValue={() => ''} oneLine />
</div>
- <div className={'value_container'} style={keyValueStyle}>
- <EditableView ref={this.valueRef} contents={this.value} SetValue={this.updateValue} GetValue={() => ''} oneLine={true} />
+ <div className="value_container" style={keyValueStyle}>
+ <EditableView ref={this.valueRef} contents={this.value} SetValue={this.updateValue} GetValue={() => ''} oneLine />
</div>
- <div onClick={() => this.props.remove(this)} title={'Delete Entry'}>
+ <div onClick={() => this.props.remove(this)} title="Delete Entry">
<FontAwesomeIcon
- icon={'plus'}
- color={'red'}
- size={'1x'}
+ icon="plus"
+ color="red"
+ size="1x"
style={{
marginLeft: 15,
marginRight: 15,
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index a2f5826fe..a07550e09 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
-import { GestureUtils } from '../../pen-gestures/GestureUtils';
import { Utils } from '../../Utils';
+import { Gestures } from '../../pen-gestures/GestureTypes';
import './InteractionUtils.scss';
export namespace InteractionUtils {
@@ -9,81 +9,86 @@ export namespace InteractionUtils {
export const PENTYPE = 'pen';
export const ERASERTYPE = 'eraser';
- const POINTER_PEN_BUTTON = -1;
- const REACT_POINTER_PEN_BUTTON = 0;
const ERASER_BUTTON = 5;
- export class MultiTouchEvent<T extends React.TouchEvent | TouchEvent> {
- constructor(
- readonly fingers: number,
- readonly targetTouches: T extends React.TouchEvent ? React.Touch[] : Touch[],
- readonly touches: T extends React.TouchEvent ? React.Touch[] : Touch[],
- readonly changedTouches: T extends React.TouchEvent ? React.Touch[] : Touch[],
- readonly touchEvent: T extends React.TouchEvent ? React.TouchEvent : TouchEvent
- ) {}
- }
-
- export interface MultiTouchEventDisposer {
- (): void;
- }
-
- /**
- *
- * @param element - element to turn into a touch target
- * @param startFunc - event handler, typically Touchable.onTouchStart (classes that inherit touchable can pass in this.onTouchStart)
- */
- export function MakeMultiTouchTarget(element: HTMLElement, startFunc: (e: Event, me: MultiTouchEvent<React.TouchEvent>) => void): MultiTouchEventDisposer {
- const onMultiTouchStartHandler = (e: Event) => startFunc(e, (e as CustomEvent<MultiTouchEvent<React.TouchEvent>>).detail);
- // const onMultiTouchMoveHandler = moveFunc ? (e: Event) => moveFunc(e, (e as CustomEvent<MultiTouchEvent<TouchEvent>>).detail) : undefined;
- // const onMultiTouchEndHandler = endFunc ? (e: Event) => endFunc(e, (e as CustomEvent<MultiTouchEvent<TouchEvent>>).detail) : undefined;
- element.addEventListener('dashOnTouchStart', onMultiTouchStartHandler);
- // if (onMultiTouchMoveHandler) {
- // element.addEventListener("dashOnTouchMove", onMultiTouchMoveHandler);
- // }
- // if (onMultiTouchEndHandler) {
- // element.addEventListener("dashOnTouchEnd", onMultiTouchEndHandler);
- // }
- return () => {
- element.removeEventListener('dashOnTouchStart', onMultiTouchStartHandler);
- // if (onMultiTouchMoveHandler) {
- // element.removeEventListener("dashOnTouchMove", onMultiTouchMoveHandler);
- // }
- // if (onMultiTouchEndHandler) {
- // element.removeEventListener("dashOnTouchend", onMultiTouchEndHandler);
- // }
- };
- }
-
- /**
- * Turns an element onto a target for touch hold handling.
- * @param element - element to add events to
- * @param func - function to add to the event
- */
- export function MakeHoldTouchTarget(element: HTMLElement, func: (e: Event, me: MultiTouchEvent<React.TouchEvent>) => void): MultiTouchEventDisposer {
- const handler = (e: Event) => func(e, (e as CustomEvent<MultiTouchEvent<React.TouchEvent>>).detail);
- element.addEventListener('dashOnTouchHoldStart', handler);
- return () => {
- element.removeEventListener('dashOnTouchHoldStart', handler);
- };
- }
-
- export function GetMyTargetTouches(mte: InteractionUtils.MultiTouchEvent<React.TouchEvent | TouchEvent>, prevPoints: Map<number, React.Touch>, ignorePen: boolean): React.Touch[] {
- const myTouches = new Array<React.Touch>();
- for (const pt of mte.touches) {
- if (!ignorePen || ((pt as any).radiusX > 1 && (pt as any).radiusY > 1)) {
- for (const tPt of mte.targetTouches) {
- if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) {
- if (pt && prevPoints.has(pt.identifier)) {
- myTouches.push(pt);
- }
- }
+ export function makePolygon(shape: string, points: { X: number; Y: number }[]) {
+ // if arrow/line/circle, the two end points should be the starting and the ending point
+ let left = points[0].X;
+ let top = points[0].Y;
+ let right = points[1].X;
+ let bottom = points[1].Y;
+ if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) {
+ // pointer is up (first and last points are the same)
+ if (![Gestures.Arrow, Gestures.Line, Gestures.Circle].includes(shape as any as Gestures)) {
+ // otherwise take max and min
+ const xs = points.map(p => p.X);
+ const ys = points.map(p => p.Y);
+ right = Math.max(...xs);
+ left = Math.min(...xs);
+ bottom = Math.max(...ys);
+ top = Math.min(...ys);
+ }
+ } else {
+ // if in the middle of drawing
+ // take first and last points
+ right = points[points.length - 1].X;
+ left = points[0].X;
+ bottom = points[points.length - 1].Y;
+ top = points[0].Y;
+ if (shape !== Gestures.Arrow && shape !== Gestures.Line && shape !== Gestures.Circle) {
+ // switch left/right and top/bottom if needed
+ if (left > right) {
+ const temp = right;
+ right = left;
+ left = temp;
+ }
+ if (top > bottom) {
+ const temp = top;
+ top = bottom;
+ bottom = temp;
}
}
}
- // if (mte.touches.length !== myTouches.length) {
- // throw Error("opo")
- // }
- return myTouches;
+ const polyPts = [];
+ switch (shape) {
+ case Gestures.Rectangle:
+ polyPts.push({ X: left, Y: top });
+ polyPts.push({ X: right, Y: top });
+ polyPts.push({ X: right, Y: bottom });
+ polyPts.push({ X: left, Y: bottom });
+ polyPts.push({ X: left, Y: top });
+ break;
+ case Gestures.Triangle:
+ polyPts.push({ X: left, Y: bottom });
+ polyPts.push({ X: right, Y: bottom });
+ polyPts.push({ X: (right + left) / 2, Y: top });
+ polyPts.push({ X: left, Y: bottom });
+ break;
+ case Gestures.Circle:
+ {
+ const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
+ const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
+ const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
+ for (let x = centerX - radius; x < centerX + radius; x++) {
+ const y = Math.sqrt(radius ** 2 - (x - centerX) ** 2) + centerY;
+ polyPts.push({ X: x, Y: y });
+ }
+ for (let x = centerX + radius; x > centerX - radius; x--) {
+ const y = Math.sqrt(radius ** 2 - (x - centerX) ** 2) + centerY;
+ const newY = centerY - (y - centerY);
+ polyPts.push({ X: x, Y: newY });
+ }
+ polyPts.push({ X: centerX - radius, Y: Math.sqrt(radius ** 2 - (-radius) ** 2) + centerY });
+ }
+ break;
+
+ case Gestures.Line:
+ default:
+ polyPts.push({ X: left, Y: top });
+ polyPts.push({ X: right, Y: bottom });
+ break;
+ }
+ return polyPts;
}
export function CreatePolyline(
@@ -101,20 +106,20 @@ export namespace InteractionUtils {
arrowEnd: string,
markerScale: number,
dash: string | undefined,
- scalex: number,
- scaley: number,
+ scalexIn: number,
+ scaleyIn: number,
shape: string,
pevents: string,
opacity: number,
nodefs: boolean,
downHdlr?: (e: React.PointerEvent) => void,
- mask?: boolean,
- dropshadow?: string
+ mask?: boolean
+ // dropshadow?: string
) {
const pts = shape ? makePolygon(shape, points) : points;
- if (isNaN(scalex)) scalex = 1;
- if (isNaN(scaley)) scaley = 1;
+ const scalex = isNaN(scalexIn) ? 1 : scalexIn;
+ const scaley = isNaN(scaleyIn) ? 1 : scaleyIn;
const toScr = (p: { X: number; Y: number }) => ` ${!p ? 0 : (p.X - left - width / 2) * scalex + width / 2}, ${!p ? 0 : (p.Y - top - width / 2) * scaley + width / 2} `;
const strpts = bezier
@@ -137,7 +142,7 @@ export namespace InteractionUtils {
<defs>
{!mask ? null : (
<filter id={`mask${defGuid}`} x="-1" y="-1" width="500%" height="500%">
- <feGaussianBlur result="blurOut" in="offOut" stdDeviation="5"></feGaussianBlur>
+ <feGaussianBlur result="blurOut" in="offOut" stdDeviation="5" />
</filter>
)}
{arrowStart !== 'dot' && arrowEnd !== 'dot' ? null : (
@@ -172,7 +177,7 @@ export namespace InteractionUtils {
<Tag
d={bezier ? strpts + (arrowStart || arrowEnd ? ' ' : '') : undefined}
points={bezier ? undefined : strpts}
- //filter={!dropshadow ? undefined : `drop-shadow(-1px -1px 0px ${dropshadow}) `}
+ // filter={!dropshadow ? undefined : `drop-shadow(-1px -1px 0px ${dropshadow}) `}
style={{
// filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
fill: fill && fill !== 'transparent' ? fill : 'none',
@@ -193,83 +198,6 @@ export namespace InteractionUtils {
);
}
- export function makePolygon(shape: string, points: { X: number; Y: number }[]) {
- if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) {
- //pointer is up (first and last points are the same)
- if (shape === GestureUtils.Gestures.Arrow || shape === GestureUtils.Gestures.Line || shape === GestureUtils.Gestures.Circle) {
- //if arrow or line, the two end points should be the starting and the ending point
- var left = points[0].X;
- var top = points[0].Y;
- var right = points[1].X;
- var bottom = points[1].Y;
- } else {
- //otherwise take max and min
- const xs = points.map(p => p.X);
- const ys = points.map(p => p.Y);
- right = Math.max(...xs);
- left = Math.min(...xs);
- bottom = Math.max(...ys);
- top = Math.min(...ys);
- }
- } else {
- //if in the middle of drawing
- //take first and last points
- right = points[points.length - 1].X;
- left = points[0].X;
- bottom = points[points.length - 1].Y;
- top = points[0].Y;
- if (shape !== GestureUtils.Gestures.Arrow && shape !== GestureUtils.Gestures.Line && shape !== GestureUtils.Gestures.Circle) {
- //switch left/right and top/bottom if needed
- if (left > right) {
- const temp = right;
- right = left;
- left = temp;
- }
- if (top > bottom) {
- const temp = top;
- top = bottom;
- bottom = temp;
- }
- }
- }
- points = [];
- switch (shape) {
- case GestureUtils.Gestures.Rectangle:
- points.push({ X: left, Y: top });
- points.push({ X: right, Y: top });
- points.push({ X: right, Y: bottom });
- points.push({ X: left, Y: bottom });
- points.push({ X: left, Y: top });
- break;
- case GestureUtils.Gestures.Triangle:
- points.push({ X: left, Y: bottom });
- points.push({ X: right, Y: bottom });
- points.push({ X: (right + left) / 2, Y: top });
- points.push({ X: left, Y: bottom });
- break;
- case GestureUtils.Gestures.Circle:
- const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
- const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
- const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
- for (var x = centerX - radius; x < centerX + radius; x++) {
- const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY;
- points.push({ X: x, Y: y });
- }
- for (var x = centerX + radius; x > centerX - radius; x--) {
- const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY;
- const newY = centerY - (y - centerY);
- points.push({ X: x, Y: newY });
- }
- points.push({ X: centerX - radius, Y: Math.sqrt(Math.pow(radius, 2) - Math.pow(-radius, 2)) + centerY });
- break;
-
- case GestureUtils.Gestures.Line:
- points.push({ X: left, Y: top });
- points.push({ X: right, Y: bottom });
- break;
- }
- return points;
- }
/**
* Returns whether or not the pointer event passed in is of the type passed in
* @param e - pointer event. this event could be from a mouse, a pen, or a finger
@@ -279,10 +207,11 @@ export namespace InteractionUtils {
// prettier-ignore
switch (type) {
// pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2
- case PENTYPE: return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
+ case PENTYPE: return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
case ERASERTYPE: return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON);
- case TOUCHTYPE: return e.pointerType === TOUCHTYPE;
- }
+ case TOUCHTYPE: return e.pointerType === TOUCHTYPE;
+ default:
+ } // prettier-ignore
return e.pointerType === type;
}
@@ -292,7 +221,7 @@ export namespace InteractionUtils {
* @param pt2
*/
export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number {
- return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2));
+ return Math.sqrt((pt1.clientX - pt2.clientX) ** 2 + (pt1.clientY - pt2.clientY) ** 2);
}
/**
@@ -349,72 +278,4 @@ export namespace InteractionUtils {
}
return 0;
}
-
- export function IsDragging(oldTouches: Map<number, React.Touch>, newTouches: React.Touch[], leniency: number): boolean {
- for (const touch of newTouches) {
- if (touch) {
- const oldTouch = oldTouches.get(touch.identifier);
- if (oldTouch) {
- if (TwoPointEuclidist(touch, oldTouch) >= leniency) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- // These might not be very useful anymore, but I'll leave them here for now -syip2
- {
- /**
- * Returns the type of Touch Interaction from a list of points.
- * Also returns any data that is associated with a Touch Interaction
- * @param pts - List of points
- */
- // export function InterpretPointers(pts: React.Touch[]): { type: Opt<TouchInteraction>, data?: any } {
- // const leniency = 200;
- // switch (pts.length) {
- // case 1:
- // return { type: OneFinger };
- // case 2:
- // return { type: TwoSeperateFingers };
- // case 3:
- // let pt1 = pts[0];
- // let pt2 = pts[1];
- // let pt3 = pts[2];
- // if (pt1 && pt2 && pt3) {
- // let dist12 = TwoPointEuclidist(pt1, pt2);
- // let dist23 = TwoPointEuclidist(pt2, pt3);
- // let dist13 = TwoPointEuclidist(pt1, pt3);
- // let dist12close = dist12 < leniency;
- // let dist23close = dist23 < leniency;
- // let dist13close = dist13 < leniency;
- // let xor2313 = dist23close ? !dist13close : dist13close;
- // let xor = dist12close ? !xor2313 : xor2313;
- // // three input xor because javascript doesn't have logical xor's
- // if (xor) {
- // let points: number[] = [];
- // let min = Math.min(dist12, dist23, dist13);
- // switch (min) {
- // case dist12:
- // points = [0, 1, 2];
- // break;
- // case dist23:
- // points = [1, 2, 0];
- // break;
- // case dist13:
- // points = [0, 2, 1];
- // break;
- // }
- // return { type: TwoToOneFingers, data: points };
- // }
- // else {
- // return { type: ThreeSeperateFingers, data: null };
- // }
- // }
- // default:
- // return { type: undefined };
- // }
- // }
- }
}
diff --git a/src/client/util/KeyCodes.ts b/src/client/util/KeyCodes.ts
index de2457a5a..fb21f78ee 100644
--- a/src/client/util/KeyCodes.ts
+++ b/src/client/util/KeyCodes.ts
@@ -133,4 +133,4 @@ export class KeyCodes {
public static NUM_9: number = 57;
public static SUBTRACT: number = 189;
public static ADD: number = 187;
-} \ No newline at end of file
+}
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 6c0bf3242..9a0edcfec 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -1,15 +1,12 @@
import { action, runInAction } from 'mobx';
-import { Doc, DocListCast, Field, FieldResult, Opt } from '../../fields/Doc';
-import { ScriptField } from '../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
+import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
import { DocumentType } from '../documents/DocumentTypes';
-import { OpenWhere } from '../views/nodes/DocumentView';
-import { FocusViewOptions } from '../views/nodes/FieldView';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { FocusViewOptions } from '../views/nodes/FocusViewOptions';
+import { OpenWhere } from '../views/nodes/OpenWhere';
import { PresBox } from '../views/nodes/trails';
-import { DocumentManager } from './DocumentManager';
-import { LinkManager } from './LinkManager';
import { ScriptingGlobals } from './ScriptingGlobals';
-import { SelectionManager } from './SelectionManager';
import { SnappingManager } from './SnappingManager';
import { UndoManager } from './UndoManager';
/*
@@ -25,6 +22,9 @@ import { UndoManager } from './UndoManager';
* - user defined kvps
*/
export class LinkFollower {
+ public static Init() {
+ DocumentView.FollowLink = LinkFollower.FollowLink;
+ }
// follows a link - if the target is on screen, it highlights/pans to it.
// if the target isn't onscreen, then it will open up the target in the lightbox, or in place
// depending on the followLinkLocation property of the source (or the link itself as a fallback);
@@ -43,9 +43,9 @@ export class LinkFollower {
};
public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, finished?: () => void, traverseBacklink?: boolean) {
- const getView = (doc: Doc) => DocumentManager.Instance.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc));
- const isAnchor = (sourceDoc: Doc, anchor: Doc) => Doc.AreProtosEqual(anchor, sourceDoc) || Doc.AreProtosEqual(anchor.annotationOn as Doc, sourceDoc);
- const linkDocs = link ? [link] : LinkManager.Links(sourceDoc);
+ const getView = (doc: Doc) => DocumentView.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc));
+ const isAnchor = (source: Doc, anchor: Doc) => Doc.AreProtosEqual(anchor, source) || Doc.AreProtosEqual(anchor.annotationOn as Doc, source);
+ const linkDocs = link ? [link] : Doc.Links(sourceDoc);
const fwdLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_1 as Doc)); // link docs where 'sourceDoc' is link_anchor_1
const backLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_2 as Doc)); // link docs where 'sourceDoc' is link_anchor_2
const fwdLinkWithoutTargetView = fwdLinks.find(l => !getView(DocCast(l.link_anchor_2)));
@@ -53,7 +53,7 @@ export class LinkFollower {
const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView ?? backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
const linkDocList = linkWithoutTargetDoc && !sourceDoc.followAllLinks ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? fwdLinks.concat(backLinks) : traverseBacklink ? backLinks : fwdLinks;
const followLinks = sourceDoc.followLinkToggle || sourceDoc.followAllLinks ? linkDocList : linkDocList.slice(0, 1);
- var count = 0;
+ let count = 0;
const allFinished = () => ++count === followLinks.length && finished?.();
if (!followLinks.length) {
finished?.();
@@ -69,7 +69,7 @@ export class LinkFollower {
? linkDoc.link_anchor_2
: linkDoc.link_anchor_1
) as Doc;
- const srcAnchor = LinkManager.getOppositeAnchor(linkDoc, target) ?? sourceDoc;
+ const srcAnchor: Doc = Doc.getOppositeAnchor(linkDoc, target) ?? sourceDoc;
if (target) {
const doFollow = (canToggle?: boolean) => {
const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle);
@@ -88,14 +88,14 @@ export class LinkFollower {
zoomTextSelections: BoolCast(srcAnchor.followLinkZoomText),
};
if (target.type === DocumentType.PRES) {
- const containerDocContext = DocumentManager.GetContextPath(sourceDoc, true); // gather all views that affect layout of sourceDoc so we can revert them after playing the rail
- SelectionManager.DeselectAll();
- if (!DocumentManager.Instance.AddViewRenderedCb(target, dv => containerDocContext.length && (dv.ComponentView as PresBox).PlayTrail(containerDocContext))) {
+ const containerDocContext = DocumentView.getContextPath(sourceDoc, true); // gather all views that affect layout of sourceDoc so we can revert them after playing the rail
+ DocumentView.DeselectAll();
+ if (!DocumentView.addViewRenderedCb(target, dv => containerDocContext.length && dv.ComponentView?.playTrail?.(containerDocContext))) {
PresBox.OpenPresMinimized(target, [0, 0]);
}
finished?.();
} else {
- DocumentManager.Instance.showDocument(target, options, allFinished);
+ DocumentView.showDocument(target, options, allFinished);
}
};
let movedTarget = false;
@@ -113,10 +113,12 @@ export class LinkFollower {
}
const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)];
if (srcAnchor.followLinkXoffset !== undefined && moveTo[0] !== target.x) {
+ // eslint-disable-next-line prefer-destructuring
target.x = moveTo[0];
movedTarget = true;
}
if (srcAnchor.followLinkYoffset !== undefined && moveTo[1] !== target.y) {
+ // eslint-disable-next-line prefer-destructuring
target.y = moveTo[1];
movedTarget = true;
}
@@ -130,14 +132,8 @@ export class LinkFollower {
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) {
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
return LinkFollower.FollowLink(undefined, doc, altKey) ? undefined : { select: true };
});
-
-export function FollowLinkScript() {
- return ScriptField.MakeScript('return followLink(this,altKey)', { altKey: 'boolean' });
-}
-export function IsFollowLinkScript(field: FieldResult<Field>) {
- return ScriptCast(field)?.script.originalScript.includes('return followLink(');
-}
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 8972bf705..56d5dce4e 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,12 +1,16 @@
-import { action, makeObservable, observable, observe, runInAction } from 'mobx';
+import { action, makeObservable, observable, observe } from 'mobx';
import { computedFn } from 'mobx-utils';
-import { Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc';
+import * as rp from 'request-promise';
+import { ClientUtils } from '../../ClientUtils';
+import { Doc, DocListCast, DocListCastAsync, FieldType, Opt } from '../../fields/Doc';
import { DirectLinks, DocData } from '../../fields/DocSymbols';
import { FieldLoader } from '../../fields/FieldLoader';
+import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { ProxyField } from '../../fields/Proxy';
import { Cast, DocCast, PromiseValue, StrCast } from '../../fields/Types';
import { DocServer } from '../DocServer';
+import { DocumentType } from '../documents/DocumentTypes';
import { ScriptingGlobals } from './ScriptingGlobals';
/*
* link doc:
@@ -21,12 +25,13 @@ import { ScriptingGlobals } from './ScriptingGlobals';
* - user defined kvps
*/
export class LinkManager {
+ // eslint-disable-next-line no-use-before-define
@observable static _instance: LinkManager;
@observable.shallow userLinkDBs: Doc[] = [];
@observable public currentLink: Opt<Doc> = undefined;
@observable public currentLinkAnchor: Opt<Doc> = undefined;
- public static get Instance() {
- return LinkManager._instance;
+ public static get Instance(): LinkManager {
+ return Doc.UserDoc() ? LinkManager._instance ?? new LinkManager() : (undefined as any as LinkManager);
}
public static Links(doc: Doc | undefined) {
@@ -42,9 +47,13 @@ export class LinkManager {
this.userLinkDBs.push(linkDb);
};
public static AutoKeywords = 'keywords:Usages';
- constructor() {
+ private constructor() {
makeObservable(this);
LinkManager._instance = this;
+ Doc.AddLink = this.addLink;
+ Doc.DeleteLink = this.deleteLink;
+ Doc.Links = LinkManager.Links;
+ Doc.getOppositeAnchor = LinkManager.getOppositeAnchor;
this.createlink_relationshipLists();
// since this is an action, not a reaction, we get only one shot to add this link to the Anchor docs
// Thus make sure all promised values are resolved from link -> link.proto -> link.link_anchor_[1,2] -> link.link_anchor_[1,2].proto
@@ -56,7 +65,7 @@ export class LinkManager {
Promise.all(lAnchs.map(lAnch => PromiseValue(lAnch?.proto as Doc))).then((lAnchProtos: Opt<Doc>[]) =>
Promise.all(lAnchProtos.map(lAnchProto => PromiseValue(lAnchProto?.proto as Doc))).then(
link &&
- action(lAnchProtoProtos => {
+ action(() => {
Doc.AddDocToList(Doc.UserDoc(), 'links', link);
lAnchs[0]?.[DocData][DirectLinks].add(link);
lAnchs[1]?.[DocData][DirectLinks].add(link);
@@ -73,7 +82,7 @@ export class LinkManager {
Promise.all([lproto?.link_anchor_1 as Doc, lproto?.link_anchor_2 as Doc].map(PromiseValue)).then((lAnchs: Opt<Doc>[]) =>
Promise.all(lAnchs.map(lAnch => PromiseValue(lAnch?.proto as Doc))).then((lAnchProtos: Opt<Doc>[]) =>
Promise.all(lAnchProtos.map(lAnchProto => PromiseValue(lAnchProto?.proto as Doc))).then(
- action(lAnchProtoProtos => {
+ action(() => {
link && lAnchs[0] && lAnchs[0][DocData][DirectLinks].delete(link);
link && lAnchs[1] && lAnchs[1][DocData][DirectLinks].delete(link);
})
@@ -84,7 +93,7 @@ export class LinkManager {
);
const watchUserLinkDB = (userLinkDBDoc: Doc) => {
- const toRealField = (field: Field) => (field instanceof ProxyField ? field.value : field); // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields
+ const toRealField = (field: FieldType) => (field instanceof ProxyField ? field.value : field); // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields
if (userLinkDBDoc.data) {
observe(
userLinkDBDoc.data,
@@ -95,7 +104,8 @@ export class LinkManager {
(change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link)));
(change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link)));
break;
- case 'update': //let oldValue = change.oldValue;
+ case 'update': // let oldValue = change.oldValue;
+ default:
}
},
true
@@ -115,6 +125,8 @@ export class LinkManager {
added?.forEach((link: any) => addLinkToDoc(toRealField(link)));
removed?.forEach((link: any) => remLinkFromDoc(toRealField(link)));
});
+ break;
+ default:
}
},
true
@@ -128,7 +140,8 @@ export class LinkManager {
case 'splice':
(change as any).added.forEach(watchUserLinkDB);
break;
- case 'update': //let oldValue = change.oldValue;
+ case 'update': // let oldValue = change.oldValue;
+ default:
}
},
true
@@ -138,7 +151,7 @@ export class LinkManager {
}
public createlink_relationshipLists = () => {
- //create new lists for link relations and their associated colors if the lists don't already exist
+ // create new lists for link relations and their associated colors if the lists don't already exist
!Doc.UserDoc().link_relationshipList && (Doc.UserDoc().link_relationshipList = new List<string>());
!Doc.UserDoc().link_ColorList && (Doc.UserDoc().link_ColorList = new List<string>());
!Doc.UserDoc().link_relationshipSizes && (Doc.UserDoc().link_relationshipSizes = new List<number>());
@@ -148,7 +161,8 @@ export class LinkManager {
Doc.AddDocToList(Doc.UserDoc(), 'links', linkDoc);
if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) {
Doc.AddDocToList(Doc.LinkDBDoc(), 'data', linkDoc);
- setTimeout(DocServer.UPDATE_SERVER_CACHE, 100);
+ // eslint-disable-next-line no-use-before-define
+ setTimeout(UPDATE_SERVER_CACHE, 100);
}
}
public deleteLink(linkDoc: Doc) {
@@ -188,9 +202,7 @@ export class LinkManager {
);
};
- relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
- return this.computedRelatedLinks(anchor, [anchor]);
- }, true);
+ relatedLinker = computedFn((anchor: Doc): Doc[] => this.computedRelatedLinks(anchor, [anchor]), true);
// returns map of group type to anchor's links in that group type
public getRelatedGroupedLinks(anchor: Doc): Map<string, Array<Doc>> {
@@ -198,7 +210,7 @@ export class LinkManager {
this.relatedLinker(anchor).forEach(link => {
if (link.link_relationship && link.link_relationship !== '-ungrouped-') {
const relation = StrCast(link.link_relationship);
- const anchorRelation = relation.indexOf(':') !== -1 ? relation.split(':')[Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), anchor) ? 0 : 1] : relation;
+ const anchorRelation: string = relation.indexOf(':') !== -1 ? relation.split(':')[Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), anchor) ? 0 : 1] : relation;
const group = anchorGroups.get(anchorRelation);
anchorGroups.set(anchorRelation, group ? [...group, link] : [link]);
} else {
@@ -229,18 +241,48 @@ export class LinkManager {
if (Doc.AreProtosEqual(DocCast(anchor?.annotationOn, anchor), DocCast(a1?.annotationOn, a1))) return '1';
if (Doc.AreProtosEqual(DocCast(anchor?.annotationOn, anchor), DocCast(a2?.annotationOn, a2))) return '2';
if (Doc.AreProtosEqual(anchor, linkDoc)) return '0';
-
- // const a1 = DocCast(linkDoc.link_anchor_1);
- // const a2 = DocCast(linkDoc.link_anchor_2);
- // if (linkDoc.link_matchEmbeddings) {
- // return [a2, a2.annotationOn].includes(anchor) ? '2' : '1';
- // }
- // if (Doc.AreProtosEqual(a2, anchor) || Doc.AreProtosEqual(a2.annotationOn as Doc, anchor)) return '2';
- // return Doc.AreProtosEqual(a1, anchor) || Doc.AreProtosEqual(a1.annotationOn as Doc, anchor) ? '1' : '2';
+ return undefined;
}
}
+let cacheDocumentIds = ''; // ; separate string of all documents ids in the user's working set (cached on the server)
+export function UPDATE_SERVER_CACHE() {
+ const prototypes = Object.values(DocumentType)
+ .filter(type => type !== DocumentType.NONE)
+ .map(type => DocServer.GetCachedRefField(type + 'Proto'))
+ .filter(doc => doc instanceof Doc)
+ .map(doc => doc as Doc);
+ const references = new Set<Doc>(prototypes);
+ Doc.FindReferences(Doc.UserDoc(), references, undefined);
+ DocListCast(DocCast(Doc.UserDoc().myLinkDatabase).data).forEach(link => {
+ if (!references.has(DocCast(link.link_anchor_1)) && !references.has(DocCast(link.link_anchor_2))) {
+ Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myLinkDatabase), 'data', link);
+ Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link);
+ }
+ });
+ LinkManager.Instance.userLinkDBs.forEach(linkDb => Doc.FindReferences(linkDb, references, undefined));
+ const filtered = Array.from(references);
+
+ const newCacheUpdate = filtered.map(doc => doc[Id]).join(';');
+ if (newCacheUpdate === cacheDocumentIds) return;
+ cacheDocumentIds = newCacheUpdate;
+
+ // print out cached docs
+ Doc.MyDockedBtns.linearView_IsOpen && console.log('Set cached docs = ');
+ const isFiltered = filtered.filter(doc => !Doc.IsSystem(doc));
+ const strings = isFiltered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)'));
+ Doc.MyDockedBtns.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
+
+ rp.post(ClientUtils.prepend('/setCacheDocumentIds'), {
+ body: {
+ cacheDocumentIds,
+ },
+ json: true,
+ });
+}
+
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function links(doc: any) {
return new List(LinkManager.Links(doc));
},
diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts
index 7638e2ce0..255e9cee0 100644
--- a/src/client/util/PingManager.ts
+++ b/src/client/util/PingManager.ts
@@ -1,8 +1,10 @@
import { action, makeObservable, observable, runInAction } from 'mobx';
import { Networking } from '../Network';
-import { CurrentUserUtils } from './CurrentUserUtils';
+import { SnappingManager } from './SnappingManager';
+
export class PingManager {
// create static instance and getter for global use
+ // eslint-disable-next-line no-use-before-define
@observable static _instance: PingManager;
@observable IsBeating = true;
static get Instance(): PingManager {
@@ -29,7 +31,9 @@ export class PingManager {
sendPing = async (): Promise<void> => {
try {
const res = await Networking.PostToServer('/ping', { date: new Date() });
- runInAction(() => (CurrentUserUtils.ServerVersion = res.message));
+ runInAction(() => {
+ SnappingManager.SetServerVersion(res.message);
+ });
!this.IsBeating && this.setIsBeating(true);
} catch {
if (this.IsBeating) {
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index 35b1579df..248fda7e3 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -2,18 +2,17 @@ import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { MainViewModal } from '../views/MainViewModal';
-import { SettingsManager } from './SettingsManager';
+import { SnappingManager } from './SnappingManager';
@observer
export class RTFMarkup extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
static Instance: RTFMarkup;
@observable private isOpen = false; // whether the SharingManager modal is open or not
- @action
- public open = () => (this.isOpen = true);
-
- @action
- public close = () => (this.isOpen = false);
+ public setOpen = action((status: boolean) => {
+ this.isOpen = status;
+ });
constructor(props: {}) {
super(props);
@@ -21,20 +20,18 @@ export class RTFMarkup extends React.Component<{}> {
RTFMarkup.Instance = this;
}
- @observable _stats: { [key: string]: any } | undefined = undefined;
-
/**
* @returns the main interface of the SharingManager.
*/
@computed get cheatSheet() {
return (
- <div style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, textAlign: 'initial', height: '100%' }}>
+ <div style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor, textAlign: 'initial', height: '100%' }}>
<p>
- <b style={{ fontSize: 'larger' }}>{`(@wiki:phrase)`}</b>
+ <b style={{ fontSize: 'larger' }}>(@wiki:phrase)</b>
{` display wikipedia page for entered text (terminate with carriage return)`}
</p>
<p>
- <b style={{ fontSize: 'larger' }}>{`(( any text ))`}</b>
+ <b style={{ fontSize: 'larger' }}>(( any text ))</b>
{` submit text to Chat GPT to have results appended afterward`}
</p>
<p>
@@ -132,11 +129,11 @@ export class RTFMarkup extends React.Component<{}> {
render() {
return (
<MainViewModal
- dialogueBoxStyle={{ backgroundColor: SettingsManager.userBackgroundColor, alignContent: 'normal', color: SettingsManager.userColor, padding: '16px' }}
+ dialogueBoxStyle={{ backgroundColor: SnappingManager.userBackgroundColor, alignContent: 'normal', color: SnappingManager.userColor, padding: '16px' }}
contents={this.cheatSheet}
isDisplayed={this.isOpen}
- interactive={true}
- closeOnExternalClick={this.close}
+ interactive
+ closeOnExternalClick={() => this.setOpen(false)}
/>
);
}
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
index b881f18b4..530fcf211 100644
--- a/src/client/util/ReplayMovements.ts
+++ b/src/client/util/ReplayMovements.ts
@@ -2,10 +2,10 @@ 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';
-import { OpenWhereMod } from '../views/nodes/DocumentView';
+import { OpenWhereMod } from '../views/nodes/OpenWhere';
import { VideoBox } from '../views/nodes/VideoBox';
-import { DocumentManager } from './DocumentManager';
import { Movement, Presentation } from './TrackMovements';
+import { DocumentView } from '../views/nodes/DocumentView';
export class ReplayMovements {
private timers: NodeJS.Timeout[] | null;
@@ -14,6 +14,7 @@ export class ReplayMovements {
private isPlaying: boolean;
// create static instance and getter for global use
+ // eslint-disable-next-line no-use-before-define
@observable static _instance: ReplayMovements;
static get Instance(): ReplayMovements {
return ReplayMovements._instance;
@@ -90,7 +91,7 @@ export class ReplayMovements {
loadPresentation = (presentation: Presentation) => {
const { movements } = presentation;
if (movements === null) {
- throw '[recordingApi.ts] followMovements() failed: no presentation data';
+ throw new Error('[recordingApi.ts] followMovements() failed: no presentation data');
}
movements.forEach((movement, i) => {
@@ -105,10 +106,8 @@ export class ReplayMovements {
// returns undefined if the docView isn't open on the screen
getCollectionFFView = (doc: Doc) => {
- const isInView = DocumentManager.Instance.getDocumentView(doc);
- if (isInView) {
- return isInView.ComponentView as CollectionFreeFormView;
- }
+ const isInView = DocumentView.getDocumentView(doc);
+ return isInView?.ComponentView as CollectionFreeFormView;
};
// will open the doc in a tab then return the CollectionFFView that holds it
@@ -119,7 +118,7 @@ export class ReplayMovements {
}
// console.log('openTab', docId, doc);
CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
- const docView = DocumentManager.Instance.getDocumentView(doc);
+ const docView = DocumentView.getDocumentView(doc);
// BUG - this returns undefined if the doc is already open
return docView?.ComponentView as CollectionFreeFormView;
};
@@ -136,9 +135,9 @@ export class ReplayMovements {
if (movements === null) return new Map();
// generate a set of all unique docIds
const docIdtoFirstMove = new Map<Doc, Movement>();
- for (const move of movements) {
+ movements.forEach(move => {
if (!docIdtoFirstMove.has(move.doc)) docIdtoFirstMove.set(move.doc, move);
- }
+ });
return docIdtoFirstMove;
};
@@ -151,10 +150,10 @@ export class ReplayMovements {
// console.info('playMovements', presentation, timeViewed, docIdtoDoc);
if (presentation.movements === null || presentation.movements.length === 0) {
- //|| this.playFFView === null) {
- return new Error('[recordingApi.ts] followMovements() failed: no presentation data');
+ // || this.playFFView === null) {
+ return '[recordingApi.ts] followMovements() failed: no presentation data';
}
- if (this.isPlaying) return;
+ if (this.isPlaying) return undefined;
this.isPlaying = true;
Doc.UserDoc().presentationMode = 'watching';
@@ -170,10 +169,10 @@ export class ReplayMovements {
// for the open tabs, set it to the first move
const docIdtoFirstMove = this.getFirstMovements(filteredMovements);
- for (const [doc, firstMove] of docIdtoFirstMove) {
+ Array.from(docIdtoFirstMove).forEach(([doc, firstMove]) => {
const colFFView = this.getCollectionFFView(doc);
if (colFFView) this.zoomAndPan(firstMove, colFFView);
- }
+ });
};
handleFirstMovements();
@@ -197,5 +196,6 @@ export class ReplayMovements {
}
}, timeDiff);
});
+ return undefined;
};
}
diff --git a/src/client/util/ScriptManager.ts b/src/client/util/ScriptManager.ts
index 87509f2ea..9158f6c0b 100644
--- a/src/client/util/ScriptManager.ts
+++ b/src/client/util/ScriptManager.ts
@@ -7,8 +7,10 @@ import { ScriptingGlobals } from './ScriptingGlobals';
export class ScriptManager {
static _initialized = false;
+ // eslint-disable-next-line no-use-before-define
private static _instance: ScriptManager;
public static get Instance(): ScriptManager {
+ // eslint-disable-next-line no-return-assign
return this._instance || (this._instance = new this());
}
private constructor() {
@@ -58,14 +60,16 @@ export class ScriptManager {
const params = Cast(scriptDoc['data-params'], listSpec('string'), []);
const paramNames = params.reduce((o: string, p: string) => {
+ let out = o;
if (params.indexOf(p) === params.length - 1) {
- o = o + p.split(':')[0].trim();
+ out += p.split(':')[0].trim();
} else {
- o = o + p.split(':')[0].trim() + ',';
+ out += p.split(':')[0].trim() + ',';
}
- return o;
+ return out;
}, '' as string);
+ // eslint-disable-next-line no-new-func
const f = new Function(paramNames, StrCast(scriptDoc.script));
Object.defineProperty(f, 'name', { value: StrCast(scriptDoc.name), writable: false });
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 422e708bc..6948469cc 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -1,15 +1,17 @@
+/* eslint-disable import/no-unresolved */
+/* eslint-disable import/no-webpack-loader-syntax */
// export const ts = (window as any).ts;
// // @ts-ignore
// import * as typescriptlib from '!!raw-loader!../../../node_modules/typescript/lib/lib.d.ts'
-// // @ts-ignore
// import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts'
-// @ts-ignore
+// eslint-disable-next-line node/no-unpublished-import
import * as typescriptlib from '!!raw-loader!./type_decls.d';
import * as ts from 'typescript';
-import { Doc, Field } from '../../fields/Doc';
+import { Doc, FieldType } from '../../fields/Doc';
import { RefField } from '../../fields/RefField';
import { ScriptField } from '../../fields/ScriptField';
-import { scriptingGlobals, ScriptingGlobals } from './ScriptingGlobals';
+import { ScriptingGlobals, scriptingGlobals } from './ScriptingGlobals';
+
export { ts };
export interface ScriptSuccess {
@@ -30,6 +32,7 @@ export type ScriptParam = { [name: string]: string };
export interface CompiledScript {
readonly compiled: true;
readonly originalScript: string;
+ // eslint-disable-next-line no-use-before-define
readonly options: Readonly<ScriptOptions>;
run(args?: { [name: string]: any }, onError?: (res: any) => void, errorVal?: any): ScriptResult;
}
@@ -47,6 +50,7 @@ export function isCompileError(toBeDetermined: CompileResult): toBeDetermined is
return false;
}
+// eslint-disable-next-line no-use-before-define
function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult {
const errors = diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error);
if ((options.typecheck !== false && errors.length) || !script) {
@@ -60,6 +64,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
// let params: any[] = [Docs, ...fieldTypes];
const compiledFunction = (() => {
try {
+ // eslint-disable-next-line no-new-func
return new Function(...paramNames, `return ${script}`);
} catch (e) {
console.log(e);
@@ -68,20 +73,17 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
})();
if (!compiledFunction) return { compiled: false, errors };
const { capturedVariables = {} } = options;
+ // eslint-disable-next-line default-param-last
const run = (args: { [name: string]: any } = {}, onError?: (e: any) => void, errorVal?: any): ScriptResult => {
const argsArray: any[] = [];
+ // eslint-disable-next-line no-restricted-syntax
for (const name of customParams) {
- if (name === 'this') {
- continue;
- }
- if (name in args) {
- argsArray.push(args[name]);
- } else {
- argsArray.push(capturedVariables[name]);
+ if (name !== 'this') {
+ argsArray.push(name in args ? args[name] : capturedVariables[name]);
}
}
const thisParam = args.this || capturedVariables.this;
- let batch: { end(): void } | undefined = undefined;
+ let batch: { end(): void } | undefined;
try {
if (!options.editable) {
batch = Doc.MakeReadOnly();
@@ -109,7 +111,7 @@ class ScriptingCompilerHost {
files: File[] = [];
// getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined): ts.SourceFile | undefined {
- getSourceFile(fileName: string, languageVersion: any, onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined): any | undefined {
+ getSourceFile(fileName: string, languageVersion: any /* , onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined */): any | undefined {
const contents = this.readFile(fileName);
if (contents !== undefined) {
return ts.createSourceFile(fileName, contents, languageVersion, true);
@@ -118,11 +120,11 @@ class ScriptingCompilerHost {
}
// getDefaultLibFileName(options: ts.CompilerOptions): string {
- getDefaultLibFileName(options: any): string {
+ getDefaultLibFileName(/* options: any */): string {
return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means...
}
writeFile(fileName: string, content: string) {
- const file = this.files.find(file => file.fileName === fileName);
+ const file = this.files.find(f => f.fileName === fileName);
if (file) {
file.content = content;
} else {
@@ -145,7 +147,7 @@ class ScriptingCompilerHost {
return this.files.some(file => file.fileName === fileName);
}
readFile(fileName: string): string | undefined {
- const file = this.files.find(file => file.fileName === fileName);
+ const file = this.files.find(f => f.fileName === fileName);
if (file) {
return file.content;
}
@@ -157,7 +159,7 @@ export type Traverser = (node: ts.Node, indentation: string) => boolean | void;
export type TraverserParam = Traverser | { onEnter: Traverser; onLeave: Traverser };
export type Transformer = {
transformer: ts.TransformerFactory<ts.Node>;
- getVars?: () => { [name: string]: Field };
+ getVars?: () => { [name: string]: FieldType };
};
export interface ScriptOptions {
requiredType?: string; // does function required a typed return value
@@ -193,7 +195,6 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
if (found) return found as CompiledScript;
const { requiredType = '', addReturn = false, params = {}, capturedVariables = {}, typecheck = true } = options;
if (options.params && !options.params.this) options.params.this = Doc.name;
- if (options.params && !options.params.self) options.params.self = Doc.name;
if (options.globals) {
ScriptingGlobals.setScriptingGlobals(options.globals);
}
@@ -210,12 +211,13 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
const newCaptures = options.transformer.getVars?.();
if (Object.keys(newCaptures ?? {}).length) {
// tslint:disable-next-line: prefer-object-spread
- //options.capturedVariables = Object.assign(capturedVariables, newCaptures!) as any;
+ // options.capturedVariables = Object.assign(capturedVariables, newCaptures!) as any;
- const transformed = result.transformed;
+ const { transformed } = result;
const printer = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed,
});
+ // eslint-disable-next-line no-param-reassign
script = printer.printFile(transformed[0].getSourceFile());
}
result.dispose();
@@ -224,19 +226,23 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
if ('this' in params || 'this' in capturedVariables) {
paramNames.push('this');
}
+ // eslint-disable-next-line no-restricted-syntax
for (const key in params) {
- if (key === 'this') continue;
- paramNames.push(key);
+ if (key !== 'this') {
+ paramNames.push(key);
+ }
}
const paramList = paramNames.map(key => {
const val = params[key];
return `${key}: ${val}`;
});
+ // eslint-disable-next-line no-restricted-syntax
for (const key in capturedVariables) {
- if (key === 'this') continue;
- const val = capturedVariables[key];
- paramNames.push(key);
- paramList.push(`${key}: ${typeof val === 'object' ? Object.getPrototypeOf(val).constructor.name : typeof val}`);
+ if (key !== 'this') {
+ const val = capturedVariables[key];
+ paramNames.push(key);
+ paramList.push(`${key}: ${typeof val === 'object' ? Object.getPrototypeOf(val).constructor.name : typeof val}`);
+ }
}
const paramString = paramList.join(', ');
const body = addReturn && !script.startsWith('{ return') ? `return ${script};` : script;
@@ -259,8 +265,3 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
!signature.includes('XXX') && ScriptField._scriptFieldCache.set(script + ':' + signature, result as CompiledScript);
return result;
}
-
-ScriptingGlobals.add(CompileScript);
-ScriptingGlobals.add(function runScript(doc: Doc, script: ScriptField) {
- return script?.script.run({ this: doc }).result;
-});
diff --git a/src/client/util/ScriptingGlobals.ts b/src/client/util/ScriptingGlobals.ts
index f151acd81..ac524394a 100644
--- a/src/client/util/ScriptingGlobals.ts
+++ b/src/client/util/ScriptingGlobals.ts
@@ -1,8 +1,18 @@
+import ts from 'typescript';
-import * as ts from "typescript";
export { ts };
+const _scriptingGlobals: { [name: string]: any } = {};
+const _scriptingDescriptions: { [name: string]: any } = {};
+const _scriptingParams: { [name: string]: any } = {};
+// eslint-disable-next-line import/no-mutable-exports
+export let scriptingGlobals: { [name: string]: any } = _scriptingGlobals;
export namespace ScriptingGlobals {
+ export function getGlobals() { return Object.keys(_scriptingGlobals); } // prettier-ignore
+ export function getGlobalObj() { return _scriptingGlobals; } // prettier-ignore
+ export function getDescriptions() { return _scriptingDescriptions; } // prettier-ignore
+ export function getParameters() { return _scriptingParams; } // prettier-ignore
+
export function add(global: { name: string }): void;
export function add(name: string, global: any): void;
export function add(global: { name: string }, decription?: string, params?: string): void;
@@ -11,7 +21,7 @@ export namespace ScriptingGlobals {
let obj: any;
if (second !== undefined) {
- if (typeof first === "string") {
+ if (typeof first === 'string') {
n = first;
obj = second;
} else {
@@ -22,18 +32,21 @@ export namespace ScriptingGlobals {
_scriptingParams[n] = third;
}
}
- } else if (first && typeof first.name === "string") {
+ } else if (first && typeof first.name === 'string') {
n = first.name;
obj = first;
} else {
- throw new Error("Must either register an object with a name, or give a name and an object");
+ throw new Error('Must either register an object with a name, or give a name and an object');
}
- if (n === undefined || n === "undefined") {
+ if (n === undefined || n === 'undefined') {
return false;
- } else if (_scriptingGlobals.hasOwnProperty(n)) {
+ }
+ // eslint-disable-next-line no-prototype-builtins
+ if (_scriptingGlobals.hasOwnProperty(n)) {
throw new Error(`Global with name ${n} is already registered, choose another name`);
}
_scriptingGlobals[n] = obj;
+ return true;
}
export function makeMutableGlobalsCopy(globals?: { [name: string]: any }) {
return { ..._scriptingGlobals, ...(globals || {}) };
@@ -57,25 +70,16 @@ export namespace ScriptingGlobals {
return false;
}
- export function resetScriptingGlobals() { scriptingGlobals = _scriptingGlobals; }
+ export function resetScriptingGlobals() {
+ scriptingGlobals = _scriptingGlobals;
+ }
// const types = Object.keys(ts.SyntaxKind).map(kind => ts.SyntaxKind[kind]);
- export function printNodeType(node: any, indentation = "") { console.log(indentation + ts.SyntaxKind[node.kind]); }
-
- export function getGlobals() { return Object.keys(_scriptingGlobals); }
-
- export function getGlobalObj() { return _scriptingGlobals; }
-
- export function getDescriptions() { return _scriptingDescriptions; }
-
- export function getParameters() { return _scriptingParams; }
+ export function printNodeType(node: any, indentation = '') {
+ console.log(indentation + ts.SyntaxKind[node.kind]);
+ }
}
-export function scriptingGlobal(constructor: { new(...args: any[]): any }) {
+export function scriptingGlobal(constructor: { new (...args: any[]): any }) {
ScriptingGlobals.add(constructor);
}
-
-const _scriptingGlobals: { [name: string]: any } = {};
-export let scriptingGlobals: { [name: string]: any } = _scriptingGlobals;
-const _scriptingDescriptions: { [name: string]: any } = {};
-const _scriptingParams: { [name: string]: any } = {}; \ No newline at end of file
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index fff2737b6..609fedfa9 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -1,5 +1,5 @@
import { ObservableMap } from 'mobx';
-import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, Opt } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { StrCast } from '../../fields/Types';
import { DocumentType } from '../documents/DocumentTypes';
@@ -8,16 +8,16 @@ import { DocOptions, FInfo } from '../documents/Documents';
export namespace SearchUtil {
export type HighlightingResult = { [id: string]: { [key: string]: string[] } };
- export function SearchCollection(collectionDoc: Opt<Doc>, query: string, matchKeyNames: boolean, onlyKeys?: string[]) {
+ export function SearchCollection(collectionDoc: Opt<Doc>, queryIn: string, matchKeyNames: boolean, onlyKeys?: string[]) {
const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
const blockedKeys = matchKeyNames
? []
: Object.entries(DocOptions)
- .filter(([key, info]: [string, FInfo]) => !info?.searchable())
+ .filter(([, info]: [string, FInfo]) => !info?.searchable())
.map(([key]) => key);
- const exact = query.startsWith('=');
- query = query.toLowerCase().split('=').lastElement();
+ const exact = queryIn.startsWith('=');
+ const query = queryIn.toLowerCase().split('=').lastElement();
const results = new ObservableMap<Doc, string[]>();
if (collectionDoc) {
@@ -30,7 +30,7 @@ export namespace SearchUtil {
(onlyKeys ?? SearchUtil.documentKeys(doc)).forEach(
key =>
(val => (exact ? val === query.toLowerCase() : val.includes(query.toLowerCase())))(
- matchKeyNames ? key : Field.toString(doc[key] as Field))
+ matchKeyNames ? key : Field.toString(doc[key] as FieldType))
&& hlights.add(key)
); // prettier-ignore
blockedKeys.forEach(key => hlights.delete(key));
@@ -51,7 +51,11 @@ export namespace SearchUtil {
*/
export function documentKeys(doc: Doc) {
const keys: { [key: string]: boolean } = {};
- Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false)));
+ Doc.GetAllPrototypes(doc).map(proto =>
+ Object.keys(proto).forEach(key => {
+ keys[key] = false;
+ })
+ );
return Array.from(Object.keys(keys));
}
@@ -62,16 +66,18 @@ export namespace SearchUtil {
* This method iterates through an array of docs and all docs within those docs, calling
* the function func on each doc.
*/
- export function foreachRecursiveDoc(docs: Doc[], func: (depth: number, doc: Doc) => void) {
+ export function foreachRecursiveDoc(docsIn: Doc[], func: (depth: number, doc: Doc) => void) {
+ let docs = docsIn;
let newarray: Doc[] = [];
- var depth = 0;
+ let depth = 0;
const visited: Doc[] = [];
while (docs.length > 0) {
newarray = [];
+ // eslint-disable-next-line no-loop-func
docs.filter(d => d && !visited.includes(d)).forEach(d => {
visited.push(d);
const fieldKey = Doc.LayoutFieldKey(d);
- const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView');
+ const annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes('CollectionView');
const data = d[annos ? fieldKey + '_annotations' : fieldKey];
data && newarray.push(...DocListCast(data));
const sidebar = d[fieldKey + '_sidebar'];
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 36b926053..0b942116c 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,6 +1,5 @@
import { action, makeObservable, observable, runInAction } from 'mobx';
import { Doc, Opt } from '../../fields/Doc';
-import { DocViews } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
import { listSpec } from '../../fields/Schema';
import { Cast, DocCast } from '../../fields/Types';
@@ -11,6 +10,7 @@ import { ScriptingGlobals } from './ScriptingGlobals';
import { UndoManager } from './UndoManager';
export class SelectionManager {
+ // eslint-disable-next-line no-use-before-define
private static _manager: SelectionManager;
private static get Instance() {
return SelectionManager._manager ?? new SelectionManager();
@@ -23,6 +23,13 @@ export class SelectionManager {
private constructor() {
SelectionManager._manager = this;
makeObservable(this);
+ DocumentView.DeselectAll = SelectionManager.DeselectAll;
+ DocumentView.DeselectView = SelectionManager.DeselectView;
+ DocumentView.SelectView = SelectionManager.SelectView;
+ DocumentView.SelectedDocs = SelectionManager.Docs;
+ DocumentView.Selected = SelectionManager.Views;
+ DocumentView.SelectSchemaDoc = SelectionManager.SelectSchemaViewDoc;
+ DocumentView.SelectedSchemaDoc = () => this.SelectedSchemaDocument;
}
@action
@@ -53,35 +60,41 @@ export class SelectionManager {
public static DeselectAll = (except?: Doc): void => {
const found = this.Instance.SelectedViews.find(dv => dv.Document === except);
runInAction(() => {
- LinkManager.Instance.currentLink = undefined;
- LinkManager.Instance.currentLinkAnchor = undefined;
+ if (LinkManager.Instance) {
+ LinkManager.Instance.currentLink = undefined;
+ LinkManager.Instance.currentLinkAnchor = undefined;
+ }
this.Instance.SelectedSchemaDocument = undefined;
});
this.Instance.SelectedViews.forEach(dv => {
dv.IsSelected = false;
dv._props.whenChildContentsActiveChanged(false);
});
- runInAction(() => (this.Instance.SelectedViews.length = 0));
+ runInAction(() => {
+ this.Instance.SelectedViews.length = 0;
+ });
if (found) this.SelectView(found, false);
};
- public static IsSelected = (doc?: Doc) => Array.from(doc?.[DocViews] ?? []).some(dv => dv?.IsSelected);
- 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
+ public static Views() { return SelectionManager.Instance.SelectedViews; } // prettier-ignore
+ public static get SelectedSchemaDoc() { return SelectionManager.Instance.SelectedSchemaDocument; } // prettier-ignore
+ public static Docs() { return SelectionManager.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) {
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function SelectedDocType(type: string, expertMode: boolean, checkContext?: boolean) {
if (Doc.noviceMode && expertMode) return false;
if (type === 'tab') {
- return SelectionManager.Views.lastElement()?._props.renderDepth === 0;
+ return DocumentView.Selected().lastElement()?._props.renderDepth === 0;
}
- let selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc ?? SelectionManager.Docs.lastElement());
+ const selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(DocumentView.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
return selected?.type === type || selected?.type_collection === type || !type;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function deselectAll() {
SelectionManager.DeselectAll();
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function undo() {
SelectionManager.DeselectAll();
return UndoManager.Undo();
@@ -89,20 +102,20 @@ ScriptingGlobals.add(function undo() {
export function ShowUndoStack() {
SelectionManager.DeselectAll();
- var buffer = '';
+ let buffer = '';
UndoManager.undoStack.forEach((batch, i) => {
buffer += 'Batch => ' + UndoManager.undoStackNames[i] + '\n';
- ///batch.forEach(undo => (buffer += ' ' + undo.prop + '\n'));
+ // /batch.forEach(undo => (buffer += ' ' + undo.prop + '\n'));
});
alert(buffer);
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function redo() {
SelectionManager.DeselectAll();
return UndoManager.Redo();
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
- const docs = SelectionManager.Views.map(dv => dv.Document).filter(
- d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))
- );
+ const docs = SelectionManager.Docs().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 8daa69890..d9d22437c 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -1,5 +1,5 @@
import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema } from 'serializr';
-import { Field } from '../../fields/Doc';
+// import { Field } from '../../fields/Doc';
let serializing = 0;
export function afterDocDeserialize(cb: (err: any, val: any) => void, err: any, newValue: any) {
@@ -7,12 +7,16 @@ export function afterDocDeserialize(cb: (err: any, val: any) => void, err: any,
cb(err, newValue);
serializing--;
}
+
+const serializationTypes: { [name: string]: { ctor: { new (): any }; afterDeserialize?: (obj: any) => void | Promise<any> } } = {};
+const reverseMap: { [ctor: string]: string } = {};
+
export namespace SerializationHelper {
export function IsSerializing() {
return serializing > 0;
}
- export function Serialize(obj: Field): any {
+ export function Serialize(obj: any /* Field */): any {
if (obj === undefined || obj === null) {
return null;
}
@@ -24,7 +28,7 @@ export namespace SerializationHelper {
serializing++;
if (!(obj.constructor.name in reverseMap)) {
serializing--;
- throw Error('Error: ' + `type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`);
+ throw Error(`Error: type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`);
}
const json = serialize(obj);
@@ -52,30 +56,29 @@ export namespace SerializationHelper {
}
const type = serializationTypes[obj.__type];
- const value = await new Promise(res => deserialize(type.ctor, obj, (err, result) => res(result)));
+ const value = await new Promise(res => {
+ deserialize(type.ctor, obj, (err, result) => res(result));
+ });
type.afterDeserialize?.(value);
return value;
}
}
-const serializationTypes: { [name: string]: { ctor: { new (): any }; afterDeserialize?: (obj: any) => void | Promise<any> } } = {};
-const reverseMap: { [ctor: string]: string } = {};
-
-export function Deserializable(className: string, afterDeserialize?: (obj: any) => void | Promise<any>, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void {
- function addToMap(className: string, ctor: { new (...args: any[]): any }) {
- const schema = getDefaultModelSchema(ctor) as any;
- if (schema.targetClass !== ctor || constructorArgs) {
- setDefaultModelSchema(ctor, { ...schema, factory: (context: any) => new ctor(...(constructorArgs ?? [])?.map(arg => context.json[arg])) });
+export function Deserializable(classNameForSerializer: string, afterDeserialize?: (obj: any) => void | Promise<any>, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void {
+ function addToMap(className: string, Ctor: { new (...args: any[]): any }) {
+ const schema = getDefaultModelSchema(Ctor) as any;
+ if (schema.targetClass !== Ctor || constructorArgs) {
+ setDefaultModelSchema(Ctor, { ...schema, factory: (context: any) => new Ctor(...(constructorArgs ?? []).map(arg => context.json[arg])) });
}
if (!(className in serializationTypes)) {
- serializationTypes[className] = { ctor, afterDeserialize };
- reverseMap[ctor.name] = className;
+ serializationTypes[className] = { ctor: Ctor, afterDeserialize };
+ reverseMap[Ctor.name] = className;
} else {
throw new Error(`Name ${className} has already been registered as deserializable`);
}
}
- return (ctor: { new (...args: any[]): any }) => addToMap(className, ctor);
+ return (ctor: { new (...args: any[]): any }) => addToMap(classNameForSerializer, ctor);
}
export function autoObject(): PropSchema {
diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx
index c8df9182d..57363663d 100644
--- a/src/client/util/ServerStats.tsx
+++ b/src/client/util/ServerStats.tsx
@@ -1,44 +1,28 @@
-import { action, computed, makeObservable, observable } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { MainViewModal } from '../views/MainViewModal';
import './SharingManager.scss';
import { PingManager } from './PingManager';
-import { StrCast } from '../../fields/Types';
-import { Doc } from '../../fields/Doc';
import { SettingsManager } from './SettingsManager';
@observer
export class ServerStats extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: ServerStats;
@observable private isOpen = false; // whether the SharingManager modal is open or not
+ @observable _stats: { [key: string]: any } | undefined = undefined;
// private get linkVisible() {
- // return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
+ // return this.targetDoc ? this.targetDoc['acl_' + PublicKey] !== SharingPermissions.None : false;
// }
- @action
- public open = async () => {
- /**
- * Populates the list of users.
- */
- fetch('/stats').then((res: Response) => res.text().then(action(stats => (this._stats = JSON.parse(stats)))));
-
- this.isOpen = true;
- };
-
- public close = action(() => {
- this.isOpen = false;
- });
-
constructor(props: {}) {
super(props);
makeObservable(this);
ServerStats.Instance = this;
}
- @observable _stats: { [key: string]: any } | undefined = undefined;
-
/**
* @returns the main interface of the SharingManager.
*/
@@ -63,7 +47,28 @@ export class ServerStats extends React.Component<{}> {
);
}
+ // eslint-disable-next-line react/sort-comp
+ public close = action(() => {
+ this.isOpen = false;
+ });
+ public open = async () => {
+ /**
+ * Populates the list of users.
+ */
+ fetch('/stats').then((res: Response) =>
+ res.text().then(
+ action(stats => {
+ this._stats = JSON.parse(stats);
+ })
+ )
+ );
+
+ runInAction(() => {
+ this.isOpen = true;
+ });
+ };
+
render() {
- return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} />;
+ return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive closeOnExternalClick={this.close} />;
}
}
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 8594a1c92..d3c10f9f4 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -1,23 +1,24 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, ColorPicker, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, makeObservable, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { BsGoogle } from 'react-icons/bs';
import { FaFillDrip, FaPalette } from 'react-icons/fa';
-import { Utils, addStyleSheet, addStyleSheetRule } from '../../Utils';
-import { Doc, Opt } from '../../fields/Doc';
+import { ClientUtils, addStyleSheet, addStyleSheetRule } from '../../ClientUtils';
+import { Doc } from '../../fields/Doc';
import { DashVersion } from '../../fields/DocSymbols';
import { BoolCast, Cast, NumCast, StrCast } from '../../fields/Types';
import { DocServer } from '../DocServer';
import { Networking } from '../Network';
import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
-import { GestureOverlay } from '../views/GestureOverlay';
import { MainViewModal } from '../views/MainViewModal';
-import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
import { GroupManager } from './GroupManager';
import './SettingsManager.scss';
-import { undoBatch } from './UndoManager';
+import { SnappingManager, freeformScrollMode } from './SnappingManager';
+import { undoable } from './UndoManager';
export enum ColorScheme {
Dark = 'Dark',
@@ -27,60 +28,124 @@ export enum ColorScheme {
Cupcake = 'Cupcake',
}
-export enum freeformScrollMode {
- Pan = 'pan',
- Zoom = 'zoom',
-}
-
@observer
export class SettingsManager extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: SettingsManager;
static _settingsStyle = addStyleSheet();
- @observable public isOpen = false;
- @observable private passwordResultText = '';
- @observable private playgroundMode = false;
+ @observable private _passwordResultText = '';
+ @observable private _playgroundMode = false;
+
+ @observable private _curr_password = '';
+ @observable private _new_password = '';
+ @observable private _new_confirm = '';
+ @observable private _activeTab = 'Accounts';
+ @observable private _isOpen = false;
- @observable private curr_password = '';
- @observable private new_password = '';
- @observable private new_confirm = '';
- @observable private _lastPressedSidebarBtn: Opt<Doc> = undefined; // bcz: this is a hack to handle highlighting buttons in the leftpanel menu .. need to find a cleaner approach
- @observable activeTab = 'Accounts';
+ private googleAuthorize = action(() => GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true));
- @observable public propertiesWidth: number = 0;
+ public closeMgr = action(() => {
+ this._isOpen = false;
+ });
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ public openMgr = action(() => {
+ this._isOpen = true;
+ });
+
+ private matchSystem = undoable(() => {
+ if (Doc.UserDoc().userThemeSystem) {
+ if (window.matchMedia('(prefers-color-scheme: dark)').matches) this.changeColorScheme(ColorScheme.Dark);
+ if (window.matchMedia('(prefers-color-scheme: light)').matches) this.changeColorScheme(ColorScheme.Light);
+ }
+ }, 'match system theme');
+ private setFreeformScrollMode = undoable((mode: string) => {
+ Doc.UserDoc().freeformScrollMode = mode;
+ }, 'set scroll mode');
+ private selectUserMode = undoable((mode: string) => {
+ Doc.noviceMode = mode === 'Novice';
+ }, 'change user mode');
+ private changeFontFamily = undoable((font: string) => {
+ Doc.UserDoc().fontFamily = font;
+ }, 'change font family');
+ private switchUserBackgroundColor = undoable((color: string) => {
+ Doc.UserDoc().userBackgroundColor = color;
+ addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${color} !important` });
+ }, 'change background color');
+ private switchUserColor = undoable((color: string) => {
+ Doc.UserDoc().userColor = color;
+ }, 'change user color');
+ switchUserVariantColor = undoable((color: string) => {
+ Doc.UserDoc().userVariantColor = color;
+ }, 'change variant color');
+ userThemeSystemToggle = undoable(() => {
+ Doc.UserDoc().userThemeSystem = !Doc.UserDoc().userThemeSystem;
+ this.matchSystem();
+ }, 'change theme color');
+ playgroundModeToggle = undoable(
+ action(() => {
+ this._playgroundMode = !this._playgroundMode;
+ if (this._playgroundMode) {
+ DocServer.Control.makeReadOnly();
+ addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' });
+ } else ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Control.makeEditable();
+ }),
+ 'set playgorund mode'
+ );
+ changeColorScheme = undoable(
+ action((scheme: string) => {
+ Doc.UserDoc().userTheme = scheme;
+ switch (scheme) {
+ case ColorScheme.Light:
+ this.switchUserColor('#323232');
+ this.switchUserBackgroundColor('#DFDFDF');
+ this.switchUserVariantColor('#BDDDF5');
+ break;
+ case ColorScheme.Dark:
+ this.switchUserColor('#DFDFDF');
+ this.switchUserBackgroundColor('#323232');
+ this.switchUserVariantColor('#4476F7');
+ break;
+ case ColorScheme.CoolBlue:
+ this.switchUserColor('#ADEAFF');
+ this.switchUserBackgroundColor('#060A15');
+ this.switchUserVariantColor('#3C51FF');
+ break;
+ case ColorScheme.Cupcake:
+ this.switchUserColor('#3BC7FF');
+ this.switchUserBackgroundColor('#fffdf7');
+ this.switchUserVariantColor('#FFD7F3');
+ break;
+ case ColorScheme.Custom:
+ break;
+ default:
+ }
+ }),
+ 'change color scheme'
+ );
constructor(props: {}) {
super(props);
makeObservable(this);
SettingsManager.Instance = this;
this.matchSystem();
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
if (Doc.UserDoc().userThemeSystem) {
if (window.matchMedia('(prefers-color-scheme: dark)').matches) this.changeColorScheme(ColorScheme.Dark);
if (window.matchMedia('(prefers-color-scheme: light)').matches) this.changeColorScheme(ColorScheme.Light);
}
// undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
});
+ reaction(
+ () => [SettingsManager.userBackgroundColor, SettingsManager.userColor, SettingsManager.userVariantColor],
+ ([back, user, variant]) => {
+ SnappingManager.userBackgroundColor = back;
+ SnappingManager.userVariantColor = variant;
+ SnappingManager.userColor = user;
+ },
+ { fireImmediately: true }
+ );
+ SnappingManager.SettingsStyle = SettingsManager._settingsStyle;
}
- matchSystem = () => {
- if (Doc.UserDoc().userThemeSystem) {
- if (window.matchMedia('(prefers-color-scheme: dark)').matches) this.changeColorScheme(ColorScheme.Dark);
- if (window.matchMedia('(prefers-color-scheme: light)').matches) this.changeColorScheme(ColorScheme.Light);
- }
- };
-
- public close = action(() => (this.isOpen = false));
- public open = action(() => (this.isOpen = true));
-
- private googleAuthorize = action(() => GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true));
- private changePassword = async () => {
- if (!(this.curr_password && this.new_password && this.new_confirm)) {
- runInAction(() => (this.passwordResultText = "Error: Hey, we're missing some fields!"));
- } else {
- const passwordBundle = { curr_pass: this.curr_password, new_pass: this.new_password, new_confirm: this.new_confirm };
- const { error } = await Networking.PostToServer('/internalResetPassword', passwordBundle);
- runInAction(() => (this.passwordResultText = error ? 'Error: ' + error[0].msg + '...' : 'Password successfully updated!'));
- }
- };
@computed public static get userColor() {
return StrCast(Doc.UserDoc().userColor);
@@ -94,60 +159,6 @@ export class SettingsManager extends React.Component<{}> {
return StrCast(Doc.UserDoc().userBackgroundColor);
}
- public get LastPressedBtn() { return this._lastPressedSidebarBtn; } // prettier-ignore
- public SetLastPressedBtn = (state?:Doc) => runInAction(() => (this._lastPressedSidebarBtn = state)); // prettier-ignore
-
- @undoBatch selectUserMode = action((mode: string) => (Doc.noviceMode = mode === 'Novice'));
- @undoBatch changelayout_showTitle = action((e: React.ChangeEvent) => (Doc.UserDoc().layout_showTitle = (e.currentTarget as any).value ? 'title' : undefined));
- @undoBatch changeFontFamily = action((font: string) => (Doc.UserDoc().fontFamily = font));
- @undoBatch changeFontSize = action((val: number) => (Doc.UserDoc().fontSize = val));
- @undoBatch switchUserBackgroundColor = action((color: string) => {
- Doc.UserDoc().userBackgroundColor = color;
- addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${color} !important` });
- });
- @undoBatch switchUserColor = action((color: string) => (Doc.UserDoc().userColor = color));
- @undoBatch switchUserVariantColor = action((color: string) => (Doc.UserDoc().userVariantColor = color));
- @undoBatch userThemeSystemToggle = action(() => {
- Doc.UserDoc().userThemeSystem = !Doc.UserDoc().userThemeSystem;
- this.matchSystem();
- });
- @undoBatch playgroundModeToggle = action(() => {
- this.playgroundMode = !this.playgroundMode;
- if (this.playgroundMode) {
- DocServer.Control.makeReadOnly();
- addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' });
- } else Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable();
- });
-
- @undoBatch
- changeColorScheme = action((scheme: string) => {
- Doc.UserDoc().userTheme = scheme;
- switch (scheme) {
- case ColorScheme.Light:
- this.switchUserColor('#323232');
- this.switchUserBackgroundColor('#DFDFDF');
- this.switchUserVariantColor('#BDDDF5');
- break;
- case ColorScheme.Dark:
- this.switchUserColor('#DFDFDF');
- this.switchUserBackgroundColor('#323232');
- this.switchUserVariantColor('#4476F7');
- break;
- case ColorScheme.CoolBlue:
- this.switchUserColor('#ADEAFF');
- this.switchUserBackgroundColor('#060A15');
- this.switchUserVariantColor('#3C51FF');
- break;
- case ColorScheme.Cupcake:
- this.switchUserColor('#3BC7FF');
- this.switchUserBackgroundColor('#fffdf7');
- this.switchUserVariantColor('#FFD7F3');
- break;
- case ColorScheme.Custom:
- break;
- }
- });
-
@computed get colorsContent() {
const schemeMap = Array.from(Object.keys(ColorScheme));
const userTheme = StrCast(Doc.UserDoc().userTheme);
@@ -176,7 +187,7 @@ export class SettingsManager extends React.Component<{}> {
{userTheme === ColorScheme.Custom && (
<Group formLabel="Custom Theme">
<ColorPicker
- tooltip={'User Color'} //
+ tooltip="User Color" //
color={SettingsManager.userColor}
type={Type.SEC}
icon={<FaFillDrip />}
@@ -185,7 +196,7 @@ export class SettingsManager extends React.Component<{}> {
setFinalColor={this.switchUserColor}
/>
<ColorPicker
- tooltip={'User Background Color'}
+ tooltip="User Background Color"
color={SettingsManager.userColor}
type={Type.SEC}
icon={<FaPalette />}
@@ -194,7 +205,7 @@ export class SettingsManager extends React.Component<{}> {
setFinalColor={this.switchUserBackgroundColor}
/>
<ColorPicker
- tooltip={'User Variant Color'}
+ tooltip="User Variant Color"
color={SettingsManager.userColor}
type={Type.SEC}
icon={<FaPalette />}
@@ -212,79 +223,84 @@ export class SettingsManager extends React.Component<{}> {
return (
<div className="prefs-content">
<Toggle
- formLabel={'Show document header'}
- formLabelPlacement={'right'}
+ formLabel="Show document header"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date')}
+ onClick={() => {
+ Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date';
+ }}
toggleStatus={Doc.UserDoc().layout_showTitle !== undefined}
size={Size.XSMALL}
color={SettingsManager.userColor}
/>
<Toggle
- formLabel={'Show Full Toolbar'}
- formLabelPlacement={'right'}
- toggleType={ToggleType.SWITCH}
- onClick={e => (Doc.UserDoc()['documentLinksButton-fullMenu'] = !Doc.UserDoc()['documentLinksButton-fullMenu'])}
- toggleStatus={BoolCast(Doc.UserDoc()['documentLinksButton-fullMenu'])}
- size={Size.XSMALL}
- color={SettingsManager.userColor}
- />
- <Toggle
- formLabel={'Show Button Labels'}
- formLabelPlacement={'right'}
+ formLabel="Show Full Toolbar"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (FontIconBox.ShowIconLabels = !FontIconBox.ShowIconLabels)}
- toggleStatus={FontIconBox.ShowIconLabels}
+ onClick={() => {
+ Doc.UserDoc().documentLinksButton_fullMenu = !Doc.UserDoc().documentLinksButton_fullMenu;
+ }}
+ toggleStatus={BoolCast(Doc.UserDoc().documentLinksButton_fullMenu)}
size={Size.XSMALL}
color={SettingsManager.userColor}
/>
<Toggle
- formLabel={'Recognize Ink Gestures'}
- formLabelPlacement={'right'}
+ formLabel="Recognize Ink Gestures"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (GestureOverlay.RecognizeGestures = !GestureOverlay.RecognizeGestures)}
- toggleStatus={GestureOverlay.RecognizeGestures}
+ onClick={() => {
+ Doc.UserDoc().recognizeGestures = !Doc.UserDoc().recognizeGestures;
+ }}
+ toggleStatus={BoolCast(Doc.UserDoc().recognizeGestures)}
size={Size.XSMALL}
color={SettingsManager.userColor}
/>
<Toggle
- formLabel={'Hide Labels In Ink Shapes'}
- formLabelPlacement={'right'}
+ formLabel="Hide Labels In Ink Shapes"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels)}
+ onClick={() => {
+ Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels;
+ }}
toggleStatus={BoolCast(Doc.UserDoc().activeInkHideTextLabels)}
size={Size.XSMALL}
color={SettingsManager.userColor}
/>
<Toggle
- formLabel={'Open Ink Docs in Lightbox'}
- formLabelPlacement={'right'}
+ formLabel="Open Ink Docs in Lightbox"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox)}
+ onClick={() => {
+ Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox;
+ }}
toggleStatus={BoolCast(Doc.UserDoc().openInkInLightbox)}
size={Size.XSMALL}
color={SettingsManager.userColor}
/>
- <Toggle
- formLabel={'Show Link Lines'}
- formLabelPlacement={'right'}
+ {/* <Toggle
+ formLabel="Show Link Lines"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (Doc.UserDoc().showLinkLines = !Doc.UserDoc().showLinkLines)}
+ onClick={() => {
+ Doc.UserDoc().showLinkLines = !Doc.UserDoc().showLinkLines;
+ }}
toggleStatus={BoolCast(Doc.UserDoc().showLinkLines)}
size={Size.XSMALL}
color={SettingsManager.userColor}
- />
+ /> */}
<Group formLabel="Title Height">
<NumberDropdown
number={NumCast(Doc.UserDoc().headerHeight, 30)}
color={SettingsManager.userColor}
- numberDropdownType={'slider'}
+ numberDropdownType="slider"
min={6}
max={60}
step={2}
type={Type.TERT}
- unit={'px'}
- setNumber={val => console.log('GOT: ' + (Doc.UserDoc().headerHeight = val))}
+ unit="px"
+ setNumber={val => {
+ Doc.UserDoc().headerHeight = val;
+ }}
/>
</Group>
</div>
@@ -308,7 +324,6 @@ export class SettingsManager extends React.Component<{}> {
@computed get textContent() {
const fontFamilies = ['Times New Roman', 'Arial', 'Georgia', 'Comic Sans MS', 'Tahoma', 'Impact', 'Crimson Text', 'Roboto'];
- const fontSizes = ['7px', '8px', '9px', '10px', '12px', '14px', '16px', '18px', '20px', '24px', '32px', '48px', '72px'];
return (
<div className="tab-content appearances-content">
@@ -316,7 +331,7 @@ export class SettingsManager extends React.Component<{}> {
<div className="tab-column-title">Text</div>
<div className="tab-column-content">
{/* <NumberInput/> */}
- <Group formLabel={'Default Font'}>
+ <Group formLabel="Default Font">
<NumberDropdown
color={SettingsManager.userColor}
numberDropdownType="slider"
@@ -325,20 +340,20 @@ export class SettingsManager extends React.Component<{}> {
step={2}
type={Type.PRIM}
number={NumCast(Doc.UserDoc().fontSize, Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')))}
- unit={'px'}
- setNumber={val => (Doc.UserDoc().fontSize = val + 'px')}
+ unit="px"
+ setNumber={val => {
+ Doc.UserDoc().fontSize = val + 'px';
+ }}
/>
<Dropdown
- items={fontFamilies.map(val => {
- return {
- text: val,
- val: val,
- style: {
- fontFamily: val,
- },
- };
- })}
- closeOnSelect={true}
+ items={fontFamilies.map(val => ({
+ text: val,
+ val: val,
+ style: {
+ fontFamily: val,
+ },
+ }))}
+ closeOnSelect
dropdownType={DropdownType.SELECT}
type={Type.TERT}
selectedVal={StrCast(Doc.UserDoc().fontFamily)}
@@ -355,30 +370,15 @@ export class SettingsManager extends React.Component<{}> {
);
}
- @action
- changeVal = (value: string, pass: string) => {
- switch (pass) {
- case 'curr':
- this.curr_password = value;
- break;
- case 'new':
- this.new_password = value;
- break;
- case 'conf':
- this.new_confirm = value;
- break;
- }
- };
-
@computed get passwordContent() {
return (
<div className="password-content">
- <EditableText placeholder="Current password" type={Type.SEC} color={SettingsManager.userColor} val={''} setVal={val => this.changeVal(val as string, 'curr')} fillWidth password />
- <EditableText placeholder="New password" type={Type.SEC} color={SettingsManager.userColor} val={''} setVal={val => this.changeVal(val as string, 'new')} fillWidth password />
- <EditableText placeholder="Confirm new password" type={Type.SEC} color={SettingsManager.userColor} val={''} setVal={val => this.changeVal(val as string, 'conf')} fillWidth password />
- {!this.passwordResultText ? null : <div className={`${this.passwordResultText.startsWith('Error') ? 'error' : 'success'}-text`}>{this.passwordResultText}</div>}
- <Button type={Type.SEC} text={'Forgot Password'} color={SettingsManager.userColor} />
- <Button type={Type.TERT} text={'Submit'} onClick={this.changePassword} color={SettingsManager.userColor} />
+ <EditableText placeholder="Current password" type={Type.SEC} color={SettingsManager.userColor} val="" setVal={val => this.changeVal(val as string, 'curr')} fillWidth password />
+ <EditableText placeholder="New password" type={Type.SEC} color={SettingsManager.userColor} val="" setVal={val => this.changeVal(val as string, 'new')} fillWidth password />
+ <EditableText placeholder="Confirm new password" type={Type.SEC} color={SettingsManager.userColor} val="" setVal={val => this.changeVal(val as string, 'conf')} fillWidth password />
+ {!this._passwordResultText ? null : <div className={`${this._passwordResultText.startsWith('Error') ? 'error' : 'success'}-text`}>{this._passwordResultText}</div>}
+ <Button type={Type.SEC} text="Forgot Password" color={SettingsManager.userColor} />
+ <Button type={Type.TERT} text="Submit" onClick={this.changePassword} color={SettingsManager.userColor} />
</div>
);
}
@@ -386,7 +386,7 @@ export class SettingsManager extends React.Component<{}> {
@computed get accountOthersContent() {
return (
<div className="account-others-content">
- <Button type={Type.TERT} text={'Connect to Google'} iconPlacement="left" icon={<BsGoogle />} onClick={() => this.googleAuthorize()} />
+ <Button type={Type.TERT} text="Connect to Google" iconPlacement="left" icon={<BsGoogle />} onClick={() => this.googleAuthorize()} />
</div>
);
}
@@ -406,10 +406,6 @@ export class SettingsManager extends React.Component<{}> {
);
}
- setFreeformScrollMode = (mode: string) => {
- Doc.UserDoc().freeformScrollMode = mode;
- };
-
@computed get modesContent() {
return (
<div className="tab-content modes-content">
@@ -417,8 +413,8 @@ export class SettingsManager extends React.Component<{}> {
<div className="tab-column-title">Modes</div>
<div className="tab-column-content">
<Dropdown
- formLabel={'Mode'}
- closeOnSelect={true}
+ formLabel="Mode"
+ closeOnSelect
items={[
{
text: 'Novice',
@@ -442,7 +438,7 @@ export class SettingsManager extends React.Component<{}> {
color={SettingsManager.userColor}
fillWidth
/>
- <Toggle formLabel={'Playground Mode'} toggleType={ToggleType.SWITCH} toggleStatus={this.playgroundMode} onClick={this.playgroundModeToggle} color={SettingsManager.userColor} />
+ <Toggle formLabel="Playground Mode" toggleType={ToggleType.SWITCH} toggleStatus={this._playgroundMode} onClick={this.playgroundModeToggle} color={SettingsManager.userColor} />
</div>
<div className="tab-column-title" style={{ marginTop: 20, marginBottom: 10 }}>
Freeform Navigation
@@ -450,7 +446,7 @@ export class SettingsManager extends React.Component<{}> {
<div className="tab-column-content">
<Dropdown
formLabel="Scroll Mode"
- closeOnSelect={true}
+ closeOnSelect
items={[
{
text: 'Scroll to Pan',
@@ -475,16 +471,34 @@ export class SettingsManager extends React.Component<{}> {
<div className="tab-column">
<div className="tab-column-title">Permissions</div>
<div className="tab-column-content">
- <Button text={'Manage Groups'} type={Type.TERT} onClick={() => GroupManager.Instance?.open()} color={SettingsManager.userColor} />
+ <Button text="Manage Groups" type={Type.TERT} onClick={() => GroupManager.Instance?.open()} color={SettingsManager.userColor} />
<Toggle
toggleType={ToggleType.SWITCH}
- formLabel={'Default access private'}
+ formLabel="Default access private"
color={SettingsManager.userColor}
toggleStatus={BoolCast(Doc.defaultAclPrivate)}
- onClick={action(() => (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))}
+ onClick={action(() => {
+ Doc.defaultAclPrivate = !Doc.defaultAclPrivate;
+ })}
+ />
+ <Toggle
+ toggleType={ToggleType.SWITCH}
+ formLabel="Enable Sharing UI"
+ color={SettingsManager.userColor}
+ toggleStatus={BoolCast(Doc.IsSharingEnabled)}
+ onClick={action(() => {
+ Doc.IsSharingEnabled = !Doc.IsSharingEnabled;
+ })}
+ />
+ <Toggle
+ toggleType={ToggleType.SWITCH}
+ formLabel="Disable Info UI"
+ color={SettingsManager.userColor}
+ toggleStatus={BoolCast(Doc.IsInfoUIDisabled)}
+ onClick={action(() => {
+ Doc.IsInfoUIDisabled = !Doc.IsInfoUIDisabled;
+ })}
/>
- <Toggle toggleType={ToggleType.SWITCH} formLabel="Enable Sharing UI" color={SettingsManager.userColor} toggleStatus={BoolCast(Doc.IsSharingEnabled)} onClick={action(() => (Doc.IsSharingEnabled = !Doc.IsSharingEnabled))} />
- <Toggle toggleType={ToggleType.SWITCH} formLabel="Disable Info UI" color={SettingsManager.userColor} toggleStatus={BoolCast(Doc.IsInfoUIDisabled)} onClick={action(() => (Doc.IsInfoUIDisabled = !Doc.IsInfoUIDisabled))} />
</div>
</div>
</div>
@@ -506,7 +520,7 @@ export class SettingsManager extends React.Component<{}> {
<div className="settings-panel" style={{ background: SettingsManager.userColor }}>
<div className="settings-tabs">
{tabs.map(tab => {
- const isActive = this.activeTab === tab.title;
+ const isActive = this._activeTab === tab.title;
return (
<div
key={tab.title}
@@ -515,7 +529,9 @@ export class SettingsManager extends React.Component<{}> {
color: isActive ? SettingsManager.userColor : SettingsManager.userBackgroundColor,
}}
className={'tab-control ' + (isActive ? 'active' : 'inactive')}
- onClick={action(() => (this.activeTab = tab.title))}>
+ onClick={action(() => {
+ this._activeTab = tab.title;
+ })}>
{tab.title}
</div>
);
@@ -525,19 +541,19 @@ export class SettingsManager extends React.Component<{}> {
<div className="settings-user">
<div style={{ color: SettingsManager.userBackgroundColor }}>{DashVersion}</div>
<div className="settings-username" style={{ color: SettingsManager.userBackgroundColor }}>
- {Doc.CurrentUserEmail}
+ {ClientUtils.CurrentUserEmail()}
</div>
- <Button text={Doc.GuestDashboard ? 'Exit' : 'Log Out'} type={Type.TERT} color={SettingsManager.userVariantColor} onClick={() => window.location.assign(Utils.prepend('/logout'))} />
+ <Button text={Doc.GuestDashboard ? 'Exit' : 'Log Out'} type={Type.TERT} color={SettingsManager.userVariantColor} onClick={() => window.location.assign(ClientUtils.prepend('/logout'))} />
</div>
</div>
<div className="close-button">
- <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={SettingsManager.userColor} />
+ <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={this.closeMgr} color={SettingsManager.userColor} />
</div>
<div className="settings-content" style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}>
{tabs.map(tab => (
- <div key={tab.title} className={'tab-section ' + (this.activeTab === tab.title ? 'active' : 'inactive')}>
+ <div key={tab.title} className={'tab-section ' + (this._activeTab === tab.title ? 'active' : 'inactive')}>
{tab.ele}
</div>
))}
@@ -546,13 +562,37 @@ export class SettingsManager extends React.Component<{}> {
);
}
+ private changePassword = async () => {
+ if (!(this._curr_password && this._new_password && this._new_confirm)) {
+ runInAction(() => {
+ this._passwordResultText = "Error: Hey, we're missing some fields!";
+ });
+ } else {
+ const passwordBundle = { curr_pass: this._curr_password, new_pass: this._new_password, new_confirm: this._new_confirm };
+ const { error } = await Networking.PostToServer('/internalResetPassword', passwordBundle);
+ runInAction(() => {
+ this._passwordResultText = error ? 'Error: ' + error[0].msg + '...' : 'Password successfully updated!';
+ });
+ }
+ };
+
+ @action
+ changeVal = (value: string, pass: string) => {
+ switch (pass) {
+ case 'curr': this._curr_password = value; break;
+ case 'new': this._new_password = value; break;
+ case 'conf': this._new_confirm = value; break;
+ default:
+ } // prettier-ignore
+ };
+
render() {
return (
<MainViewModal
contents={this.settingsInterface}
- isDisplayed={this.isOpen}
- interactive={true}
- closeOnExternalClick={this.close}
+ isDisplayed={this._isOpen}
+ interactive
+ closeOnExternalClick={this.closeMgr}
dialogueBoxStyle={{ width: 'fit-content', height: '300px', background: Cast(Doc.UserDoc().userColor, 'string', null) }}
/>
);
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index fddf735e3..c2a52cae9 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, IconButton, Size, Type } from 'browndash-components';
import { concat, intersection } from 'lodash';
@@ -6,25 +9,23 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import Select from 'react-select';
import * as RequestPromise from 'request-promise';
+import { ClientUtils } from '../../ClientUtils';
+import { Utils } from '../../Utils';
import { Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
import { AclAdmin, AclPrivate, DocAcl, DocData } from '../../fields/DocSymbols';
import { FieldLoader } from '../../fields/FieldLoader';
import { Id } from '../../fields/FieldSymbols';
import { StrCast } from '../../fields/Types';
-import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from '../../fields/util';
-import { Utils } from '../../Utils';
+import { GetEffectiveAcl, SharingPermissions, TraceMobx, distributeAcls, normalizeEmail } from '../../fields/util';
import { DocServer } from '../DocServer';
-import { DictationOverlay } from '../views/DictationOverlay';
import { MainViewModal } from '../views/MainViewModal';
import { DocumentView } from '../views/nodes/DocumentView';
import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
-import { DocumentManager } from './DocumentManager';
import { GroupManager, UserOptions } from './GroupManager';
import { GroupMemberView } from './GroupMemberView';
import { SearchUtil } from './SearchUtil';
-import { SelectionManager } from './SelectionManager';
-import { SettingsManager } from './SettingsManager';
import './SharingManager.scss';
+import { SnappingManager } from './SnappingManager';
import { undoable } from './UndoManager';
export interface User {
@@ -64,63 +65,36 @@ interface ValidatedUser {
@observer
export class SharingManager extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: SharingManager;
+ private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the share button, used for the position of the popup
+ private populating: boolean = false; // whether the list of users is populating or not
@observable private isOpen = false; // whether the SharingManager modal is open or not
@observable public users: ValidatedUser[] = []; // the list of users with sharing docs
@observable private targetDoc: Doc | undefined = undefined; // the document being shared
@observable private targetDocView: DocumentView | undefined = undefined; // the DocumentView of the document being shared
// @observable private copied = false;
@observable private dialogueBoxOpacity = 1; // for the modal
- @observable private overlayOpacity = 0.4; // for the modal
@observable private selectedUsers: UserOptions[] | null = null; // users (individuals/groups) selected to share with
@observable private permissions: SharingPermissions = SharingPermissions.Edit; // the permission with which to share with other users
@observable private individualSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of individuals
@observable private groupSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of groups
- private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the share button, used for the position of the popup
// if both showUserOptions and showGroupOptions are false then both are displayed
@observable private showUserOptions: boolean = false; // whether to show individuals as options when sharing (in the react-select component)
@observable private showGroupOptions: boolean = false; // // whether to show groups as options when sharing (in the react-select component)
- private populating: boolean = false; // whether the list of users is populating or not
@observable private upgradeNested: boolean = false; // whether child docs in a collection/dashboard should be changed to be less private - initially selected so default is upgrade all
@observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used
@observable private myDocAcls: boolean = false; // whether the My Docs checkbox is selected or not
- @observable private _buttonDown = false;
// private get linkVisible() {
- // return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
+ // return this.targetDoc ? this.targetDoc['acl_' + PublicKey] !== SharingPermissions.None : false;
// }
- public open = (target?: DocumentView, target_doc?: Doc) => {
- this.populateUsers();
- runInAction(() => {
- this.targetDocView = target;
- this.targetDoc = target_doc || target?.Document;
- DictationOverlay.Instance.hasActiveModal = true;
- this.isOpen = this.targetDoc !== undefined;
- this.permissions = SharingPermissions.Augment;
- this.upgradeNested = true;
- });
- };
-
- public close = action(() => {
- this.isOpen = false;
- this.selectedUsers = null; // resets the list of users and selected users (in the react-select component)
- TaskCompletionBox.taskCompleted = false;
- setTimeout(
- action(() => {
- // this.copied = false;
- DictationOverlay.Instance.hasActiveModal = false;
- this.targetDoc = undefined;
- }),
- 500
- );
- this.layoutDocAcls = false;
- });
-
constructor(props: {}) {
super(props);
makeObservable(this);
SharingManager.Instance = this;
+ DocumentView.ShareOpen = this.open;
}
/**
@@ -131,223 +105,6 @@ export class SharingManager extends React.Component<{}> {
}
/**
- * Populates the list of validated users (this.users) by adding registered users which have a sharingDocument.
- */
- populateUsers = async () => {
- if (!this.populating && Doc.UserDoc()[Id] !== Utils.GuestID()) {
- this.populating = true;
- const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
- const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== Doc.CurrentUserEmail);
- runInAction(() => (FieldLoader.ServerLoadStatus.message = 'users'));
- const docs = await DocServer.GetRefFields(raw.reduce((list, user) => [...list, user.sharingDocumentId, user.linkDatabaseId], [] as string[]));
- raw.map(
- action((newUser: User) => {
- const sharingDoc = docs[newUser.sharingDocumentId];
- const linkDatabase = docs[newUser.linkDatabaseId];
- if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) {
- if (!this.users.find(user => user.user.email === newUser.email)) {
- this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) });
- //LinkManager.addLinkDB(linkDatabase);
- }
- }
- })
- );
- this.populating = false;
- }
- };
-
- /**
- * Shares the document with a user.
- */
- setInternalSharing = undoable((recipient: ValidatedUser, permission: string, targetDoc: Doc | undefined) => {
- const { user, sharingDoc } = recipient;
- const target = targetDoc || this.targetDoc!;
- const acl = `acl-${normalizeEmail(user.email)}`;
- const docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document);
- docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
- distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
- if (permission !== SharingPermissions.None) {
- Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc);
- } else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc);
- });
- }, 'set Doc permissions');
-
- /**
- * Sets the permission on the target for the group.
- * @param group
- * @param permission
- */
- setInternalGroupSharing = undoable((group: Doc | { title: string }, permission: string, targetDoc?: Doc) => {
- const target = targetDoc || this.targetDoc!;
- const acl = `acl-${normalizeEmail(StrCast(group.title))}`;
-
- const docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document);
- docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
- distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
-
- if (group instanceof Doc) {
- Doc.AddDocToList(group, 'docsShared', doc);
-
- this.users
- .filter(({ user: { email } }) => JSON.parse(StrCast(group.members)).includes(email))
- .forEach(({ user, sharingDoc }) => {
- if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc); // add the doc to the sharingDoc if it hasn't already been added
- else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc); // remove the doc from the list if it already exists
- });
- }
- });
- }, 'set group permissions');
-
- /**
- * Shares the documents shared with a group with a new user who has been added to that group.
- * @param group
- * @param emailId
- */
- shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => {
- const user = this.users.find(({ user: { email } }) => email === emailId)!;
- const self = this;
- if (group.docsShared) {
- if (!user) retry && this.populateUsers().then(() => self.shareWithAddedMember(group, emailId, false));
- else {
- DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
- DocListCastAsync(group.docsShared).then(dl => {
- const filtered = dl?.filter(doc => !doc.dockingConfig && !userdocs?.includes(doc));
- filtered && userdocs?.push(...filtered);
- })
- );
- DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs =>
- DocListCastAsync(group.docsShared).then(dl => {
- const filtered = dl?.filter(doc => doc.dockingConfig && !userdocs?.includes(doc));
- filtered && userdocs?.push(...filtered);
- })
- );
- }
- }
- };
-
- /**
- * Called from the properties sidebar to change permissions of a user.
- */
- shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => {
- if (layout) this.layoutDocAcls = true;
- if (shareWith !== 'Guest') {
- const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? Doc.CurrentUserEmail : shareWith));
- docs.forEach(doc => {
- if (user) this.setInternalSharing(user, permission, doc);
- else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc, undefined, true);
- });
- } else {
- docs.forEach(doc => {
- if (GetEffectiveAcl(doc) === AclAdmin) {
- distributeAcls(`acl-${shareWith}`, permission, doc, undefined);
- }
- });
- }
- this.layoutDocAcls = false;
- }, 'sidebar set permissions');
-
- /**
- * Removes the documents shared with a user through a group when the user is removed from the group.
- * @param group
- * @param emailId
- */
- removeMember = (group: Doc, emailId: string) => {
- const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
-
- if (group.docsShared && user) {
- DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
- DocListCastAsync(group.docsShared).then(dl => {
- const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || [];
- userdocs?.splice(0, userdocs.length, ...remaining);
- })
- );
- DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs =>
- DocListCastAsync(group.docsShared).then(dl => {
- const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || [];
- userdocs?.splice(0, userdocs.length, ...remaining);
- })
- );
- }
- };
-
- /**
- * Removes a group's permissions from documents that have been shared with it.
- * @param group
- */
- removeGroup = (group: Doc) => {
- if (group.docsShared) {
- DocListCast(group.docsShared).forEach(doc => {
- const acl = `acl-${StrCast(group.title)}`;
- distributeAcls(acl, SharingPermissions.None, doc);
-
- const members: string[] = JSON.parse(StrCast(group.members));
- const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
-
- users.forEach(({ sharingDoc }) => Doc.RemoveDocFromList(sharingDoc, storage, doc));
- });
- }
- };
-
- // private setExternalSharing = (permission: string) => {
- // const targetDoc = this.targetDoc;
- // if (!targetDoc) {
- // return;
- // }
- // targetDoc["acl-" + PublicKey] = permission;
- // }s
-
- /**
- * Copies the Public sharing url to the user's clipboard.
- */
- private copyURL = (e: any) => {
- Utils.CopyText(Utils.shareUrl(this.targetDoc![Id]));
- };
-
- /**
- * Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share
- */
- private sharingOptions(uniform: boolean, showGuestOptions?: boolean) {
- const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : Object.values(SharingPermissions);
- if (!uniform) dropdownValues.unshift('-multiple-');
- return dropdownValues.map(permission => (
- <option key={permission} value={permission}>
- {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
- </option>
- ));
- }
-
- private focusOn = (contents: string) => {
- const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
- const docs = SelectionManager.Views.length > 1 ? SelectionManager.Views.map(docView => docView.props.Document) : [this.targetDoc];
- return (
- <span
- className="focus-span"
- title={title}
- onClick={() => {
- if (this.targetDoc && this.targetDocView && docs.length === 1) {
- DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true });
- }
- }}
- onPointerEnter={action(() => {
- if (docs.length) {
- docs.forEach(doc => doc && Doc.BrushDoc(doc));
- this.dialogueBoxOpacity = 0.1;
- this.overlayOpacity = 0.1;
- }
- })}
- onPointerLeave={action(() => {
- if (docs.length) {
- docs.forEach(doc => doc && Doc.UnBrushDoc(doc));
- this.dialogueBoxOpacity = 1;
- this.overlayOpacity = 0.4;
- }
- })}>
- {contents}
- </span>
- );
- };
-
- /**
* Handles changes in the users selected in react-select
*/
@action
@@ -366,57 +123,6 @@ export class SharingManager extends React.Component<{}> {
);
/**
- * Calls the relevant method for sharing, displays the popup, and resets the relevant variables.
- */
- share = undoable(
- action(() => {
- if (this.selectedUsers) {
- this.selectedUsers.forEach(user => {
- if (user.value.includes(indType)) {
- this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions, undefined);
- } else {
- this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
- }
- });
-
- if (this.shareDocumentButtonRef.current) {
- const { left, width, top, height } = this.shareDocumentButtonRef.current.getBoundingClientRect();
- TaskCompletionBox.popupX = left - 1.5 * width;
- TaskCompletionBox.popupY = top - 1.5 * height;
- TaskCompletionBox.textDisplayed = 'Document shared!';
- TaskCompletionBox.taskCompleted = true;
- setTimeout(
- action(() => (TaskCompletionBox.taskCompleted = false)),
- 2000
- );
- }
-
- this.layoutDocAcls = false;
- this.selectedUsers = null;
- }
- }),
- 'share Doc'
- );
-
- /**
- * Sorting algorithm to sort users.
- */
- sortUsers = (u1: ValidatedUser, u2: ValidatedUser) => {
- const { email: e1 } = u1.user;
- const { email: e2 } = u2.user;
- return e1 < e2 ? -1 : e1 === e2 ? 0 : 1;
- };
-
- /**
- * Sorting algorithm to sort groups.
- */
- sortGroups = (group1: Doc, group2: Doc) => {
- const g1 = StrCast(group1.title);
- const g2 = StrCast(group2.title);
- return g1 < g2 ? -1 : g1 === g2 ? 0 : 1;
- };
-
- /**
* @returns the main interface of the SharingManager.
*/
@computed get sharingInterface() {
@@ -445,7 +151,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.Document);
+ let docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().map(docView => docView.Document);
if (this.myDocAcls) {
const newDocs: Doc[] = [];
@@ -464,18 +170,18 @@ export class SharingManager extends React.Component<{}> {
// the list of users shared with
const userListContents = users
- // .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email))
+ // .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl_${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email))
.filter(({ user }) => docs[0]?.author !== user.email)
.map(({ user, linkDatabase, sharingDoc, userColor }) => {
- const userKey = `acl-${normalizeEmail(user.email)}`;
+ const userKey = `acl_${normalizeEmail(user.email)}`;
const uniform = docs.every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]);
// const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
let permissions = targetDoc[DocAcl][userKey] ? HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name : StrCast(targetDoc[userKey]);
permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
return !permissions ? null : (
- <div key={userKey} className={'container'}>
- <span className={'padding'}>{user.email}</span>
+ <div key={userKey} className="container">
+ <span className="padding">{user.email}</span>
<div className="edit-actions">
{admin || this.myDocAcls ? (
<select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value, undefined)}>
@@ -496,21 +202,21 @@ export class SharingManager extends React.Component<{}> {
const sameAuthor = docs.every(doc => doc?.author === docs[0]?.author);
// the owner of the doc and the current user are placed at the top of the user list.
- const userKey = `acl-${normalizeEmail(Doc.CurrentUserEmail)}`;
+ const userKey = `acl_${normalizeEmail(ClientUtils.CurrentUserEmail())}`;
const curUserPermission = StrCast(targetDoc[userKey]);
// const curUserPermission = HierarchyMapping.get(effectiveAcls[0])!.name
userListContents.unshift(
sameAuthor ? (
- <div key={'owner'} className={'container'}>
- <span className="padding">{targetDoc?.author === Doc.CurrentUserEmail ? 'Me' : StrCast(targetDoc?.author)}</span>
+ <div key="owner" className="container">
+ <span className="padding">{targetDoc?.author === ClientUtils.CurrentUserEmail() ? 'Me' : StrCast(targetDoc?.author)}</span>
<div className="edit-actions">
- <div className={'permissions-dropdown'}>Owner</div>
+ <div className="permissions-dropdown">Owner</div>
</div>
</div>
) : null,
- sameAuthor && targetDoc?.author !== Doc.CurrentUserEmail ? (
- <div key={'me'} className={'container'}>
- <span className={'padding'}>Me</span>
+ sameAuthor && targetDoc?.author !== ClientUtils.CurrentUserEmail() ? (
+ <div key="me" className="container">
+ <span className="padding">Me</span>
<div className="edit-actions">
<div className={`permissions-dropdown-${curUserPermission}`}>
{effectiveAcls.every(acl => acl === effectiveAcls[0]) ? concat(ReverseHierarchyMap.get(curUserPermission!)?.image, ' ', curUserPermission) : '-multiple-'}
@@ -522,19 +228,28 @@ export class SharingManager extends React.Component<{}> {
);
// the list of groups shared with
- const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true));
- groupListMap.unshift({ title: 'Guest' }); //, { title: "ALL" });
+ const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl_${normalizeEmail(StrCast(title))}`) : true));
+ groupListMap.unshift({ title: 'Guest' }); // , { title: "ALL" });
const groupListContents = groupListMap.map(group => {
- let groupKey = `acl-${StrCast(group.title)}`;
+ const groupKey = `acl_${StrCast(group.title)}`;
const uniform = docs.every(doc => doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey]);
const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-';
return !permissions ? null : (
- <div key={groupKey} className={'container'} style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
- <div className={'padding'}>{StrCast(group.title)}</div>
+ <div key={groupKey} className="container" style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor }}>
+ <div className="padding">{StrCast(group.title)}</div>
&nbsp;
- {group instanceof Doc ? <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={SettingsManager.userColor} onClick={action(() => (GroupManager.Instance.currentGroup = group))} /> : null}
- <div className={'edit-actions'}>
+ {group instanceof Doc ? (
+ <IconButton
+ icon={<FontAwesomeIcon icon="info-circle" />}
+ size={Size.XSMALL}
+ color={SnappingManager.userColor}
+ onClick={action(() => {
+ GroupManager.Instance.currentGroup = group;
+ })}
+ />
+ ) : null}
+ <div className="edit-actions">
{admin || this.myDocAcls ? (
<select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}>
{this.sharingOptions(uniform, group.title === 'Guest')}
@@ -551,25 +266,32 @@ export class SharingManager extends React.Component<{}> {
});
return (
<div className="sharing-interface">
- {GroupManager.Instance?.currentGroup ? <GroupMemberView group={GroupManager.Instance.currentGroup} onCloseButtonClick={action(() => (GroupManager.Instance.currentGroup = undefined))} /> : null}
+ {GroupManager.Instance?.currentGroup ? (
+ <GroupMemberView
+ group={GroupManager.Instance.currentGroup}
+ onCloseButtonClick={action(() => {
+ GroupManager.Instance.currentGroup = undefined;
+ })}
+ />
+ ) : null}
<div
className="sharing-contents"
style={{
- background: SettingsManager.userBackgroundColor,
+ background: SnappingManager.userBackgroundColor,
color: StrCast(Doc.UserDoc().userColor),
}}>
- <p className="share-title" style={{ color: SettingsManager.userColor }}>
+ <p className="share-title" style={{ color: SnappingManager.userColor }}>
<div className="share-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')}>
- <FontAwesomeIcon icon={'question-circle'} size={'sm'} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')} />
+ <FontAwesomeIcon icon="question-circle" size="sm" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')} />
</div>
<b>Share </b>
{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}
</p>
<div className="share-copy-link">
- <Button type={Type.TERT} color={SettingsManager.userColor} icon={<FontAwesomeIcon icon={'copy'} size="sm" />} iconPlacement={'left'} text={'Copy Guest URL'} onClick={this.copyURL} />
+ <Button type={Type.TERT} color={SnappingManager.userColor} icon={<FontAwesomeIcon icon="copy" size="sm" />} iconPlacement="left" text="Copy Guest URL" onClick={this.copyURL} />
</div>
<div className="close-button">
- <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={SettingsManager.userColor} />
+ <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={this.close} color={SnappingManager.userColor} />
</div>
{admin ? (
<div className="share-container">
@@ -611,19 +333,45 @@ export class SharingManager extends React.Component<{}> {
</select>
</div>
<div className="share-button">
- <Button text={'SHARE'} type={Type.TERT} color={SettingsManager.userColor} onClick={this.share} />
+ <Button text="SHARE" type={Type.TERT} color={SnappingManager.userColor} onClick={this.share} />
</div>
</div>
<div className="sort-checkboxes">
- <input type="checkbox" onChange={action(() => (this.showUserOptions = !this.showUserOptions))} /> <label style={{ marginRight: 10 }}>Individuals</label>
- <input type="checkbox" onChange={action(() => (this.showGroupOptions = !this.showGroupOptions))} /> <label>Groups</label>
+ <input
+ type="checkbox"
+ onChange={action(() => {
+ this.showUserOptions = !this.showUserOptions;
+ })}
+ />{' '}
+ <label style={{ marginRight: 10 }}>Individuals</label>
+ <input
+ type="checkbox"
+ onChange={action(() => {
+ this.showGroupOptions = !this.showGroupOptions;
+ })}
+ />{' '}
+ <label>Groups</label>
</div>
<div className="acl-container">
{Doc.noviceMode ? null : (
<div className="layoutDoc-acls">
- <input type="checkbox" onChange={action(() => (this.upgradeNested = !this.upgradeNested))} checked={this.upgradeNested} /> <label>Upgrade Nested </label>
- <input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
+ <input
+ type="checkbox"
+ onChange={action(() => {
+ this.upgradeNested = !this.upgradeNested;
+ })}
+ checked={this.upgradeNested}
+ />{' '}
+ <label>Upgrade Nested </label>
+ <input
+ type="checkbox"
+ onChange={action(() => {
+ this.layoutDocAcls = !this.layoutDocAcls;
+ })}
+ checked={this.layoutDocAcls}
+ />{' '}
+ <label>Layout</label>
</div>
)}
</div>
@@ -632,14 +380,25 @@ export class SharingManager extends React.Component<{}> {
<div className="share-container">
<div className="acl-container">
<div className="layoutDoc-acls">
- <input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
+ <input
+ type="checkbox"
+ onChange={action(() => {
+ this.layoutDocAcls = !this.layoutDocAcls;
+ })}
+ checked={this.layoutDocAcls}
+ />{' '}
+ <label>Layout</label>
</div>
</div>
</div>
)}
<div className="main-container" style={{ color: StrCast(Doc.UserDoc().userColor), border: StrCast(Doc.UserDoc().userColor) }}>
- <div className={'individual-container'}>
- <div className="user-sort" onClick={action(() => (this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'))}>
+ <div className="individual-container">
+ <div
+ className="user-sort"
+ onClick={action(() => {
+ this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending';
+ })}>
<div className="title-individual">
Individuals
<IconButton
@@ -651,11 +410,15 @@ export class SharingManager extends React.Component<{}> {
</div>
<div className="users-list">{userListContents}</div>
</div>
- <div className={'group-container'}>
- <div className="user-sort" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}>
+ <div className="group-container">
+ <div
+ className="user-sort"
+ onClick={action(() => {
+ this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending';
+ })}>
<div className="title-group">
Groups
- <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => GroupManager.Instance.open())} />
+ <IconButton icon={<FontAwesomeIcon icon="info-circle" />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => GroupManager.Instance.open())} />
<IconButton
icon={<FontAwesomeIcon icon={this.groupSort === 'ascending' ? 'caret-up' : this.groupSort === 'descending' ? 'caret-down' : 'caret-right'} />}
size={Size.XSMALL}
@@ -663,7 +426,7 @@ export class SharingManager extends React.Component<{}> {
/>
</div>
</div>
- <div className={'groups-list'}>{groupListContents}</div>
+ <div className="groups-list">{groupListContents}</div>
</div>
</div>
</div>
@@ -671,7 +434,307 @@ export class SharingManager extends React.Component<{}> {
);
}
+ /**
+ * Shares the document with a user.
+ */
+ setInternalSharing = undoable((recipient: ValidatedUser, permission: string, targetDoc: Doc | undefined) => {
+ const { user, sharingDoc } = recipient;
+ const target = targetDoc || this.targetDoc!;
+ const acl = `acl_${normalizeEmail(user.email)}`;
+ const docs = DocumentView.Selected().length < 2 ? [target] : DocumentView.Selected().map(docView => docView.Document);
+ docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
+ distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
+ if (permission !== SharingPermissions.None) {
+ Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc);
+ } else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc);
+ });
+ }, 'set Doc permissions');
+
+ /**
+ * Sets the permission on the target for the group.
+ * @param group
+ * @param permission
+ */
+ setInternalGroupSharing = undoable((group: Doc | { title: string }, permission: string, targetDoc?: Doc) => {
+ const target = targetDoc || this.targetDoc!;
+ const acl = `acl_${normalizeEmail(StrCast(group.title))}`;
+
+ const docs = DocumentView.Selected().length < 2 ? [target] : DocumentView.Selected().map(docView => docView.Document);
+ docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
+ distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
+
+ if (group instanceof Doc) {
+ Doc.AddDocToList(group, 'docsShared', doc);
+
+ this.users
+ .filter(({ user: { email } }) => JSON.parse(StrCast(group.members)).includes(email))
+ .forEach(({ user, sharingDoc }) => {
+ if (permission !== SharingPermissions.None)
+ Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc); // add the doc to the sharingDoc if it hasn't already been added
+ else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc); // remove the doc from the list if it already exists
+ });
+ }
+ });
+ }, 'set group permissions');
+ /**
+ * Populates the list of validated users (this.users) by adding registered users which have a sharingDocument.
+ */
+ populateUsers = async () => {
+ if (!this.populating && Doc.UserDoc()[Id] !== Utils.GuestID()) {
+ this.populating = true;
+ const userList = await RequestPromise.get(ClientUtils.prepend('/getUsers'));
+ const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== ClientUtils.CurrentUserEmail());
+ runInAction(() => {
+ FieldLoader.ServerLoadStatus.message = 'users';
+ });
+ const docs = await DocServer.GetRefFields(raw.reduce((list, user) => [...list, user.sharingDocumentId, user.linkDatabaseId], [] as string[]));
+ raw.map(
+ action((newUser: User) => {
+ const sharingDoc = docs[newUser.sharingDocumentId];
+ const linkDatabase = docs[newUser.linkDatabaseId];
+ if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) {
+ if (!this.users.find(user => user.user.email === newUser.email)) {
+ this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) });
+ // LinkManager.addLinkDB(linkDatabase);
+ }
+ }
+ })
+ );
+ this.populating = false;
+ }
+ };
+
+ // eslint-disable-next-line react/sort-comp
+ public close = action(() => {
+ this.isOpen = false;
+ this.selectedUsers = null; // resets the list of users and selected users (in the react-select component)
+ TaskCompletionBox.taskCompleted = false;
+ setTimeout(
+ action(() => {
+ // this.copied = false;
+ this.targetDoc = undefined;
+ }),
+ 500
+ );
+ this.layoutDocAcls = false;
+ });
+
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ public open = (target?: DocumentView, targetDoc?: Doc) => {
+ this.populateUsers();
+ runInAction(() => {
+ this.targetDocView = target;
+ this.targetDoc = targetDoc || target?.Document;
+ this.isOpen = this.targetDoc !== undefined;
+ this.permissions = SharingPermissions.Augment;
+ this.upgradeNested = true;
+ });
+ };
+
+ /**
+ * Shares the documents shared with a group with a new user who has been added to that group.
+ * @param group
+ * @param emailId
+ */
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => {
+ const user = this.users.find(({ user: { email } }) => email === emailId)!;
+ const self = this;
+ if (group.docsShared) {
+ if (!user) retry && this.populateUsers().then(() => self.shareWithAddedMember(group, emailId, false));
+ else {
+ DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const filtered = dl?.filter(doc => !doc.dockingConfig && !userdocs?.includes(doc));
+ filtered && userdocs?.push(...filtered);
+ })
+ );
+ DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const filtered = dl?.filter(doc => doc.dockingConfig && !userdocs?.includes(doc));
+ filtered && userdocs?.push(...filtered);
+ })
+ );
+ }
+ }
+ };
+
+ /**
+ * Called from the properties sidebar to change permissions of a user.
+ */
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => {
+ if (layout) this.layoutDocAcls = true;
+ if (shareWith !== 'Guest') {
+ const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? ClientUtils.CurrentUserEmail() : shareWith));
+ docs.forEach(doc => {
+ if (user) this.setInternalSharing(user, permission, doc);
+ else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc, undefined, true);
+ });
+ } else {
+ docs.forEach(doc => {
+ if (GetEffectiveAcl(doc) === AclAdmin) {
+ distributeAcls(`acl_${shareWith}`, permission, doc, undefined);
+ }
+ });
+ }
+ this.layoutDocAcls = false;
+ }, 'sidebar set permissions');
+
+ /**
+ * Removes the documents shared with a user through a group when the user is removed from the group.
+ * @param group
+ * @param emailId
+ */
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ removeMember = (group: Doc, emailId: string) => {
+ const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
+
+ if (group.docsShared && user) {
+ DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || [];
+ userdocs?.splice(0, userdocs.length, ...remaining);
+ })
+ );
+ DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || [];
+ userdocs?.splice(0, userdocs.length, ...remaining);
+ })
+ );
+ }
+ };
+
+ /**
+ * Removes a group's permissions from documents that have been shared with it.
+ * @param group
+ */
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ removeGroup = (group: Doc) => {
+ if (group.docsShared) {
+ DocListCast(group.docsShared).forEach(doc => {
+ const acl = `acl_${StrCast(group.title)}`;
+ distributeAcls(acl, SharingPermissions.None, doc);
+
+ const members: string[] = JSON.parse(StrCast(group.members));
+ const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
+
+ users.forEach(({ sharingDoc }) => Doc.RemoveDocFromList(sharingDoc, storage, doc));
+ });
+ }
+ };
+
+ // private setExternalSharing = (permission: string) => {
+ // const targetDoc = this.targetDoc;
+ // if (!targetDoc) {
+ // return;
+ // }
+ // targetDoc['acl_' + PublicKey] = permission;
+ // }s
+
+ /**
+ * Copies the Public sharing url to the user's clipboard.
+ */
+ private copyURL = () => {
+ ClientUtils.CopyText(ClientUtils.shareUrl(this.targetDoc![Id]));
+ };
+
+ private focusOn = (contents: string) => {
+ const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
+ const docs = DocumentView.Selected().length > 1 ? DocumentView.Selected().map(docView => docView.props.Document) : [this.targetDoc];
+ return (
+ <span
+ className="focus-span"
+ title={title}
+ onClick={() => {
+ if (this.targetDoc && this.targetDocView && docs.length === 1) {
+ DocumentView.showDocument(this.targetDoc, { willZoomCentered: true });
+ }
+ }}
+ onPointerEnter={action(() => {
+ if (docs.length) {
+ docs.forEach(doc => doc && Doc.BrushDoc(doc));
+ this.dialogueBoxOpacity = 0.1;
+ }
+ })}
+ onPointerLeave={action(() => {
+ if (docs.length) {
+ docs.forEach(doc => doc && Doc.UnBrushDoc(doc));
+ this.dialogueBoxOpacity = 1;
+ }
+ })}>
+ {contents}
+ </span>
+ );
+ };
+
+ /**
+ * Calls the relevant method for sharing, displays the popup, and resets the relevant variables.
+ */
+ share = undoable(
+ action(() => {
+ if (this.selectedUsers) {
+ this.selectedUsers.forEach(user => {
+ if (user.value.includes(indType)) {
+ this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions, undefined);
+ } else {
+ this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
+ }
+ });
+
+ if (this.shareDocumentButtonRef.current) {
+ const { left, width, top, height } = this.shareDocumentButtonRef.current.getBoundingClientRect();
+ TaskCompletionBox.popupX = left - 1.5 * width;
+ TaskCompletionBox.popupY = top - 1.5 * height;
+ TaskCompletionBox.textDisplayed = 'Document shared!';
+ TaskCompletionBox.taskCompleted = true;
+ setTimeout(
+ action(() => {
+ TaskCompletionBox.taskCompleted = false;
+ }),
+ 2000
+ );
+ }
+
+ this.layoutDocAcls = false;
+ this.selectedUsers = null;
+ }
+ }),
+ 'share Doc'
+ );
+
+ /**
+ * Sorting algorithm to sort users.
+ */
+ sortUsers = (u1: ValidatedUser, u2: ValidatedUser) => {
+ const { email: e1 } = u1.user;
+ const { email: e2 } = u2.user;
+ return e1 < e2 ? -1 : e1 === e2 ? 0 : 1;
+ };
+
+ /**
+ * Sorting algorithm to sort groups.
+ */
+ sortGroups = (group1: Doc, group2: Doc) => {
+ const g1 = StrCast(group1.title);
+ const g2 = StrCast(group2.title);
+ return g1 < g2 ? -1 : g1 === g2 ? 0 : 1;
+ };
+ /**
+ * Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share
+ */
+ private sharingOptions(uniform: boolean, showGuestOptions?: boolean) {
+ const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : Object.values(SharingPermissions);
+ if (!uniform) dropdownValues.unshift('-multiple-');
+ return dropdownValues.map(permission => (
+ <option key={permission} value={permission}>
+ {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
+ </option>
+ ));
+ }
+
render() {
- return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
+ return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} closeOnExternalClick={this.close} />;
}
}
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index 359140732..6789c2ab8 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -1,7 +1,11 @@
-import { observable, action, runInAction, reaction, makeObservable } from 'mobx';
-import { Doc, Opt } from '../../fields/Doc';
+import { observable, action, runInAction, makeObservable } from 'mobx';
+export enum freeformScrollMode {
+ Pan = 'pan',
+ Zoom = 'zoom',
+}
export class SnappingManager {
+ // eslint-disable-next-line no-use-before-define
private static _manager: SnappingManager;
private static get Instance() {
return SnappingManager._manager ?? new SnappingManager();
@@ -10,20 +14,27 @@ export class SnappingManager {
@observable _shiftKey = false;
@observable _ctrlKey = false;
@observable _metaKey = false;
+ @observable _showPresPaths = false;
@observable _isLinkFollowing = false;
@observable _isDragging: boolean = false;
- @observable _isResizing: Doc | undefined = undefined;
+ @observable _isResizing: string | undefined = undefined; // the string is the Id of the document being resized
@observable _canEmbed: boolean = false;
@observable _horizSnapLines: number[] = [];
@observable _vertSnapLines: number[] = [];
@observable _exploreMode = false;
+ @observable _userPanned = false;
+ @observable _serverVersion: string = '';
+ @observable _lastBtnId: string = '';
+ @observable _propertyWid: number = 0;
private constructor() {
SnappingManager._manager = this;
makeObservable(this);
}
- @action public static clearSnapLines = () => (this.Instance._vertSnapLines.length = this.Instance._horizSnapLines.length = 0);
+ @action public static clearSnapLines = () => {
+ this.Instance._vertSnapLines.length = this.Instance._horizSnapLines.length = 0;
+ };
@action public static addSnapLines = (horizLines: number[], vertLines: number[]) => {
this.Instance._horizSnapLines.push(...horizLines);
this.Instance._vertSnapLines.push(...vertLines);
@@ -34,17 +45,33 @@ export class SnappingManager {
public static get ShiftKey() { return this.Instance._shiftKey; } // prettier-ignore
public static get CtrlKey() { return this.Instance._ctrlKey; } // prettier-ignore
public static get MetaKey() { return this.Instance._metaKey; } // prettier-ignore
+ public static get ShowPresPaths() { return this.Instance._showPresPaths; } // prettier-ignore
public static get IsLinkFollowing(){ return this.Instance._isLinkFollowing; } // prettier-ignore
public static get IsDragging() { return this.Instance._isDragging; } // prettier-ignore
public static get IsResizing() { return this.Instance._isResizing; } // prettier-ignore
public static get CanEmbed() { return this.Instance._canEmbed; } // prettier-ignore
public static get ExploreMode() { return this.Instance._exploreMode; } // prettier-ignore
- public static SetShiftKey = (down: boolean) => runInAction(() => (this.Instance._shiftKey = down)); // prettier-ignore
- public static SetCtrlKey = (down: boolean) => runInAction(() => (this.Instance._ctrlKey = down)); // prettier-ignore
- public static SetMetaKey = (down: boolean) => runInAction(() => (this.Instance._metaKey = down)); // prettier-ignore
- public static SetIsLinkFollowing= (follow:boolean)=> runInAction(() => (this.Instance._isLinkFollowing = follow)); // prettier-ignore
- public static SetIsDragging = (drag: boolean) => runInAction(() => (this.Instance._isDragging = drag)); // prettier-ignore
- public static SetIsResizing = (doc: Opt<Doc>) => runInAction(() => (this.Instance._isResizing = doc)); // prettier-ignore
- public static SetCanEmbed = (embed:boolean) => runInAction(() => (this.Instance._canEmbed = embed)); // prettier-ignore
- public static SetExploreMode = (state:boolean) => runInAction(() => (this.Instance._exploreMode = state)); // prettier-ignore
+ public static get UserPanned() { return this.Instance._userPanned; } // prettier-ignore
+ public static get ServerVersion() { return this.Instance._serverVersion; } // prettier-ignore
+ public static get LastPressedBtn() { return this.Instance._lastBtnId; } // prettier-ignore
+ public static get PropertiesWidth(){ return this.Instance._propertyWid; } // prettier-ignore
+
+ public static SetShiftKey = (down: boolean) => runInAction(() => {this.Instance._shiftKey = down}); // prettier-ignore
+ public static SetCtrlKey = (down: boolean) => runInAction(() => {this.Instance._ctrlKey = down}); // prettier-ignore
+ public static SetMetaKey = (down: boolean) => runInAction(() => {this.Instance._metaKey = down}); // prettier-ignore
+ public static SetShowPresPaths = (paths:boolean) => runInAction(() => {this.Instance._showPresPaths = paths}); // prettier-ignore
+ public static SetIsLinkFollowing= (follow:boolean)=> runInAction(() => {this.Instance._isLinkFollowing = follow}); // prettier-ignore
+ public static SetIsDragging = (drag: boolean) => runInAction(() => {this.Instance._isDragging = drag}); // prettier-ignore
+ public static SetIsResizing = (docid?:string) => runInAction(() => {this.Instance._isResizing = docid}); // prettier-ignore
+ public static SetCanEmbed = (embed:boolean) => runInAction(() => {this.Instance._canEmbed = embed}); // prettier-ignore
+ public static SetExploreMode = (state:boolean) => runInAction(() => {this.Instance._exploreMode = state}); // prettier-ignore
+ public static TriggerUserPanned = () => runInAction(() => {this.Instance._userPanned = !this.Instance._userPanned}); // prettier-ignore
+ public static SetServerVersion = (version:string) =>runInAction(() => {this.Instance._serverVersion = version}); // prettier-ignore
+ public static SetLastPressedBtn = (id:string) =>runInAction(() => {this.Instance._lastBtnId = id}); // prettier-ignore
+ public static SetPropertiesWidth= (wid:number) =>runInAction(() => {this.Instance._propertyWid = wid}); // prettier-ignore
+
+ public static userColor: string | undefined;
+ public static userVariantColor: string | undefined;
+ public static userBackgroundColor: string | undefined;
+ public static SettingsStyle: any;
}
diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts
index f9c2d522f..25a3c9ad8 100644
--- a/src/client/util/TrackMovements.ts
+++ b/src/client/util/TrackMovements.ts
@@ -1,8 +1,7 @@
-import { IReactionDisposer, makeObservable, observable, observe, reaction } from 'mobx';
+import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { NumCast } from '../../fields/Types';
import { Doc, DocListCast } from '../../fields/Doc';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { Id } from '../../fields/FieldSymbols';
import { CollectionViewType } from '../documents/DocumentTypes';
export type Movement = {
@@ -33,6 +32,7 @@ export class TrackMovements {
private tabChangeDisposeFunc: IReactionDisposer | null;
// create static instance and getter for global use
+ // eslint-disable-next-line no-use-before-define
@observable static _instance: TrackMovements;
static get Instance(): TrackMovements {
return TrackMovements._instance;
@@ -92,12 +92,13 @@ export class TrackMovements {
// so that the size comparisons are correct, we must filter to only the FFViews
const isFFView = (doc: Doc) => doc && doc._type_collection === CollectionViewType.Freeform;
const tabbedFFViews = new Set<Doc>();
- for (const DashDoc of tabbedDocs) {
+ tabbedDocs.forEach(DashDoc => {
if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc);
- }
+ });
// new tab was added - need to add it
if (tabbedFFViews.size > this.recordingFFViews.size) {
+ // eslint-disable-next-line no-restricted-syntax
for (const DashDoc of tabbedDocs) {
if (!this.recordingFFViews.has(DashDoc)) {
if (isFFView(DashDoc)) {
@@ -111,6 +112,7 @@ export class TrackMovements {
}
// tab was removed - need to remove it from recordingFFViews
else if (tabbedFFViews.size < this.recordingFFViews.size) {
+ // eslint-disable-next-line no-restricted-syntax
for (const [doc] of this.recordingFFViews) {
if (!tabbedFFViews.has(doc)) {
this.removeRecordingFFView(doc);
@@ -208,11 +210,11 @@ export class TrackMovements {
return;
}
- for (const [id, disposeFunc] of this.recordingFFViews) {
+ Array.from(this.recordingFFViews).forEach(([id, disposeFunc]) => {
// console.info('calling dispose func : docId', id);
disposeFunc();
- this.recordingFFViews.delete(id);
- }
+ this.recordingFFViews?.delete(id);
+ });
};
private trackMovement = (panX: number, panY: number, doc: Doc, scale: number = 0) => {
@@ -241,9 +243,9 @@ export class TrackMovements {
// method that concatenates an array of presentatations into one
public concatPresentations = (presentations: Presentation[]): Presentation => {
// these three will lead to the combined presentation
- let combinedMovements: Movement[] = [];
+ const combinedMovements: Movement[] = [];
let sumTime = 0;
- let combinedMetas: any[] = [];
+ const combinedMetas: any[] = [];
presentations.forEach(presentation => {
const { movements, totalTime, meta } = presentation;
@@ -251,9 +253,7 @@ export class TrackMovements {
// update movements if they had one
if (movements) {
// add the summed time to the movements
- const addedTimeMovements = movements.map(move => {
- return { ...move, time: move.time + sumTime };
- });
+ const addedTimeMovements = movements.map(move => ({ ...move, time: move.time + sumTime }));
// concat the movements already in the combined presentation with these new ones
combinedMovements.push(...addedTimeMovements);
}
diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts
index dca37c960..1a07dd6ae 100644
--- a/src/client/util/Transform.ts
+++ b/src/client/util/Transform.ts
@@ -116,20 +116,14 @@ export class Transform {
preTransformed = (transform: Transform): Transform => this.copy().preTransform(transform);
- transformPoint = (x: number, y: number): [number, number] => {
- x *= this._scale;
- x += this._translateX;
- y *= this._scale;
- y += this._translateY;
- return [x, y];
- };
+ transformPoint = (x: number, y: number): [number, number] => [x * this._scale + this._translateX, y * this._scale + this._translateY];
transformDirection = (x: number, y: number): [number, number] => [x * this._scale, y * this._scale];
transformBounds(x: number, y: number, width: number, height: number): { x: number; y: number; width: number; height: number } {
- [x, y] = this.transformPoint(x, y);
- [width, height] = this.transformDirection(width, height);
- return { x, y, width, height };
+ const [tx, ty] = this.transformPoint(x, y);
+ const [twidth, theight] = this.transformDirection(width, height);
+ return { x: tx, y: ty, width: twidth, height: theight };
}
inverse = () => new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale, -this._rotate);
diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts
index 90fd299c1..9ef2aa8d7 100644
--- a/src/client/util/TypedEvent.ts
+++ b/src/client/util/TypedEvent.ts
@@ -14,27 +14,27 @@ export class TypedEvent<T> {
on = (listener: Listener<T>): Disposable => {
this.listeners.push(listener);
return {
- dispose: () => this.off(listener)
+ dispose: () => this.off(listener),
};
- }
+ };
once = (listener: Listener<T>): void => {
this.listenersOncer.push(listener);
- }
+ };
off = (listener: Listener<T>) => {
const callbackIndex = this.listeners.indexOf(listener);
if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1);
- }
+ };
emit = (event: T) => {
/** Update any general listeners */
- this.listeners.forEach((listener) => listener(event));
+ this.listeners.forEach(listener => listener(event));
/** Clear the `once` queue */
- this.listenersOncer.forEach((listener) => listener(event));
+ this.listenersOncer.forEach(listener => listener(event));
this.listenersOncer = [];
- }
+ };
- pipe = (te: TypedEvent<T>): Disposable => this.on((e) => te.emit(e));
-} \ No newline at end of file
+ pipe = (te: TypedEvent<T>): Disposable => this.on(e => te.emit(e));
+}
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 857ca852f..956c0e674 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,7 +1,11 @@
-import { observable, action, runInAction } from 'mobx';
-import { Doc, Field } from '../../fields/Doc';
-import { RichTextField } from '../../fields/RichTextField';
+/* eslint-disable prefer-spread */
+/* eslint-disable no-use-before-define */
+import { action, observable, runInAction } from 'mobx';
import { Without } from '../../Utils';
+import { RichTextField } from '../../fields/RichTextField';
+
+// eslint-disable-next-line prefer-const
+let printToConsole = false; // Doc.MyDockedBtns.linearView_IsOpen
function getBatchName(target: any, key: string | symbol): string {
const keyName = key.toString();
@@ -37,10 +41,11 @@ function propertyDecorator(target: any, key: string | symbol) {
}
export function undoable(fn: (...args: any[]) => any, batchName: string): (...args: any[]) => any {
- return function () {
+ return function (...fargs) {
const batch = UndoManager.StartBatch(batchName);
try {
- return fn.apply(undefined, arguments as any);
+ // eslint-disable-next-line prefer-rest-params
+ return fn.apply(undefined, fargs);
} finally {
batch.end();
}
@@ -48,13 +53,15 @@ export function undoable(fn: (...args: any[]) => any, batchName: string): (...ar
}
export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any;
+// eslint-disable-next-line no-redeclare
export function undoBatch(fn: (...args: any[]) => any): (...args: any[]) => any;
+// eslint-disable-next-line no-redeclare
export function undoBatch(target: any, key?: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any {
if (!key) {
- return function () {
+ return function (...fargs: any[]) {
const batch = UndoManager.StartBatch('');
try {
- return target.apply(undefined, arguments);
+ return target.apply(undefined, fargs);
} finally {
batch.end();
}
@@ -62,7 +69,7 @@ export function undoBatch(target: any, key?: string | symbol, descriptor?: Typed
}
if (!descriptor) {
propertyDecorator(target, key);
- return;
+ return undefined;
}
const oldFunction = descriptor.value;
@@ -86,24 +93,28 @@ export namespace UndoManager {
}
type UndoBatch = UndoEvent[];
- export let undoStackNames: string[] = observable([]);
- export let redoStackNames: string[] = observable([]);
- export let undoStack: UndoBatch[] = observable([]);
- export let redoStack: UndoBatch[] = observable([]);
let currentBatch: UndoBatch | undefined;
- export let batchCounter = observable.box(0);
let undoing = false;
- export let tempEvents: UndoEvent[] | undefined = undefined;
+ let tempEvents: UndoEvent[] | undefined;
+ export const undoStackNames: string[] = observable([]);
+ export const redoStackNames: string[] = observable([]);
+ export const undoStack: UndoBatch[] = observable([]);
+ export const redoStack: UndoBatch[] = observable([]);
+ export const batchCounter = observable.box(0);
+ let _fieldPrinter: (val: any) => string = val => val?.toString();
+ export function SetFieldPrinter(printer: (val: any) => string) {
+ _fieldPrinter = printer;
+ }
export function AddEvent(event: UndoEvent, value?: any): void {
if (currentBatch && batchCounter.get() && !undoing) {
- Doc.MyDockedBtns.linearView_IsOpen &&
+ printToConsole &&
console.log(
' '.slice(0, batchCounter.get()) +
'UndoEvent : ' +
event.prop +
- ' = ' +
- (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(val => Field.toJavascriptString(val)).join(',') : Field.toJavascriptString(value))
+ ' = ' + // prettier-ignore
+ (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(_fieldPrinter).join(',') : _fieldPrinter(value))
);
currentBatch.push(event);
tempEvents?.push(event);
@@ -129,21 +140,22 @@ export namespace UndoManager {
}
export function FilterBatches(fieldTypes: string[]) {
const fieldCounts: { [key: string]: number } = {};
- const lastStack = UndoManager.undoStack.slice(-1)[0]; //.lastElement();
+ const lastStack = UndoManager.undoStack.slice(-1)[0]; // .lastElement();
if (lastStack) {
- lastStack.forEach(ev => fieldTypes.includes(ev.prop) && (fieldCounts[ev.prop] = (fieldCounts[ev.prop] || 0) + 1));
+ lastStack.forEach(ev => {
+ fieldTypes.includes(ev.prop) && (fieldCounts[ev.prop] = (fieldCounts[ev.prop] || 0) + 1);
+ });
const fieldCount2: { [key: string]: number } = {};
- runInAction(
- () =>
- (UndoManager.undoStack[UndoManager.undoStack.length - 1] = lastStack.filter(ev => {
- if (fieldTypes.includes(ev.prop)) {
- fieldCount2[ev.prop] = (fieldCount2[ev.prop] || 0) + 1;
- if (fieldCount2[ev.prop] === 1 || fieldCount2[ev.prop] === fieldCounts[ev.prop]) return true;
- return false;
- }
- return true;
- }))
- );
+ runInAction(() => {
+ UndoManager.undoStack[UndoManager.undoStack.length - 1] = lastStack.filter(ev => {
+ if (fieldTypes.includes(ev.prop)) {
+ fieldCount2[ev.prop] = (fieldCount2[ev.prop] || 0) + 1;
+ if (fieldCount2[ev.prop] === 1 || fieldCount2[ev.prop] === fieldCounts[ev.prop]) return true;
+ return false;
+ }
+ return true;
+ });
+ });
}
}
export function TraceOpenBatches() {
@@ -160,11 +172,10 @@ export namespace UndoManager {
if (this.disposed) {
console.log('WARNING: undo batch already disposed');
return false;
- } else {
- this.disposed = true;
- openBatches.splice(openBatches.indexOf(this));
- return EndBatch(this.batchName, cancel);
}
+ this.disposed = true;
+ openBatches.splice(openBatches.indexOf(this));
+ return EndBatch(this.batchName, cancel);
};
end = () => this.dispose(false);
@@ -172,7 +183,7 @@ export namespace UndoManager {
}
export function StartBatch(batchName: string): Batch {
- Doc.MyDockedBtns.linearView_IsOpen && console.log(' '.slice(0, batchCounter.get()) + 'Start ' + batchCounter + ' ' + batchName);
+ printToConsole && console.log(' '.slice(0, batchCounter.get()) + 'Start ' + batchCounter + ' ' + batchName);
runInAction(() => batchCounter.set(batchCounter.get() + 1));
if (currentBatch === undefined) {
currentBatch = [];
@@ -182,7 +193,7 @@ export namespace UndoManager {
const EndBatch = action((batchName: string, cancel: boolean = false) => {
runInAction(() => batchCounter.set(batchCounter.get() - 1));
- Doc.MyDockedBtns.linearView_IsOpen && console.log(' '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + currentBatch?.length + ')');
+ printToConsole && console.log(' '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + (currentBatch?.length ?? 0) + ')');
if (batchCounter.get() === 0 && currentBatch?.length) {
if (!cancel) {
undoStack.push(currentBatch);
@@ -199,10 +210,10 @@ export namespace UndoManager {
export function StartTempBatch() {
tempEvents = [];
}
- export function EndTempBatch<T>(success: boolean) {
+ export function EndTempBatch(success: boolean) {
UndoManager.UndoTempBatch(success);
}
- //TODO Make this return the return value
+ // TODO Make this return the return value
export function RunInBatch<T>(fn: () => T, batchName: string) {
const batch = StartBatch(batchName);
try {
@@ -234,9 +245,11 @@ export namespace UndoManager {
}
undoing = true;
- for (let i = commands.length - 1; i >= 0; i--) {
- commands[i].undo();
- }
+ // eslint-disable-next-line prettier/prettier
+ commands
+ .slice()
+ .reverse()
+ .forEach(command => command.undo());
undoing = false;
redoStackNames.push(names ?? '???');
@@ -255,9 +268,7 @@ export namespace UndoManager {
}
undoing = true;
- for (const command of commands) {
- command.redo();
- }
+ commands.forEach(command => command.redo());
undoing = false;
undoStackNames.push(names ?? '???');
diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts
index 8fc6de6f9..d6f3f2340 100644
--- a/src/client/util/bezierFit.ts
+++ b/src/client/util/bezierFit.ts
@@ -1,4 +1,8 @@
-import { Point } from "../../pen-gestures/ndollar";
+/* eslint-disable no-use-before-define */
+/* eslint-disable prefer-destructuring */
+/* eslint-disable no-param-reassign */
+/* eslint-disable camelcase */
+import { Point } from '../../pen-gestures/ndollar';
class SmartRect {
minx: number = 0;
@@ -6,20 +10,43 @@ class SmartRect {
maxx: number = 0;
maxy: number = 0;
- constructor(mix: number = 0, miy: number = 0, max: number = 0, may: number = 0) { this.minx = mix; this.miny = miy; this.maxx = max; this.maxy = may; }
+ constructor(mix: number = 0, miy: number = 0, max: number = 0, may: number = 0) {
+ this.minx = mix;
+ this.miny = miy;
+ this.maxx = max;
+ this.maxy = may;
+ }
- public get Center() { return new Point((this.maxx + this.minx) / 2.0, (this.maxy + this.miny) / 2.0); }
- public get TopLeft() { return new Point(this.minx, this.miny); }
- public get TopRight() { return new Point(this.maxx, this.miny); }
- public get BotLeft() { return new Point(this.minx, this.maxy); }
- public get BotRight() { return new Point(this.maxx, this.maxy); }
- public get Width() { return this.maxx - this.minx; }
- public get Height() { return this.maxy - this.miny; }
- public static Intersect(a: SmartRect, b: SmartRect) { return a.Intersect(b); }
- public Intersect(b: SmartRect) { return !((this.minx > b.maxx) || (this.miny > b.maxy) || (b.minx > this.maxx) || (b.miny > this.maxy)); }
+ public get Center() {
+ return new Point((this.maxx + this.minx) / 2.0, (this.maxy + this.miny) / 2.0);
+ }
+ public get TopLeft() {
+ return new Point(this.minx, this.miny);
+ }
+ public get TopRight() {
+ return new Point(this.maxx, this.miny);
+ }
+ public get BotLeft() {
+ return new Point(this.minx, this.maxy);
+ }
+ public get BotRight() {
+ return new Point(this.maxx, this.maxy);
+ }
+ public get Width() {
+ return this.maxx - this.minx;
+ }
+ public get Height() {
+ return this.maxy - this.miny;
+ }
+ public static Intersect(a: SmartRect, b: SmartRect) {
+ return a.Intersect(b);
+ }
+ public Intersect(b: SmartRect) {
+ return !(this.minx > b.maxx || this.miny > b.maxy || b.minx > this.maxx || b.miny > this.maxy);
+ }
public ContainsPercentage(other: SmartRect, axis: Point) {
- var ret = 0;
+ let ret = 0;
const minx = Math.max(other.TopLeft.X * axis.X + other.TopLeft.Y * axis.Y, this.TopLeft.X * axis.X + this.TopLeft.Y * axis.Y);
const maxx = Math.max(other.BotRight.X * axis.X + other.BotRight.Y * axis.Y, this.BotRight.X * axis.X + this.BotRight.Y * axis.Y);
ret = maxx > minx ? (maxx - minx) / (axis === new Point(1, 0) ? other.Width : other.Height) : 0;
@@ -36,7 +63,7 @@ class SmartRect {
if (r.minx > r.maxx) [r.minx, r.maxx] = [r.maxx, r.minx];
if (r.miny > r.maxy) [r.miny, r.maxy] = [r.maxy, r.miny];
- for (const pt of p) {
+ p.forEach(pt => {
if (pt.X < r.minx) {
r.minx = pt.X;
} else if (pt.X > r.maxx) {
@@ -47,7 +74,7 @@ class SmartRect {
} else if (pt.Y > r.maxy) {
r.maxy = pt.Y;
}
- }
+ });
}
return r;
}
@@ -64,18 +91,18 @@ export function Normalize(p: Point) {
function ReparameterizeBezier(d: Point[], first: number, last: number, u: number[], bezCurve: Point[]) {
const uPrime = new Array<number>(last - first + 1); // New parameter values
- for (var i = first; i <= last; i++) {
+ for (let i = first; i <= last; i++) {
uPrime[i - first] = NewtonRaphsonRootFind(bezCurve, d[i], u[i - first]);
}
return uPrime;
}
function ComputeMaxError(d: Point[], first: number, last: number, bezCurve: Point[], u: number[]) {
- var maxError = 0; // Maximum error
- var splitPoint2D = (last - first + 1) / 2;
- for (var i = first + 1; i < last; i++) {
- const P = [0, 0]; // point on curve
+ let maxError = 0; // Maximum error
+ let splitPoint2D = (last - first + 1) / 2;
+ for (let i = first + 1; i < last; i++) {
+ const P = [0, 0]; // point on curve
EvalBezierFast(bezCurve, u[i - first], P);
- const dx = P[0] - d[i].X;// offset from point to curve
+ const dx = P[0] - d[i].X; // offset from point to curve
const dy = P[1] - d[i].Y;
const dist = Math.sqrt(dx * dx + dy * dy); // Current error
if (dist >= maxError) {
@@ -88,11 +115,11 @@ function ComputeMaxError(d: Point[], first: number, last: number, bezCurve: Poin
return { maxError, splitPoint2D };
}
function ChordLengthParameterize(d: Point[], first: number, last: number) {
- const u = new Array<number>(last - first + 1);// Parameterization
+ const u = new Array<number>(last - first + 1); // Parameterization
- var prev = 0.0;
+ let prev = 0.0;
u[0] = prev;
- for (var i = first + 1; i <= last; i++) {
+ for (let i = first + 1; i <= last; i++) {
const lastd = d[i - 1];
const curd = d[i];
const dx = lastd.X - curd.X;
@@ -101,27 +128,38 @@ function ChordLengthParameterize(d: Point[], first: number, last: number) {
}
const ulastfirst = u[last - first];
- for (var i = first + 1; i <= last; i++) {
+ for (let i = first + 1; i <= last; i++) {
u[i - first] /= ulastfirst;
}
return u;
}
/*
-* B0, B1, B2, B3 :
-* Bezier multipliers
-*/
-function B0(u: number) { const tmp = 1.0 - u; return tmp * tmp * tmp; }
-function B1(u: number) { const tmp = 1.0 - u; return 3 * u * tmp * tmp; }
-function B2(u: number) { const tmp = 1.0 - u; return 3 * u * u * tmp; }
-function B3(u: number) { return u * u * u; }
+ * B0, B1, B2, B3 :
+ * Bezier multipliers
+ */
+function B0(u: number) {
+ const tmp = 1.0 - u;
+ return tmp * tmp * tmp;
+}
+function B1(u: number) {
+ const tmp = 1.0 - u;
+ return 3 * u * tmp * tmp;
+}
+function B2(u: number) {
+ const tmp = 1.0 - u;
+ return 3 * u * u * tmp;
+}
+function B3(u: number) {
+ return u * u * u;
+}
function bounds(p: Point[]) {
const r = new SmartRect(p[0].X, p[0].Y, p[3].X, p[3].Y); // These are the most likely to be extremal
- if (r.minx > r.maxx) (r.minx, r.maxx);
+ if (r.minx > r.maxx) [r.minx, r.maxx] = [r.maxx, r.minx];
if (r.miny > r.maxy) [r.miny, r.maxy] = [r.maxy, r.miny]; // swap min & max
- for (var i = 1; i < 3; i++) {
+ for (let i = 1; i < 3; i++) {
if (p[i].X < r.minx) r.minx = p[i].X;
else if (p[i].X > r.maxx) r.maxx = p[i].X;
@@ -131,37 +169,36 @@ function bounds(p: Point[]) {
return r;
}
-
function splitCubic(p: Point[], t: number, left: Point[], right: Point[]) {
const sz = 4;
const Vtemp = new Array<Array<Point>>(4);
- for (var i = 0; i < 4; i++) Vtemp[i] = new Array<Point>(4);
+ for (let i = 0; i < 4; i++) Vtemp[i] = new Array<Point>(4);
/* Copy control points */
// std::copy(p.begin(), p.end(), Vtemp[0]);
- for (var i = 0; i < sz; i++) {
+ for (let i = 0; i < sz; i++) {
Vtemp[0][i].X = p[i].X;
Vtemp[0][i].Y = p[i].Y;
}
/* Triangle computation */
- for (var i = 1; i < sz; i++) {
- for (var j = 0; j < sz - i; j++) {
+ for (let i = 1; i < sz; i++) {
+ for (let j = 0; j < sz - i; j++) {
const a = Vtemp[i - 1][j];
const b = Vtemp[i - 1][j + 1];
Vtemp[i][j].X = b.X * t + a.X * (1 - t);
- Vtemp[i][j].Y = b.Y * t + a.Y * (1 - t); // Vtemp[i][j] = Point2D::Lerp(Vtemp[i - 1][j], Vtemp[i - 1][j + 1], t);
+ Vtemp[i][j].Y = b.Y * t + a.Y * (1 - t); // Vtemp[i][j] = Point2D::Lerp(Vtemp[i - 1][j], Vtemp[i - 1][j + 1], t);
}
}
if (left) {
- for (var j = 0; j < sz; j++) {
+ for (let j = 0; j < sz; j++) {
left[j].X = Vtemp[j][0].X;
left[j].Y = Vtemp[j][0].Y;
}
}
if (right) {
- for (var j = 0; j < sz; j++) {
+ for (let j = 0; j < sz; j++) {
right[j].X = Vtemp[sz - 1 - j][j].X;
right[j].Y = Vtemp[sz - 1 - j][j].Y;
}
@@ -169,51 +206,54 @@ function splitCubic(p: Point[], t: number, left: Point[], right: Point[]) {
}
/*
-* Recursively intersect two curves keeping track of their real parameters
-* and depths of intersection.
-* The results are returned in a 2-D array of doubles indicating the parameters
-* for which intersections are found. The parameters are in the order the
-* intersections were found, which is probably not in sorted order.
-* When an intersection is found, the parameter value for each of the two
-* is stored in the index elements array, and the index is incremented.
-*
-* If either of the curves has subdivisions left before it is straight
-* (depth > 0)
-* that curve (possibly both) is (are) subdivided at its (their) midpoint(s).
-* the depth(s) is (are) decremented, and the parameter value(s) corresponding
-* to the midpoints(s) is (are) computed.
-* Then each of the subcurves of one curve is intersected with each of the
-* subcurves of the other curve, first by testing the bounding boxes for
-* interference. If there is any bounding box interference, the corresponding
-* subcurves are recursively intersected.
-*
-* If neither curve has subdivisions left, the line segments from the first
-* to last control point of each segment are intersected. (Actually the
-* only the parameter value corresponding to the intersection point is found).
-*
-* The apriori flatness test is probably more efficient than testing at each
-* level of recursion, although a test after three or four levels would
-* probably be worthwhile, since many curves become flat faster than their
-* asymptotic rate for the first few levels of recursion.
-*
-* The bounding box test fails much more frequently than it succeeds, providing
-* substantial pruning of the search space.
-*
-* Each (sub)curve is subdivided only once, hence it is not possible that for
-* one final line intersection test the subdivision was at one level, while
-* for another final line intersection test the subdivision (of the same curve)
-* was at another. Since the line segments share endpoints, the intersection
-* is robust: a near-tangential intersection will yield zero or two
-* intersections.
-*/
+ * Recursively intersect two curves keeping track of their real parameters
+ * and depths of intersection.
+ * The results are returned in a 2-D array of doubles indicating the parameters
+ * for which intersections are found. The parameters are in the order the
+ * intersections were found, which is probably not in sorted order.
+ * When an intersection is found, the parameter value for each of the two
+ * is stored in the index elements array, and the index is incremented.
+ *
+ * If either of the curves has subdivisions left before it is straight
+ * (depth > 0)
+ * that curve (possibly both) is (are) subdivided at its (their) midpoint(s).
+ * the depth(s) is (are) decremented, and the parameter value(s) corresponding
+ * to the midpoints(s) is (are) computed.
+ * Then each of the subcurves of one curve is intersected with each of the
+ * subcurves of the other curve, first by testing the bounding boxes for
+ * interference. If there is any bounding box interference, the corresponding
+ * subcurves are recursively intersected.
+ *
+ * If neither curve has subdivisions left, the line segments from the first
+ * to last control point of each segment are intersected. (Actually the
+ * only the parameter value corresponding to the intersection point is found).
+ *
+ * The apriori flatness test is probably more efficient than testing at each
+ * level of recursion, although a test after three or four levels would
+ * probably be worthwhile, since many curves become flat faster than their
+ * asymptotic rate for the first few levels of recursion.
+ *
+ * The bounding box test fails much more frequently than it succeeds, providing
+ * substantial pruning of the search space.
+ *
+ * Each (sub)curve is subdivided only once, hence it is not possible that for
+ * one final line intersection test the subdivision was at one level, while
+ * for another final line intersection test the subdivision (of the same curve)
+ * was at another. Since the line segments share endpoints, the intersection
+ * is robust: a near-tangential intersection will yield zero or two
+ * intersections.
+ */
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
function recursively_intersect(a: Point[], t0: number, t1: number, deptha: number, b: Point[], u0: number, u1: number, depthb: number, parameters: number[][]) {
if (deptha > 0) {
- const a1 = new Array<Point>(4), a2 = new Array<Point>(4);
+ const a1 = new Array<Point>(4);
+ const a2 = new Array<Point>(4);
splitCubic(a, 0.5, a1, a2);
const tmid = (t0 + t1) * 0.5;
deptha--;
if (depthb > 0) {
- const b1 = new Array<Point>(4), b2 = new Array<Point>(4);
+ const b1 = new Array<Point>(4);
+ const b2 = new Array<Point>(4);
splitCubic(b, 0.5, b1, b2);
const umid = (u0 + u1) * 0.5;
depthb--;
@@ -229,8 +269,7 @@ function recursively_intersect(a: Point[], t0: number, t1: number, deptha: numbe
if (SmartRect.Intersect(bounds(a2), bounds(b2))) {
recursively_intersect(a2, tmid, t1, deptha, b2, umid, u1, depthb, parameters);
}
- }
- else {
+ } else {
if (SmartRect.Intersect(bounds(a1), bounds(b))) {
recursively_intersect(a1, t0, tmid, deptha, b, u0, u1, depthb, parameters);
}
@@ -238,50 +277,45 @@ function recursively_intersect(a: Point[], t0: number, t1: number, deptha: numbe
recursively_intersect(a2, tmid, t1, deptha, b, u0, u1, depthb, parameters);
}
}
- }
+ } else if (depthb > 0) {
+ const b1 = new Array<Point>(4);
+ const b2 = new Array<Point>(4);
+ splitCubic(b, 0.5, b1, b2);
+ const umid = (u0 + u1) * 0.5;
+ depthb--;
+ if (SmartRect.Intersect(bounds(a), bounds(b1))) {
+ recursively_intersect(a, t0, t1, deptha, b1, u0, umid, depthb, parameters);
+ }
+ if (SmartRect.Intersect(bounds(a), bounds(b2))) {
+ recursively_intersect(a, t0, t1, deptha, b2, umid, u1, depthb, parameters);
+ }
+ } // Both segments are fully subdivided; now do line segments
else {
- if (depthb > 0) {
- const b1 = new Array<Point>(4), b2 = new Array<Point>(4);
- splitCubic(b, 0.5, b1, b2);
- const umid = (u0 + u1) * 0.5;
- depthb--;
- if (SmartRect.Intersect(bounds(a), bounds(b1))) {
- recursively_intersect(a, t0, t1, deptha, b1, u0, umid, depthb, parameters);
- }
- if (SmartRect.Intersect(bounds(a), bounds(b2))) {
- recursively_intersect(a, t0, t1, deptha, b2, umid, u1, depthb, parameters);
- }
+ const xlk = a[3].X - a[0].X;
+ const ylk = a[3].Y - a[0].Y;
+ const xnm = b[3].X - b[0].X;
+ const ynm = b[3].Y - b[0].Y;
+ const xmk = b[0].X - a[0].X;
+ const ymk = b[0].Y - a[0].Y;
+ const det = xnm * ylk - ynm * xlk;
+ if (1.0 + det === 1.0) {
+ return;
}
- else // Both segments are fully subdivided; now do line segments
- {
- const xlk = a[3].X - a[0].X;
- const ylk = a[3].Y - a[0].Y;
- const xnm = b[3].X - b[0].X;
- const ynm = b[3].Y - b[0].Y;
- const xmk = b[0].X - a[0].X;
- const ymk = b[0].Y - a[0].Y;
- const det = xnm * ylk - ynm * xlk;
- if (1.0 + det === 1.0) {
- return;
- }
- else {
- const detinv = 1.0 / det;
- const s = (xnm * ymk - ynm * xmk) * detinv;
- const t = (xlk * ymk - ylk * xmk) * detinv;
- if ((s < 0.0) || (s > 1.0) || (t < 0.0) || (t > 1.0) || Number.isNaN(s) || Number.isNaN(t)) {
- return;
- }
- parameters.push([t0 + s * (t1 - t0), u0 + t * (u1 - u0)]);
- }
+ const detinv = 1.0 / det;
+ const s = (xnm * ymk - ynm * xmk) * detinv;
+ const t = (xlk * ymk - ylk * xmk) * detinv;
+ if (s < 0.0 || s > 1.0 || t < 0.0 || t > 1.0 || isNaN(s) || isNaN(t)) {
+ return;
}
+ parameters.push([t0 + s * (t1 - t0), u0 + t * (u1 - u0)]);
}
}
/*
-* EvalBezier :
-* Evaluate a Bezier curve at a particular parameter value
-*
-*/
+ * EvalBezier :
+ * Evaluate a Bezier curve at a particular parameter value
+ *
+ */
const MAX_DEGREE = 5;
function EvalBezier(V: Point[], degree: number, t: number, result: number[]) {
if (degree + 1 > MAX_DEGREE) {
@@ -293,35 +327,36 @@ function EvalBezier(V: Point[], degree: number, t: number, result: number[]) {
const Vtemp = [new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)]; // Local copy of control points
/* Copy array */
- for (var i = 0; i <= degree; i++) {
+ for (let i = 0; i <= degree; i++) {
Vtemp[i].X = V[i].X;
Vtemp[i].Y = V[i].Y;
}
/* Triangle computation */
- for (var i = 1; i <= degree; i++) {
- for (var j = 0; j <= degree - i; j++) {
+ for (let i = 1; i <= degree; i++) {
+ for (let j = 0; j <= degree - i; j++) {
Vtemp[j].X = (1.0 - t) * Vtemp[j].X + t * Vtemp[j + 1].X;
Vtemp[j].Y = (1.0 - t) * Vtemp[j].Y + t * Vtemp[j + 1].Y;
}
}
result[0] = Vtemp[0].X;
- result[1] = Vtemp[0].Y;// Point on curve at parameter t
+ result[1] = Vtemp[0].Y; // Point on curve at parameter t
}
function EvalBezierFast(p: Point[], t: number, result: number[]) {
const n = 3;
const u = 1.0 - t;
- var bc = 1, tn = 1;
- var tmpX = p[0].X * u;
- var tmpY = p[0].Y * u;
- tn = tn * t;
- bc = bc * (n - 1 + 1) / 1;
+ let bc = 1;
+ let tn = 1;
+ let tmpX = p[0].X * u;
+ let tmpY = p[0].Y * u;
+ tn *= t;
+ bc = (bc * (n - 1 + 1)) / 1;
tmpX = (tmpX + tn * bc * p[1].X) * u;
tmpY = (tmpY + tn * bc * p[1].Y) * u;
- tn = tn * t;
- bc = bc * (n - 2 + 1) / 2;
+ tn *= t;
+ bc = (bc * (n - 2 + 1)) / 2;
tmpX = (tmpX + tn * bc * p[2].X) * u;
tmpY = (tmpY + tn * bc * p[2].Y) * u;
@@ -329,9 +364,9 @@ function EvalBezierFast(p: Point[], t: number, result: number[]) {
result[1] = tmpY + tn * t * p[3].Y;
}
/*
-* ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent :
-*Approximate unit tangents at endpoints and "center" of digitized curve
-*/
+ * ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent :
+ *Approximate unit tangents at endpoints and "center" of digitized curve
+ */
function ComputeLeftTangent(d: Point[], end: number) {
const use = 1;
const tHat1 = new Point(d[end + use].X - d[end].X, d[end + use].Y - d[end].Y);
@@ -348,19 +383,19 @@ function ComputeCenterTangent(d: Point[], center: number) {
}
const V1 = ComputeLeftTangent(d, center); // d[center] - d[center-1];
const V2 = ComputeRightTangent(d, center); // d[center] - d[center + 1];
- var tHatCenter = new Point((-V1.X + V2.X) / 2.0, (-V1.Y + V2.Y) / 2.0);
+ let tHatCenter = new Point((-V1.X + V2.X) / 2.0, (-V1.Y + V2.Y) / 2.0);
if (tHatCenter === new Point(0, 0)) {
- tHatCenter = new Point(-V1.Y, -V1.X);// V1.Perp();
+ tHatCenter = new Point(-V1.Y, -V1.X); // V1.Perp();
}
return Normalize(tHatCenter);
}
function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[], tHat1: Point, tHat2: Point, result: Point[] /* must be prealloacted to size 4 */) {
const nPts = last - first + 1; // Number of pts in sub-curve
- const Ax = new Array<number>(nPts * 2);// Precomputed rhs for eqn //std::vector<Vector2D> A(nPts * 2);
- const Ay = new Array<number>(nPts * 2);// Precomputed rhs for eqn //std::vector<Vector2D> A(nPts * 2);
+ const Ax = new Array<number>(nPts * 2); // Precomputed rhs for eqn //std::vector<Vector2D> A(nPts * 2);
+ const Ay = new Array<number>(nPts * 2); // Precomputed rhs for eqn //std::vector<Vector2D> A(nPts * 2);
/* Compute the A's */
- for (var i = 0; i < nPts; i++) {
+ for (let i = 0; i < nPts; i++) {
const uprime = uPrime[i];
const b1 = B1(uprime);
const b2 = B2(uprime);
@@ -371,39 +406,42 @@ function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[
}
/* Create the C and X matrices */
- const C = [[0, 0], [0, 0]];
+ const C = [
+ [0, 0],
+ [0, 0],
+ ];
const df = d[first];
const dl = d[last];
- const X = [0, 0]; // Matrix X
- for (var i = 0; i < nPts; i++) {
- C[0][0] += Ax[i] * Ax[i] + Ay[i] * Ay[i]; //A[i+0*nPts].Dot(A[i+0*nPts]);
- C[0][1] += Ax[i] * Ax[i + nPts] + Ay[i] * Ay[i + nPts];//A[i+0*nPts].Dot(A[i+1*nPts]);
+ const X = [0, 0]; // Matrix X
+ for (let i = 0; i < nPts; i++) {
+ C[0][0] += Ax[i] * Ax[i] + Ay[i] * Ay[i]; // A[i+0*nPts].Dot(A[i+0*nPts]);
+ C[0][1] += Ax[i] * Ax[i + nPts] + Ay[i] * Ay[i + nPts]; // A[i+0*nPts].Dot(A[i+1*nPts]);
C[1][0] = C[0][1];
- C[1][1] += Ax[i + nPts] * Ax[i + nPts] + Ay[i + nPts] * Ay[i + nPts];// A[i+1*nPts].Dot(A[i+1*nPts]);
+ C[1][1] += Ax[i + nPts] * Ax[i + nPts] + Ay[i + nPts] * Ay[i + nPts]; // A[i+1*nPts].Dot(A[i+1*nPts]);
const uprime = uPrime[i];
const b0plb1 = B0(uprime) + B1(uprime);
const b2plb3 = B2(uprime) + B3(uprime);
const df1 = d[first + i];
- const tmpX = df1.X - (df.X * b0plb1 + (dl.X * b2plb3));
- const tmpY = df1.Y - (df.Y * b0plb1 + (dl.Y * b2plb3));
+ const tmpX = df1.X - (df.X * b0plb1 + dl.X * b2plb3);
+ const tmpY = df1.Y - (df.Y * b0plb1 + dl.Y * b2plb3);
X[0] += Ax[i] * tmpX + Ay[i] * tmpY; // A[i+0*nPts].Dot(tmp)
- X[1] += Ax[i + nPts] * tmpX + Ay[i + nPts] * tmpY; //A[i+1*nPts].Dot(tmp)
+ X[1] += Ax[i + nPts] * tmpX + Ay[i + nPts] * tmpY; // A[i+1*nPts].Dot(tmp)
}
/* Compute the determinants of C and X */
- const det_C0_C1 = (C[0][0] * C[1][1] - C[1][0] * C[0][1]) || (C[0][0] * C[1][1]) * 10e-12;
+ const det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1] || C[0][0] * C[1][1] * 10e-12;
const det_C0_X = C[0][0] * X[1] - C[0][1] * X[0];
const det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
/* Finally, derive alpha values */
- var alpha_l = (det_C0_C1 === 0) ? 0.0 : det_X_C1 / det_C0_C1;
- var alpha_r = (det_C0_C1 === 0) ? 0.0 : det_C0_X / det_C0_C1;
+ let alpha_l = det_C0_C1 === 0 ? 0.0 : det_X_C1 / det_C0_C1;
+ let alpha_r = det_C0_C1 === 0 ? 0.0 : det_C0_X / det_C0_C1;
/* If alpha negative, use the Wu/Barsky heuristic (see text) */
/* (if alpha is 0, you get coincident control points that lead to
- * divide by zero in any subsequent NewtonRaphsonRootFind() call. */
+ * divide by zero in any subsequent NewtonRaphsonRootFind() call. */
const segLength = Math.sqrt((df.X - dl.X) * (df.X - dl.X) + (df.Y - dl.Y) * (df.Y - dl.Y));
const epsilon = 1.0e-6 * segLength;
if (alpha_l < epsilon || alpha_r < epsilon) {
@@ -415,10 +453,10 @@ function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[
/* positioned exactly at the first and last data points */
/* Control points 1 and 2 are positioned an alpha distance out */
/* on the tangent vectors, left and right, respectively */
- result[0] = df;// RETURN bezier curve ctl pts
+ result[0] = df; // RETURN bezier curve ctl pts
result[3] = dl;
- result[1] = new Point(df.X + (tHat1.X * alpha_l), df.Y + (tHat1.Y * alpha_l));
- result[2] = new Point(dl.X + (tHat2.X * alpha_r), dl.Y + (tHat2.Y * alpha_r));
+ result[1] = new Point(df.X + tHat1.X * alpha_l, df.Y + tHat1.Y * alpha_l);
+ result[2] = new Point(dl.X + tHat2.X * alpha_r, dl.Y + tHat2.Y * alpha_r);
}
/*
@@ -426,21 +464,24 @@ function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[
* Use Newton-Raphson iteration to find better root.
*/
function NewtonRaphsonRootFind(Q: Point[], P: Point, u: number) {
- const Q1 = [new Point(0, 0), new Point(0, 0), new Point(0, 0)], Q2 = [new Point(0, 0), new Point(0, 0)]; // Q' and Q''
- const Q_u = [0, 0], Q1_u = [0, 0], Q2_u = [0, 0]; //u evaluated at Q, Q', & Q''
+ const Q1 = [new Point(0, 0), new Point(0, 0), new Point(0, 0)];
+ const Q2 = [new Point(0, 0), new Point(0, 0)]; // Q' and Q''
+ const Q_u = [0, 0];
+ const Q1_u = [0, 0];
+ const Q2_u = [0, 0]; // u evaluated at Q, Q', & Q''
/* Compute Q(u) */
- var uPrime: number; // Improved u
+ let uPrime: number; // Improved u
EvalBezierFast(Q, u, Q_u);
/* Generate control vertices for Q' */
- for (var i = 0; i <= 2; i++) {
+ for (let i = 0; i <= 2; i++) {
Q1[i].X = (Q[i + 1].X - Q[i].X) * 3.0;
Q1[i].Y = (Q[i + 1].Y - Q[i].Y) * 3.0;
}
/* Generate control vertices for Q'' */
- for (var i = 0; i <= 1; i++) {
+ for (let i = 0; i <= 1; i++) {
Q2[i].X = (Q1[i + 1].X - Q1[i].X) * 2.0;
Q2[i].Y = (Q1[i + 1].Y - Q1[i].Y) * 2.0;
}
@@ -450,20 +491,20 @@ function NewtonRaphsonRootFind(Q: Point[], P: Point, u: number) {
EvalBezier(Q2, 1, u, Q2_u);
/* Compute f(u)/f'(u) */
- const numerator = (Q_u[0] - P.X) * (Q1_u[0]) + (Q_u[1] - P.Y) * (Q1_u[1]);
- const denominator = (Q1_u[0]) * (Q1_u[0]) + (Q1_u[1]) * (Q1_u[1]) + (Q_u[0] - P.X) * (Q2_u[0]) + (Q_u[1] - P.Y) * (Q2_u[1]);
+ const numerator = (Q_u[0] - P.X) * Q1_u[0] + (Q_u[1] - P.Y) * Q1_u[1];
+ const denominator = Q1_u[0] * Q1_u[0] + Q1_u[1] * Q1_u[1] + (Q_u[0] - P.X) * Q2_u[0] + (Q_u[1] - P.Y) * Q2_u[1];
if (denominator === 0.0) {
uPrime = u;
- } else uPrime = u - (numerator / denominator);/* u = u - f(u)/f'(u) */
+ } else uPrime = u - numerator / denominator; /* u = u - f(u)/f'(u) */
return uPrime;
}
function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2: Point, error: number, result: Point[]) {
- const bezCurve = new Array<Point>(4); // Control points of fitted Bezier curve
- const maxIterations = 4; // Max times to try iterating
+ const bezCurve = new Array<Point>(4); // Control points of fitted Bezier curve
+ const maxIterations = 4; // Max times to try iterating
const iterationError = error * error; // Error below which you try iterating
- const nPts = last - first + 1; // Number of points in subset
+ const nPts = last - first + 1; // Number of points in subset
/* Use heuristic if region only has two points in it */
if (nPts === 2) {
@@ -471,8 +512,8 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
bezCurve[0] = d[first];
bezCurve[3] = d[last];
- bezCurve[1] = new Point(bezCurve[0].X + (tHat1.X * dist), bezCurve[0].Y + (tHat1.Y * dist));
- bezCurve[2] = new Point(bezCurve[3].X + (tHat2.X * dist), bezCurve[3].Y + (tHat2.Y * dist));
+ bezCurve[1] = new Point(bezCurve[0].X + tHat1.X * dist, bezCurve[0].Y + tHat1.Y * dist);
+ bezCurve[2] = new Point(bezCurve[3].X + tHat2.X * dist, bezCurve[3].Y + tHat2.Y * dist);
result.push(bezCurve[1]);
result.push(bezCurve[2]);
@@ -481,11 +522,11 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
}
/* Parameterize points, and attempt to fit curve */
- var u = ChordLengthParameterize(d, first, last);
+ let u = ChordLengthParameterize(d, first, last);
GenerateBezier(d, first, last, u, tHat1, tHat2, bezCurve);
/* Find max deviation of points to fitted curve */
- const { maxError, splitPoint2D } = ComputeMaxError(d, first, last, bezCurve, u); // Maximum fitting error
+ const { maxError, splitPoint2D } = ComputeMaxError(d, first, last, bezCurve, u); // Maximum fitting error
if (maxError < Math.abs(error)) {
result.push(bezCurve[1]);
result.push(bezCurve[2]);
@@ -496,11 +537,11 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
/* If error not too large, try some reparameterization */
/* and iteration */
if (maxError < iterationError) {
- for (var i = 0; i < maxIterations; i++) {
- const uPrime = ReparameterizeBezier(d, first, last, u, bezCurve); // Improved parameter values
+ for (let i = 0; i < maxIterations; i++) {
+ const uPrime = ReparameterizeBezier(d, first, last, u, bezCurve); // Improved parameter values
GenerateBezier(d, first, last, uPrime, tHat1, tHat2, bezCurve);
- const { maxError } = ComputeMaxError(d, first, last, bezCurve, uPrime);
- if (maxError < error) {
+ const { maxError: maximumError } = ComputeMaxError(d, first, last, bezCurve, uPrime);
+ if (maximumError < error) {
result.push(bezCurve[1]);
result.push(bezCurve[2]);
result.push(bezCurve[3]);
@@ -527,13 +568,13 @@ export function FitOneCurve(d: Point[], tHat1?: Point, tHat2?: Point) {
tHat1 = tHat1 ?? Normalize(ComputeLeftTangent(d, 0));
tHat2 = tHat2 ?? Normalize(ComputeRightTangent(d, d.length - 1));
tHat2 = new Point(-tHat2.X, -tHat2.Y);
- var u = ChordLengthParameterize(d, 0, d.length - 1);
+ let u = ChordLengthParameterize(d, 0, d.length - 1);
const bezCurveCtrls = [new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)];
- GenerateBezier(d, 0, d.length - 1, u, tHat1, tHat2, bezCurveCtrls); /* Find max deviation of points to fitted curve */
- var finalCtrls = bezCurveCtrls.slice();
- var { maxError: error } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, u);
- for (var i = 0; i < 10; i++) {
- const uPrime = ReparameterizeBezier(d, 0, d.length - 1, u, bezCurveCtrls); // Improved parameter values
+ GenerateBezier(d, 0, d.length - 1, u, tHat1, tHat2, bezCurveCtrls); /* Find max deviation of points to fitted curve */
+ let finalCtrls = bezCurveCtrls.slice();
+ let { maxError: error } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, u);
+ for (let i = 0; i < 10; i++) {
+ const uPrime = ReparameterizeBezier(d, 0, d.length - 1, u, bezCurveCtrls); // Improved parameter values
GenerateBezier(d, 0, d.length - 1, uPrime, tHat1, tHat2, bezCurveCtrls);
const { maxError } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, uPrime);
if (maxError < error) {
@@ -1428,4 +1469,4 @@ spliceR[0] += v;
}
#endif
-*/ \ No newline at end of file
+*/
diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx
index 0c49aeed4..2224e642d 100644
--- a/src/client/util/reportManager/ReportManager.tsx
+++ b/src/client/util/reportManager/ReportManager.tsx
@@ -1,33 +1,39 @@
-import * as React from 'react';
-import * as uuid from 'uuid';
-import '.././SettingsManager.scss';
-import './ReportManager.scss';
-import ReactLoading from 'react-loading';
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/media-has-caption */
+/* eslint-disable react/no-unused-class-component-methods */
+import { Octokit } from '@octokit/core';
+import { Button, Dropdown, DropdownType, IconButton, Type } from 'browndash-components';
import { action, makeObservable, observable } from 'mobx';
-import { BsX, BsArrowsAngleExpand, BsArrowsAngleContract } from 'react-icons/bs';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { BsArrowsAngleContract, BsArrowsAngleExpand, BsX } from 'react-icons/bs';
import { CgClose } from 'react-icons/cg';
import { HiOutlineArrowLeft } from 'react-icons/hi';
-import { Issue } from './reportManagerSchema';
-import { observer } from 'mobx-react';
+import { MdRefresh } from 'react-icons/md';
+import ReactLoading from 'react-loading';
+import * as uuid from 'uuid';
+import { ClientUtils } from '../../../ClientUtils';
import { Doc } from '../../../fields/Doc';
-import { MainViewModal } from '../../views/MainViewModal';
-import { Octokit } from '@octokit/core';
-import { Button, Dropdown, DropdownType, IconButton, Type } from 'browndash-components';
-import { BugType, FileData, Priority, ReportForm, ViewState, bugDropdownItems, darkColors, emptyReportForm, formatTitle, getAllIssues, isDarkMode, lightColors, passesTagFilter, priorityDropdownItems, uploadFilesToServer } from './reportManagerUtils';
-import { Filter, FormInput, FormTextArea, IssueCard, IssueView, Tag } from './ReportManagerComponents';
import { StrCast } from '../../../fields/Types';
-import { MdRefresh } from 'react-icons/md';
+import { MainViewModal } from '../../views/MainViewModal';
+import '../SettingsManager.scss';
import { SettingsManager } from '../SettingsManager';
+import './ReportManager.scss';
+import { Filter, FormInput, FormTextArea, IssueCard, IssueView } from './ReportManagerComponents';
+import { Issue } from './reportManagerSchema';
+import { BugType, FileData, Priority, ReportForm, ViewState, bugDropdownItems, darkColors, emptyReportForm, formatTitle, getAllIssues, isDarkMode, lightColors, passesTagFilter, priorityDropdownItems, uploadFilesToServer } from './reportManagerUtils';
/**
* Class for reporting and viewing Github issues within the app.
*/
@observer
export class ReportManager extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: ReportManager;
@observable private isOpen = false;
@observable private query = '';
+ // eslint-disable-next-line react/sort-comp
@action private setQuery = (q: string) => {
this.query = q;
};
@@ -82,7 +88,9 @@ export class ReportManager extends React.Component<{}> {
this.formData = newData;
});
- public close = action(() => (this.isOpen = false));
+ public close = action(() => {
+ this.isOpen = false;
+ });
public open = action(async () => {
this.isOpen = true;
if (this.shownIssues.length === 0) {
@@ -133,7 +141,7 @@ export class ReportManager extends React.Component<{}> {
const req = await this.octokit.request('POST /repos/{owner}/{repo}/issues', {
owner: 'brown-dash',
repo: 'Dash-Web',
- title: formatTitle(this.formData.title, Doc.CurrentUserEmail),
+ title: formatTitle(this.formData.title, ClientUtils.CurrentUserEmail()),
body: `${this.formData.description} ${formattedLinks.length > 0 ? `\n\nFiles:\n${formattedLinks.join('\n')}` : ''}`,
labels: ['from-dash-app', this.formData.type, this.formData.priority],
});
@@ -164,7 +172,7 @@ export class ReportManager extends React.Component<{}> {
* @returns JSX element of a piece of media (image, video, audio)
*/
private getMediaPreview = (fileData: FileData): JSX.Element => {
- const file = fileData.file;
+ const { file } = fileData;
const mimeType = file.type;
const preview = URL.createObjectURL(file);
@@ -179,7 +187,8 @@ export class ReportManager extends React.Component<{}> {
</div>
</div>
);
- } else if (mimeType.startsWith('video/')) {
+ }
+ if (mimeType.startsWith('video/')) {
return (
<div key={fileData._id} className="report-media-wrapper">
<div className="report-media-content">
@@ -193,7 +202,8 @@ export class ReportManager extends React.Component<{}> {
</div>
</div>
);
- } else if (mimeType.startsWith('audio/')) {
+ }
+ if (mimeType.startsWith('audio/')) {
return (
<div key={fileData._id} className="report-audio-wrapper">
<audio src={preview} controls />
@@ -203,7 +213,7 @@ export class ReportManager extends React.Component<{}> {
</div>
);
}
- return <></>;
+ return <div />;
};
/**
@@ -306,8 +316,8 @@ export class ReportManager extends React.Component<{}> {
<div className="report-selects">
<Dropdown
color={StrCast(Doc.UserDoc().userColor)}
- formLabel={'Type'}
- closeOnSelect={true}
+ formLabel="Type"
+ closeOnSelect
items={bugDropdownItems}
selectedVal={this.formData.type}
setSelectedVal={val => {
@@ -319,8 +329,8 @@ export class ReportManager extends React.Component<{}> {
/>
<Dropdown
color={StrCast(Doc.UserDoc().userColor)}
- formLabel={'Priority'}
- closeOnSelect={true}
+ formLabel="Priority"
+ closeOnSelect
items={priorityDropdownItems}
selectedVal={this.formData.priority}
setSelectedVal={val => {
@@ -346,7 +356,7 @@ export class ReportManager extends React.Component<{}> {
text="Submit"
type={Type.TERT}
color={StrCast(Doc.UserDoc().userVariantColor)}
- icon={<ReactLoading type="spin" color={'#ffffff'} width={20} height={20} />}
+ icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />}
iconPlacement="right"
onClick={() => {
this.reportIssue();
@@ -363,7 +373,7 @@ export class ReportManager extends React.Component<{}> {
/>
)}
<div style={{ position: 'absolute', top: '4px', right: '4px' }}>
- <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size={'16px'} />} onClick={this.close} />
+ <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={this.close} />
</div>
</div>
);
@@ -375,9 +385,8 @@ export class ReportManager extends React.Component<{}> {
private reportComponent = () => {
if (this.viewState === ViewState.VIEW) {
return this.viewIssuesComponent();
- } else {
- return this.reportIssueComponent();
}
+ return this.reportIssueComponent();
};
render() {
@@ -385,7 +394,7 @@ export class ReportManager extends React.Component<{}> {
<MainViewModal
contents={this.reportComponent()}
isDisplayed={this.isOpen}
- interactive={true}
+ interactive
closeOnExternalClick={this.close}
dialogueBoxStyle={{ width: 'auto', minWidth: '300px', height: '85vh', maxHeight: '90vh', background: StrCast(Doc.UserDoc().userBackgroundColor), borderRadius: '8px' }}
/>
diff --git a/src/client/util/reportManager/ReportManagerComponents.tsx b/src/client/util/reportManager/ReportManagerComponents.tsx
index 1e226bf6d..cecebc648 100644
--- a/src/client/util/reportManager/ReportManagerComponents.tsx
+++ b/src/client/util/reportManager/ReportManagerComponents.tsx
@@ -1,9 +1,15 @@
+/* eslint-disable react/require-default-props */
+/* eslint-disable prefer-destructuring */
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable no-use-before-define */
import * as React from 'react';
-import { Issue } from './reportManagerSchema';
-import { darkColors, dashBlue, getLabelColors, isDarkMode, lightColors } from './reportManagerUtils';
import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
+import { darkColors, dashBlue, getLabelColors, isDarkMode, lightColors } from './reportManagerUtils';
+import { Issue } from './reportManagerSchema';
import { StrCast } from '../../../fields/Types';
import { Doc } from '../../../fields/Doc';
@@ -18,7 +24,7 @@ interface FilterProps<T> {
}
// filter ui for issues (horizontal list of tags)
-export const Filter = <T extends string>({ items, activeValue, setActiveValue }: FilterProps<T>) => {
+export function Filter<T extends string>({ items, activeValue, setActiveValue }: FilterProps<T>) {
// establishing theme
const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor));
const colors = darkMode ? darkColors : lightColors;
@@ -28,7 +34,7 @@ export const Filter = <T extends string>({ items, activeValue, setActiveValue }:
return (
<div className="issues-filter">
<Tag
- text={'All'}
+ text="All"
onClick={() => {
setActiveValue(null);
}}
@@ -38,25 +44,23 @@ export const Filter = <T extends string>({ items, activeValue, setActiveValue }:
borderColor={activeValue === null ? StrCast(Doc.UserDoc().userColor) : colors.border}
border
/>
- {items.map(item => {
- return (
- <Tag
- key={item}
- text={item}
- onClick={() => {
- setActiveValue(item);
- }}
- fontSize="12px"
- backgroundColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : 'transparent'}
- color={activeValue === item ? activeTagTextColor : colors.textGrey}
- border
- borderColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : colors.border}
- />
- );
- })}
+ {items.map(item => (
+ <Tag
+ key={item}
+ text={item}
+ onClick={() => {
+ setActiveValue(item);
+ }}
+ fontSize="12px"
+ backgroundColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : 'transparent'}
+ color={activeValue === item ? activeTagTextColor : colors.textGrey}
+ border
+ borderColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : colors.border}
+ />
+ ))}
</div>
);
-};
+}
interface IssueCardProps {
issue: Issue;
@@ -64,7 +68,7 @@ interface IssueCardProps {
}
// Component for the issue cards list on the left
-export const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
+export function IssueCard({ issue, onSelect }: IssueCardProps) {
const [textColor, setTextColor] = React.useState('');
const [bgColor, setBgColor] = React.useState('transparent');
const [borderColor, setBorderColor] = React.useState('transparent');
@@ -103,14 +107,14 @@ export const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
<h3 className="issue-title">{issue.title}</h3>
</div>
);
-};
+}
interface IssueViewProps {
issue: Issue;
}
// Detailed issue view that displays on the right
-export const IssueView = ({ issue }: IssueViewProps) => {
+export function IssueView({ issue }: IssueViewProps) {
const [issueBody, setIssueBody] = React.useState('');
// Parses the issue body into a formatted markdown (main functionality is replacing urls with tags)
@@ -127,15 +131,16 @@ export const IssueView = ({ issue }: IssueViewProps) => {
parts.map(async part => {
if (imgTagRegex.test(part) || videoTagRegex.test(part) || audioTagRegex.test(part)) {
return `\n${await parseFileTag(part)}\n`;
- } else if (fileRegex.test(part)) {
+ }
+ if (fileRegex.test(part)) {
const tag = await parseDashFiles(part);
return tag;
- } else if (localRegex.test(part)) {
+ }
+ if (localRegex.test(part)) {
const tag = await parseLocalFiles(part);
return tag;
- } else {
- return part;
}
+ return part;
})
);
@@ -143,7 +148,7 @@ export const IssueView = ({ issue }: IssueViewProps) => {
};
// Extracts the src from an image tag and either returns the raw url if not accessible or a new image tag
- const parseFileTag = async (tag: string): Promise<string> => {
+ const parseFileTag = async (tag: string): Promise<string | undefined> => {
const regex = /src="([^"]+)"/;
let url = '';
const match = tag.match(regex);
@@ -160,18 +165,19 @@ export const IssueView = ({ issue }: IssueViewProps) => {
case '.png':
case '.jpeg':
case '.gif':
- return await getDisplayedFile(url, 'image');
+ return getDisplayedFile(url, 'image');
// video
case '.mp4':
case '.mpeg':
case '.webm':
case '.mov':
- return await getDisplayedFile(url, 'video');
- //audio
+ return getDisplayedFile(url, 'video');
+ // audio
case '.mp3':
case '.wav':
case '.ogg':
- return await getDisplayedFile(url, 'audio');
+ return getDisplayedFile(url, 'audio');
+ default:
}
return tag;
};
@@ -183,14 +189,15 @@ export const IssueView = ({ issue }: IssueViewProps) => {
const dashAudioRegex = /https:\/\/browndash\.com\/files[/\\]audio/;
if (dashImgRegex.test(url)) {
- return await getDisplayedFile(url, 'image');
- } else if (dashVideoRegex.test(url)) {
- return await getDisplayedFile(url, 'video');
- } else if (dashAudioRegex.test(url)) {
- return await getDisplayedFile(url, 'audio');
- } else {
- return url;
+ return getDisplayedFile(url, 'image');
}
+ if (dashVideoRegex.test(url)) {
+ return getDisplayedFile(url, 'video');
+ }
+ if (dashAudioRegex.test(url)) {
+ return getDisplayedFile(url, 'audio');
+ }
+ return url;
};
// Returns the corresponding HTML tag for a src url
@@ -200,31 +207,37 @@ export const IssueView = ({ issue }: IssueViewProps) => {
const dashAudioRegex = /http:\/\/localhost:1050\.com\/files[/\\]audio/;
if (imgRegex.test(url)) {
- return await getDisplayedFile(url, 'image');
- } else if (dashVideoRegex.test(url)) {
- return await getDisplayedFile(url, 'video');
- } else if (dashAudioRegex.test(url)) {
- return await getDisplayedFile(url, 'audio');
- } else {
- return url;
+ return getDisplayedFile(url, 'image');
+ }
+ if (dashVideoRegex.test(url)) {
+ return getDisplayedFile(url, 'video');
+ }
+ if (dashAudioRegex.test(url)) {
+ return getDisplayedFile(url, 'audio');
}
+ return url;
};
- const getDisplayedFile = async (url: string, fileType: 'image' | 'video' | 'audio'): Promise<string> => {
+ const getDisplayedFile = async (url: string, fileType: 'image' | 'video' | 'audio'): Promise<string | undefined> => {
switch (fileType) {
- case 'image':
+ case 'image': {
const imgValid = await isImgValid(url);
if (!imgValid) return `\n${url} (This image could not be loaded)\n`;
return `\n${url}\n<img width="100%" alt="Issue asset" src=${url} />\n`;
- case 'video':
+ }
+ case 'video': {
const videoValid = await isVideoValid(url);
if (!videoValid) return `\n${url} (This video could not be loaded)\n`;
return `\n${url}\n<video class="report-default-video" width="100%" controls alt="Issue asset" src=${url} />\n`;
- case 'audio':
+ }
+ case 'audio': {
const audioValid = await isAudioValid(url);
if (!audioValid) return `\n${url} (This audio could not be loaded)\n`;
return `\n${url}\n<audio src=${url} controls />\n`;
+ }
+ default:
}
+ return undefined;
};
// Loads an image and returns a promise that resolves as whether the image is valid or not
@@ -270,7 +283,7 @@ export const IssueView = ({ issue }: IssueViewProps) => {
<div className="issue-view">
<span className="issue-label">
Issue{' '}
- <a className="issue-link" href={issue.html_url} target="_blank">
+ <a className="issue-link" href={issue.html_url} target="_blank" rel="noreferrer">
#{issue.number}
</a>
</span>
@@ -292,7 +305,7 @@ export const IssueView = ({ issue }: IssueViewProps) => {
<ReactMarkdown children={issueBody} className="issue-content" remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} />
</div>
);
-};
+}
interface TagProps {
text: string;
@@ -305,7 +318,7 @@ interface TagProps {
}
// Small tag for labels of the issue
-export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) => {
+export function Tag({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) {
return (
<div
onClick={onClick ?? (() => {})}
@@ -314,14 +327,14 @@ export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColo
{text}
</div>
);
-};
+}
interface FormInputProps {
value: string;
placeholder: string;
onChange: (val: string) => void;
}
-export const FormInput = ({ value, placeholder, onChange }: FormInputProps) => {
+export function FormInput({ value, placeholder, onChange }: FormInputProps) {
const [inputBorderColor, setInputBorderColor] = React.useState('');
return (
@@ -349,9 +362,9 @@ export const FormInput = ({ value, placeholder, onChange }: FormInputProps) => {
}}
/>
);
-};
+}
-export const FormTextArea = ({ value, placeholder, onChange }: FormInputProps) => {
+export function FormTextArea({ value, placeholder, onChange }: FormInputProps) {
const [textAreaBorderColor, setTextAreaBorderColor] = React.useState('');
return (
@@ -378,4 +391,4 @@ export const FormTextArea = ({ value, placeholder, onChange }: FormInputProps) =
}}
/>
);
-};
+}
diff --git a/src/client/util/reportManager/reportManagerSchema.ts b/src/client/util/reportManager/reportManagerSchema.ts
index 9a1c7c3e9..171c24393 100644
--- a/src/client/util/reportManager/reportManagerSchema.ts
+++ b/src/client/util/reportManager/reportManagerSchema.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-use-before-define */
/**
* Issue interface schema from Github.
*/
diff --git a/src/client/util/reportManager/reportManagerUtils.ts b/src/client/util/reportManager/reportManagerUtils.ts
index 22e5eebbb..f14967e0a 100644
--- a/src/client/util/reportManager/reportManagerUtils.ts
+++ b/src/client/util/reportManager/reportManagerUtils.ts
@@ -63,9 +63,8 @@ export const getAllIssues = async (octokit: Octokit): Promise<any[]> => {
// 200 status means success
if (res.status === 200) {
return res.data;
- } else {
- throw new Error('Error getting issues');
}
+ throw new Error('Error getting issues');
};
/**
@@ -104,9 +103,7 @@ export const fileLinktoServerLink = (fileLink: string): string => {
* @param link response from file upload
* @returns server file path
*/
-export const getServerPath = (link: any): string => {
- return link.result.accessPaths.agnostic.server as string;
-};
+export const getServerPath = (link: any): string => link.result.accessPaths.agnostic.server as string;
/**
* Uploads media files to the server.
@@ -124,6 +121,7 @@ export const uploadFilesToServer = async (mediaFiles: FileData[]): Promise<strin
alert(err);
}
}
+ return undefined;
};
// helper functions
@@ -141,18 +139,16 @@ export const passesTagFilter = (issue: Issue, priorityFilter: string | null, bug
passesPriority = issue.labels.some(label => {
if (typeof label === 'string') {
return label === priorityFilter;
- } else {
- return label.name === priorityFilter;
}
+ return label.name === priorityFilter;
});
}
if (bugFilter) {
passesBug = issue.labels.some(label => {
if (typeof label === 'string') {
return label === bugFilter;
- } else {
- return label.name === bugFilter;
}
+ return label.name === bugFilter;
});
}
return passesPriority && passesBug;
@@ -217,7 +213,8 @@ export const bugColors: { [key: string]: string[] } = {
export const getLabelColors = (label: string): string[] => {
if (prioritySet.has(label as Priority)) {
return priorityColors[label];
- } else if (bugSet.has(label as BugType)) {
+ }
+ if (bugSet.has(label as BugType)) {
return bugColors[label];
}
return ['#0f73f6', '#ffffff'];
diff --git a/src/client/util/request-image-size.ts b/src/client/util/request-image-size.ts
index 57e8516ac..0f98a2710 100644
--- a/src/client/util/request-image-size.ts
+++ b/src/client/util/request-image-size.ts
@@ -14,19 +14,17 @@ const imageSize = require('image-size');
const HttpError = require('standard-http-error');
module.exports = function requestImageSize(options: any) {
- let opts = {
+ let opts: any = {
encoding: null,
};
if (options && typeof options === 'object') {
opts = Object.assign(options, opts);
} else if (options && typeof options === 'string') {
- opts = Object.assign(
- {
- uri: options,
- },
- opts
- );
+ opts = {
+ uri: options,
+ ...opts,
+ };
} else {
return Promise.reject(new Error('You should provide an URI string or a "request" options object.'));
}
@@ -38,7 +36,8 @@ module.exports = function requestImageSize(options: any) {
req.on('response', (res: any) => {
if (res.statusCode >= 400) {
- return reject(new HttpError(res.statusCode, res.statusMessage));
+ reject(new HttpError(res.statusCode, res.statusMessage));
+ return;
}
let buffer = Buffer.from([]);
@@ -51,20 +50,23 @@ module.exports = function requestImageSize(options: any) {
size = imageSize(buffer);
if (size) {
resolve(size);
- return req.abort();
+ req.abort();
}
- } catch (err) {}
+ } catch (err) {
+ /* empty */
+ }
});
res.on('error', reject);
res.on('end', () => {
if (!size) {
- return reject(new Error('Image has no size'));
+ reject(new Error('Image has no size'));
+ return;
}
size.downloaded = buffer.length;
- return resolve(size);
+ resolve(size);
});
});
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index db7e64deb..303672d90 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -1,8 +1,9 @@
import { action, makeObservable, observable, runInAction } from 'mobx';
import * as React from 'react';
-import { SettingsManager } from '../util/SettingsManager';
+import { SnappingManager } from '../util/SnappingManager';
import './AntimodeMenu.scss';
import { ObservableReactComponent } from './ObservableReactComponent';
+
export interface AntimodeMenuProps {}
/**
@@ -76,7 +77,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
};
@action
- protected pointerLeave = (e: React.PointerEvent) => {
+ protected pointerLeave = () => {
if (!this.Pinned && this._canFade) {
this._transitionProperty = 'opacity';
this._transitionDuration = '0.5s';
@@ -87,7 +88,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
};
@action
- protected pointerEntered = (e: React.PointerEvent) => {
+ protected pointerEntered = () => {
this._transitionProperty = 'opacity';
this._transitionDuration = '0.1s';
this._transitionDelay = '';
@@ -95,8 +96,10 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
};
@action
- protected togglePin = (e: React.MouseEvent) => {
- runInAction(() => (this.Pinned = !this.Pinned));
+ protected togglePin = () => {
+ runInAction(() => {
+ this.Pinned = !this.Pinned;
+ });
};
protected dragStart = (e: React.PointerEvent) => {
@@ -114,8 +117,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
@action
protected dragging = (e: PointerEvent) => {
- const width = this._mainCont.current!.getBoundingClientRect().width;
- const height = this._mainCont.current!.getBoundingClientRect().height;
+ const { width, height } = this._mainCont.current!.getBoundingClientRect();
const left = e.pageX - this._offsetX;
const top = e.pageY - this._offsetY;
@@ -139,9 +141,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
e.preventDefault();
};
- protected getDragger = () => {
- return <div className="antimodeMenu-dragger" key="dragger" onPointerDown={this.dragStart} style={{ width: '20px' }} />;
- };
+ protected getDragger = () => <div className="antimodeMenu-dragger" key="dragger" onPointerDown={this.dragStart} style={{ width: '20px' }} />;
protected getElement(buttons: JSX.Element, expanded: boolean = false) {
const containerClass = expanded ? 'antimodeMenu-cont expanded' : 'antimodeMenu-cont';
@@ -157,7 +157,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
left: this._left,
top: this._top,
opacity: this._opacity,
- background: SettingsManager.userBackgroundColor,
+ background: SnappingManager.userBackgroundColor,
transitionProperty: this._transitionProperty,
transitionDuration: this._transitionDuration,
transitionDelay: this._transitionDelay,
@@ -183,7 +183,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
height: 'inherit',
width: 200,
opacity: this._opacity,
- background: SettingsManager.userBackgroundColor,
+ background: SnappingManager.userBackgroundColor,
transitionProperty: this._transitionProperty,
transitionDuration: this._transitionDuration,
transitionDelay: this._transitionDelay,
@@ -206,7 +206,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
left: this._left,
top: this._top,
opacity: this._opacity,
- background: SettingsManager.userBackgroundColor,
+ background: SnappingManager.userBackgroundColor,
transitionProperty: this._transitionProperty,
transitionDuration: this._transitionDuration,
transitionDelay: this._transitionDelay,
diff --git a/src/client/views/ComponentDecorations.tsx b/src/client/views/ComponentDecorations.tsx
index ca4e5f2bc..929b549e0 100644
--- a/src/client/views/ComponentDecorations.tsx
+++ b/src/client/views/ComponentDecorations.tsx
@@ -1,14 +1,15 @@
import { observer } from 'mobx-react';
-import { SelectionManager } from '../util/SelectionManager';
-import './ComponentDecorations.scss';
import * as React from 'react';
+import './ComponentDecorations.scss';
+import { DocumentView } from './nodes/DocumentView';
@observer
export class ComponentDecorations extends React.Component<{ boundsTop: number; boundsLeft: number }, { value: string }> {
+ // eslint-disable-next-line no-use-before-define
static Instance: ComponentDecorations;
render() {
- const seldoc = SelectionManager.Views.lastElement();
+ const seldoc = DocumentView.Selected().lastElement();
return seldoc?.ComponentView?.componentUI?.(this.props.boundsLeft, this.props.boundsTop) ?? null;
}
}
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index ca877b93e..d784a14b8 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -1,16 +1,19 @@
+/* eslint-disable react/no-array-index-key */
+/* eslint-disable react/jsx-props-no-spreading */
+/* eslint-disable default-param-last */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { StrCast } from '../../fields/Types';
-import { SettingsManager } from '../util/SettingsManager';
+import { DivHeight, DivWidth } from '../../ClientUtils';
+import { SnappingManager } from '../util/SnappingManager';
import './ContextMenu.scss';
import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextMenuItem';
import { ObservableReactComponent } from './ObservableReactComponent';
-import { DivHeight, DivWidth } from '../../Utils';
@observer
export class ContextMenu extends ObservableReactComponent<{}> {
+ // eslint-disable-next-line no-use-before-define
static Instance: ContextMenu;
private _ignoreUp = false;
@@ -124,8 +127,8 @@ export class ContextMenu extends ObservableReactComponent<{}> {
@action
displayMenu = (x: number, y: number, initSearch = '', showSearch = false, onDisplay?: () => void) => {
- //maxX and maxY will change if the UI/font size changes, but will work for any amount
- //of items added to the menu
+ // maxX and maxY will change if the UI/font size changes, but will work for any amount
+ // of items added to the menu
this._showSearch = showSearch;
this._pageX = x;
@@ -147,15 +150,13 @@ export class ContextMenu extends ObservableReactComponent<{}> {
@computed get filteredItems(): (OriginalMenuProps | string[])[] {
const searchString = this._searchString.toLowerCase().split(' ');
- const matches = (descriptions: string[]): boolean => {
- return searchString.every(s => descriptions.some(desc => desc.toLowerCase().includes(s)));
- };
+ const matches = (descriptions: string[]) => searchString.every(s => descriptions.some(desc => desc.toLowerCase().includes(s)));
const flattenItems = (items: ContextMenuProps[], groupFunc: (groupName: any) => string[]) => {
let eles: (OriginalMenuProps | string[])[] = [];
const leaves: OriginalMenuProps[] = [];
- for (const item of items) {
- const description = item.description;
+ items.forEach(item => {
+ const { description } = item;
const path = groupFunc(description);
if ('subitems' in item) {
const children = flattenItems(item.subitems, name => [...groupFunc(description), name]);
@@ -163,13 +164,10 @@ export class ContextMenu extends ObservableReactComponent<{}> {
eles.push(path);
eles = eles.concat(children);
}
- } else {
- if (!matches(path)) {
- continue;
- }
+ } else if (matches(path)) {
leaves.push(item);
}
- }
+ });
eles = [...leaves, ...eles];
@@ -192,7 +190,7 @@ export class ContextMenu extends ObservableReactComponent<{}> {
key={index + value.join(' -> ')}
className="contextMenu-group"
style={{
- background: StrCast(SettingsManager.userVariantColor),
+ background: SnappingManager.userVariantColor,
}}>
<div className="contextMenu-description">{value.join(' -> ')}</div>
</div>
@@ -224,11 +222,11 @@ export class ContextMenu extends ObservableReactComponent<{}> {
display: this._display ? '' : 'none',
left: this.pageX,
...(this._yRelativeToTop ? { top: Math.max(0, this.pageY) } : { bottom: this.pageY }),
- background: SettingsManager.userBackgroundColor,
- color: SettingsManager.userColor,
+ background: SnappingManager.userBackgroundColor,
+ color: SnappingManager.userColor,
}}>
{!this.itemsNeedSearch ? null : (
- <span className={'search-icon'}>
+ <span className="search-icon">
<span className="icon-background">
<FontAwesomeIcon icon="search" size="lg" />
</span>
@@ -241,6 +239,7 @@ export class ContextMenu extends ObservableReactComponent<{}> {
value={this._searchString}
onKeyDown={this.onKeyDown}
onChange={this.onChange}
+ // eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
/>
</span>
@@ -267,7 +266,7 @@ export class ContextMenu extends ObservableReactComponent<{}> {
if (item) {
item.event({ x: this.pageX, y: this.pageY });
} else {
- //if (this._searchString.startsWith(this._defaultPrefix)) {
+ // if (this._searchString.startsWith(this._defaultPrefix)) {
this._defaultItem?.(this._searchString.substring(this._defaultPrefix.length));
}
this.closeMenu();
@@ -281,12 +280,10 @@ export class ContextMenu extends ObservableReactComponent<{}> {
this._searchString = e.target.value;
if (!this._searchString) {
this._selectedIndex = -1;
+ } else if (this._selectedIndex === -1) {
+ this._selectedIndex = 0;
} else {
- if (this._selectedIndex === -1) {
- this._selectedIndex = 0;
- } else {
- this._selectedIndex = Math.min(this.flatItems.length - 1, this._selectedIndex);
- }
+ this._selectedIndex = Math.min(this.flatItems.length - 1, this._selectedIndex);
}
};
}
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index d15ab749c..eb1030eec 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -1,26 +1,28 @@
-import * as React from 'react';
-import { observable, action, runInAction, makeObservable } from 'mobx';
-import { observer } from 'mobx-react';
+/* eslint-disable react/jsx-props-no-spreading */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, makeObservable, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { SnappingManager } from '../util/SnappingManager';
import { UndoManager } from '../util/UndoManager';
-import { SettingsManager } from '../util/SettingsManager';
import { ObservableReactComponent } from './ObservableReactComponent';
export interface OriginalMenuProps {
description: string;
event: (stuff?: any) => void;
undoable?: boolean;
- icon: IconProp | JSX.Element; //maybe should be optional (icon?)
+ icon: IconProp | JSX.Element; // maybe should be optional (icon?)
closeMenu?: () => void;
}
export interface SubmenuProps {
description: string;
+ // eslint-disable-next-line no-use-before-define
subitems: ContextMenuProps[];
noexpand?: boolean;
addDivider?: boolean;
- icon: IconProp; //maybe should be optional (icon?)
+ icon: IconProp; // maybe should be optional (icon?)
closeMenu?: () => void;
}
@@ -37,7 +39,9 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
}
componentDidMount() {
- runInAction(() => (this._items.length = 0));
+ runInAction(() => {
+ this._items.length = 0;
+ });
if ((this._props as SubmenuProps)?.subitems) {
(this._props as SubmenuProps).subitems?.forEach(i => runInAction(() => this._items.push(i)));
}
@@ -67,7 +71,9 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
this._overPosY = e.clientY;
this._overPosX = e.clientX;
this.currentTimeout = setTimeout(
- action(() => (this.overItem = true)),
+ action(() => {
+ this.overItem = true;
+ }),
ContextMenuItem.timeout
);
};
@@ -81,7 +87,9 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
return;
}
this.currentTimeout = setTimeout(
- action(() => (this.overItem = false)),
+ action(() => {
+ this.overItem = false;
+ }),
ContextMenuItem.timeout
);
};
@@ -97,14 +105,15 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
{this._props.icon ? <span className="contextMenu-item-icon-background">{this.isJSXElement(this._props.icon) ? this._props.icon : <FontAwesomeIcon icon={this._props.icon} size="sm" />}</span> : null}
<div className="contextMenu-description">{this._props.description.replace(':', '')}</div>
<div
- className={`contextMenu-item-background`}
+ className="contextMenu-item-background"
style={{
- background: SettingsManager.userColor,
+ background: SnappingManager.userColor,
}}
/>
</div>
);
- } else if ('subitems' in this._props) {
+ }
+ if ('subitems' in this._props) {
const where = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? 'flex-start' : this._overPosY > (window.innerHeight * 2) / 3 ? 'flex-end' : 'center';
const marginTop = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? '20px' : this._overPosY > (window.innerHeight * 2) / 3 ? '-20px' : '';
@@ -115,7 +124,7 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
style={{
marginLeft: window.innerWidth - this._overPosX - 50 > 0 ? '90%' : '20%',
marginTop,
- background: SettingsManager.userBackgroundColor,
+ background: SnappingManager.userBackgroundColor,
}}>
{this._items.map(prop => (
<ContextMenuItem {...prop} key={prop.description} closeMenu={this._props.closeMenu} />
@@ -144,17 +153,18 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
) : null}
<div className="contextMenu-description" onMouseEnter={this.onPointerEnter} style={{ alignItems: 'center', alignSelf: 'center' }}>
{this._props.description}
- <FontAwesomeIcon icon={'angle-right'} size="lg" style={{ position: 'absolute', right: '10px' }} />
+ <FontAwesomeIcon icon="angle-right" size="lg" style={{ position: 'absolute', right: '10px' }} />
</div>
<div
- className={`contextMenu-item-background`}
+ className="contextMenu-item-background"
style={{
- background: SettingsManager.userColor,
+ background: SnappingManager.userColor,
}}
/>
{submenu}
</div>
);
}
+ return null;
}
}
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index a4f598d1a..fc6a330f7 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -1,9 +1,12 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, ColorPicker, EditableText, Size, Type } from 'browndash-components';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { FaPlus } from 'react-icons/fa';
+import { ClientUtils } from '../../ClientUtils';
import { Doc, DocListCast } from '../../fields/Doc';
import { AclPrivate, DocAcl, DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
@@ -12,34 +15,73 @@ import { PrefetchProxy } from '../../fields/Proxy';
import { listSpec } from '../../fields/Schema';
import { ScriptField } from '../../fields/ScriptField';
import { Cast, ImageCast, StrCast } from '../../fields/Types';
-import { normalizeEmail } from '../../fields/util';
+import { SharingPermissions, inheritParentAcls, normalizeEmail } from '../../fields/util';
import { DocServer } from '../DocServer';
-import { Docs, DocumentOptions, DocUtils } from '../documents/Documents';
+import { DocUtils } from '../documents/DocUtils';
+import { Docs, DocumentOptions } from '../documents/Documents';
+import { dropActionType } from '../util/DropActionTypes';
import { HistoryUtil } from '../util/History';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { SettingsManager } from '../util/SettingsManager';
-import { SharingManager } from '../util/SharingManager';
-import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import { CollectionView } from './collections/CollectionView';
+import { SnappingManager } from '../util/SnappingManager';
+import { undoBatch, undoable } from '../util/UndoManager';
import { ContextMenu } from './ContextMenu';
import './DashboardView.scss';
-import { Colors } from './global/globalEnums';
import { MainViewModal } from './MainViewModal';
-import { ButtonType } from './nodes/FontIconBox/FontIconBox';
import { ObservableReactComponent } from './ObservableReactComponent';
-import { dropActionType } from '../util/DragManager';
+import { Colors } from './global/globalEnums';
+import { DocumentView } from './nodes/DocumentView';
+import { ButtonType } from './nodes/FontIconBox/FontIconBox';
enum DashboardGroup {
MyDashboards,
SharedDashboards,
}
+export type DocConfig = {
+ doc: Doc;
+ initialWidth?: number;
+ path?: Doc[];
+};
+
// DashboardView is the view with the dashboard previews, rendered when the app first loads
@observer
export class DashboardView extends ObservableReactComponent<{}> {
public static _urlState: HistoryUtil.DocUrl;
+ public static makeDocumentConfig(document: Doc, panelName?: string, width?: number, keyValue?: boolean) {
+ return {
+ type: 'react-component',
+ component: 'DocumentFrameRenderer',
+ title: document.title,
+ width: width,
+ props: {
+ documentId: document[Id],
+ keyValue,
+ panelName, // name of tab that can be used to close or replace its contents
+ },
+ };
+ }
+ static StandardCollectionDockingDocument(configs: Array<DocConfig>, options: DocumentOptions, id?: string, type: string = 'row') {
+ const layoutConfig = {
+ content: [
+ {
+ type: type,
+ content: [...configs.map(config => DashboardView.makeDocumentConfig(config.doc, undefined, config.initialWidth))],
+ },
+ ],
+ };
+ const doc = Docs.Create.DockDocument(
+ configs.map(c => c.doc),
+ JSON.stringify(layoutConfig),
+ ClientUtils.CurrentUserEmail() === 'guest' ? options : { acl_Guest: SharingPermissions.View, ...options },
+ id
+ );
+ configs.forEach(c => {
+ Doc.SetContainer(c.doc, doc);
+ inheritParentAcls(doc, c.doc, false);
+ });
+ return doc;
+ }
constructor(props: any) {
super(props);
makeObservable(this);
@@ -49,10 +91,18 @@ export class DashboardView extends ObservableReactComponent<{}> {
@observable private selectedDashboardGroup = DashboardGroup.MyDashboards;
@observable private newDashboardName = '';
@observable private newDashboardColor = '#AFAFAF';
- @action abortCreateNewDashboard = () => (this.openModal = false);
- @action setNewDashboardName = (name: string) => (this.newDashboardName = name);
- @action setNewDashboardColor = (color: string) => (this.newDashboardColor = color);
- @action selectDashboardGroup = (group: DashboardGroup) => (this.selectedDashboardGroup = group);
+ @action abortCreateNewDashboard = () => {
+ this.openModal = false;
+ };
+ @action setNewDashboardName = (name: string) => {
+ this.newDashboardName = name;
+ };
+ @action setNewDashboardColor = (color: string) => {
+ this.newDashboardColor = color;
+ };
+ @action selectDashboardGroup = (group: DashboardGroup) => {
+ this.selectedDashboardGroup = group;
+ };
clickDashboard = (e: React.MouseEvent, dashboard: Doc) => {
if (this.selectedDashboardGroup === DashboardGroup.SharedDashboards) {
@@ -65,7 +115,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
getDashboards = (whichGroup: DashboardGroup) => {
if (whichGroup === DashboardGroup.MyDashboards) {
- return DocListCast(Doc.MyDashboards.data).filter(dashboard => dashboard[DocData].author === Doc.CurrentUserEmail);
+ return DocListCast(Doc.MyDashboards.data).filter(dashboard => dashboard[DocData].author === ClientUtils.CurrentUserEmail());
}
return DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc.dockingConfig);
};
@@ -84,11 +134,11 @@ export class DashboardView extends ObservableReactComponent<{}> {
<div
className="new-dashboard"
style={{
- background: SettingsManager.userBackgroundColor,
- color: SettingsManager.userColor,
+ background: SnappingManager.userBackgroundColor,
+ color: SnappingManager.userColor,
}}>
<div className="header">Create New Dashboard</div>
- <EditableText formLabel="Title" placeholder={this.newDashboardName} type={Type.SEC} color={SettingsManager.userColor} setVal={val => this.setNewDashboardName(val as string)} fillWidth />
+ <EditableText formLabel="Title" placeholder={this.newDashboardName} type={Type.SEC} color={SnappingManager.userColor} setVal={val => this.setNewDashboardName(val as string)} fillWidth />
<ColorPicker
formLabel="Background" //
colorPickerType="github"
@@ -98,8 +148,8 @@ export class DashboardView extends ObservableReactComponent<{}> {
setSelectedColor={this.setNewDashboardColor}
/>
<div className="button-bar">
- <Button text="Cancel" color={SettingsManager.userColor} onClick={this.abortCreateNewDashboard} />
- <Button text="Create" color={SettingsManager.userVariantColor} type={Type.TERT} onClick={() => this.createNewDashboard(this.newDashboardName, this.newDashboardColor)} />
+ <Button text="Cancel" color={SnappingManager.userColor} onClick={this.abortCreateNewDashboard} />
+ <Button text="Create" color={SnappingManager.userVariantColor} type={Type.TERT} onClick={() => this.createNewDashboard(this.newDashboardName, this.newDashboardColor)} />
</div>
</div>
);
@@ -120,7 +170,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
ContextMenu.Instance.addItem({
description: `Share Dashboard`,
- event: () => SharingManager.Instance.open(undefined, dashboard),
+ event: () => DocumentView.ShareOpen(undefined, dashboard),
icon: 'edit',
});
ContextMenu.Instance.addItem({
@@ -133,15 +183,15 @@ export class DashboardView extends ObservableReactComponent<{}> {
};
render() {
- const color = SettingsManager.userColor;
- const variant = SettingsManager.userVariantColor;
+ const color = SnappingManager.userColor;
+ const variant = SnappingManager.userVariantColor;
return (
<>
<div className="dashboard-view">
<div className="left-menu">
- <Button text="My Dashboards" active={this.selectedDashboardGroup === DashboardGroup.MyDashboards} color={color} align={'flex-start'} onClick={() => this.selectDashboardGroup(DashboardGroup.MyDashboards)} fillWidth />
+ <Button text="My Dashboards" active={this.selectedDashboardGroup === DashboardGroup.MyDashboards} color={color} align="flex-start" onClick={() => this.selectDashboardGroup(DashboardGroup.MyDashboards)} fillWidth />
<Button
- text={'Shared Dashboards' + ' (' + this.getDashboards(DashboardGroup.SharedDashboards).length + ')'}
+ text={'Shared Dashboards (' + this.getDashboards(DashboardGroup.SharedDashboards).length + ')'}
active={this.selectedDashboardGroup === DashboardGroup.SharedDashboards}
color={this.getDashboards(DashboardGroup.SharedDashboards).some(dash => !DocListCast(Doc.MySharedDocs.viewed).includes(dash)) ? 'green' : color}
align="flex-start"
@@ -156,7 +206,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
: this.getDashboards(this.selectedDashboardGroup).map(dashboard => {
const href = ImageCast(dashboard.thumb)?.url?.href;
const shared = Object.keys(dashboard[DocAcl])
- .filter(key => key !== `acl-${normalizeEmail(Doc.CurrentUserEmail)}` && !['acl-Me', 'acl-Guest'].includes(key))
+ .filter(key => key !== `acl_${normalizeEmail(ClientUtils.CurrentUserEmail())}` && !['acl_Me', 'acl_Guest'].includes(key))
.some(key => dashboard[DocAcl][key] !== AclPrivate);
return (
<div
@@ -166,14 +216,22 @@ export class DashboardView extends ObservableReactComponent<{}> {
onContextMenu={e => this.onContextMenu(dashboard, e)}
onClick={e => this.clickDashboard(e, dashboard)}>
<img
+ alt=""
src={
href ??
'https://media.istockphoto.com/photos/hot-air-balloons-flying-over-the-botan-canyon-in-turkey-picture-id1297349747?b=1&k=20&m=1297349747&s=170667a&w=0&h=oH31fJty_4xWl_JQ4OIQWZKP8C6ji9Mz7L4XmEnbqRU='
}
/>
<div className="info">
- <EditableText type={Type.PRIM} color={color} val={StrCast(dashboard.title)} setVal={val => (dashboard[DocData].title = val)} />
- {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ? <div>unviewed</div> : <div></div>}
+ <EditableText
+ type={Type.PRIM}
+ color={color}
+ val={StrCast(dashboard.title)}
+ setVal={val => {
+ dashboard[DocData].title = val;
+ }}
+ />
+ {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ? <div>unviewed</div> : <div />}
<div
className="more"
onPointerDown={e => {
@@ -187,7 +245,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
<div
className="background"
style={{
- background: SettingsManager.userColor,
+ background: SnappingManager.userColor,
filter: 'opacity(0.2)',
}}
/>
@@ -201,7 +259,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
<div
className="background"
style={{
- background: SettingsManager.userColor,
+ background: SnappingManager.userColor,
filter: 'opacity(0.2)',
}}
/>
@@ -209,7 +267,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
)}
</div>
</div>
- <MainViewModal contents={this.namingInterface} isDisplayed={this.openModal} interactive={true} closeOnExternalClick={this.abortCreateNewDashboard} dialogueBoxStyle={{ width: '400px', height: '180px', color: Colors.LIGHT_GRAY }} />
+ <MainViewModal contents={this.namingInterface} isDisplayed={this.openModal} interactive closeOnExternalClick={this.abortCreateNewDashboard} dialogueBoxStyle={{ width: '400px', height: '180px', color: Colors.LIGHT_GRAY }} />
</>
);
}
@@ -217,9 +275,6 @@ export class DashboardView extends ObservableReactComponent<{}> {
public static closeActiveDashboard() {
Doc.ActiveDashboard = undefined;
}
- public static snapshotDashboard() {
- return CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard);
- }
public static openSharedDashboard = (dashboard: Doc) => {
Doc.AddDocToList(Doc.MySharedDocs, 'viewed', dashboard);
@@ -250,16 +305,17 @@ export class DashboardView extends ObservableReactComponent<{}> {
});
if (state.readonly === true || state.readonly === null) {
DocServer.Control.makeReadOnly();
- } else if (state.safe) {
- if (!state.nro) {
- DocServer.Control.makeReadOnly();
- }
- CollectionView.SetSafeMode(true);
+ // } else if (state.safe) {
+ // if (!state.nro) {
+ // DocServer.Control.makeReadOnly();
+ // }
+ // CollectionView.SetSafeMode(true);
} else if (state.nro || state.nro === null || state.readonly === false) {
+ /* empty */
} else if (doc.readOnly) {
DocServer.Control.makeReadOnly();
} else {
- Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable();
+ ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Control.makeEditable();
}
}
@@ -278,7 +334,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
public static resetDashboard = (dashboard: Doc) => {
const config = StrCast(dashboard.dockingConfig);
- const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+ const matches = config.match(/"documentId":"[a-z0-9-]+"/g);
const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? [];
const components =
@@ -372,9 +428,9 @@ export class DashboardView extends ObservableReactComponent<{}> {
title: `Untitled Tab 1`,
};
- const title = name ? name : `Dashboard ${dashboardCount}`;
+ const title = name || `Dashboard ${dashboardCount}`;
const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
- const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row');
+ const dashboardDoc = DashboardView.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row');
Doc.AddDocToList(Doc.MyHeaderBar, 'data', freeformDoc, undefined, undefined, true);
dashboardDoc.pane_count = 1;
@@ -473,23 +529,23 @@ export class DashboardView extends ObservableReactComponent<{}> {
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function createNewDashboard() {
return DashboardView.createNewDashboard();
}, 'creates a new dashboard when called');
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function shareDashboard(dashboard: Doc) {
- SharingManager.Instance.open(undefined, dashboard);
+ DocumentView.ShareOpen(undefined, dashboard);
}, 'opens sharing dialog for Dashboard');
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function removeDashboard(dashboard: Doc) {
DashboardView.removeDashboard(dashboard);
}, 'Remove Dashboard from Dashboards');
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function resetDashboard(dashboard: Doc) {
DashboardView.resetDashboard(dashboard);
}, 'move all dashboard tabs to single stack');
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function addToDashboards(dashboard: Doc) {
DashboardView.openDashboard(Doc.MakeEmbedding(dashboard));
}, 'adds Dashboard to set of Dashboards');
-ScriptingGlobals.add(async function snapshotDashboard() {
- const batch = UndoManager.StartBatch('snapshot');
- await DashboardView.snapshotDashboard();
- batch.end();
-}, 'creates a snapshot copy of a dashboard');
diff --git a/src/client/views/DictationOverlay.tsx b/src/client/views/DictationOverlay.tsx
index e098bc361..b242acdba 100644
--- a/src/client/views/DictationOverlay.tsx
+++ b/src/client/views/DictationOverlay.tsx
@@ -7,14 +7,14 @@ import { MainViewModal } from './MainViewModal';
@observer
export class DictationOverlay extends React.Component {
+ // eslint-disable-next-line no-use-before-define
public static Instance: DictationOverlay;
@observable private _dictationState = DictationManager.placeholder;
@observable private _dictationSuccessState: boolean | undefined = undefined;
@observable private _dictationDisplayState = false;
@observable private _dictationListeningState: DictationManager.Controls.ListeningUIStatus = false;
- public isPointerDown = false;
- public overlayTimeout: NodeJS.Timeout | undefined;
+ // eslint-disable-next-line react/no-unused-class-component-methods
public hasActiveModal = false;
constructor(props: any) {
@@ -23,47 +23,32 @@ export class DictationOverlay extends React.Component {
DictationOverlay.Instance = this;
}
- public initiateDictationFade = () => {
- const duration = DictationManager.Commands.dictationFadeDuration;
- this.overlayTimeout = setTimeout(() => {
- this.dictationOverlayVisible = false;
- this.dictationSuccess = undefined;
- DictationOverlay.Instance.hasActiveModal = false;
- setTimeout(() => (this.dictatedPhrase = DictationManager.placeholder), 500);
- }, duration);
- };
- public cancelDictationFade = () => {
- if (this.overlayTimeout) {
- clearTimeout(this.overlayTimeout);
- this.overlayTimeout = undefined;
- }
- };
-
- @computed public get dictatedPhrase() {
- return this._dictationState;
- }
- @computed public get dictationSuccess() {
- return this._dictationSuccessState;
- }
- @computed public get dictationOverlayVisible() {
- return this._dictationDisplayState;
- }
- @computed public get isListening() {
- return this._dictationListeningState;
- }
-
+ @computed public get dictatedPhrase() { return this._dictationState; } // prettier-ignore
public set dictatedPhrase(value: string) {
- runInAction(() => (this._dictationState = value));
+ runInAction(() => {
+ this._dictationState = value;
+ });
}
+ @computed public get dictationSuccess() { return this._dictationSuccessState; } // prettier-ignore
public set dictationSuccess(value: boolean | undefined) {
- runInAction(() => (this._dictationSuccessState = value));
+ runInAction(() => { this._dictationSuccessState = value; }); // prettier-ignore
}
+ @computed public get dictationOverlayVisible() { return this._dictationDisplayState; } // prettier-ignore
public set dictationOverlayVisible(value: boolean) {
- runInAction(() => (this._dictationDisplayState = value));
+ runInAction(() => { this._dictationDisplayState = value; }); // prettier-ignore
}
+ @computed public get isListening() { return this._dictationListeningState; } // prettier-ignore
public set isListening(value: DictationManager.Controls.ListeningUIStatus) {
- runInAction(() => (this._dictationListeningState = value));
+ runInAction(() => { this._dictationListeningState = value; }); // prettier-ignore
}
+ public initiateDictationFade = () => {
+ setTimeout(() => {
+ this.dictationOverlayVisible = false;
+ this.dictationSuccess = undefined;
+ DictationOverlay.Instance.hasActiveModal = false;
+ setTimeout(() => { this.dictatedPhrase = DictationManager.placeholder; }, 500); // prettier-ignore
+ }, DictationManager.Commands.dictationFadeDuration);
+ };
render() {
const success = this.dictationSuccess;
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index de4df1830..70b20aec7 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,30 +1,30 @@
import { action, computed, makeObservable, observable } from 'mobx';
import * as React from 'react';
-import { returnFalse } from '../../Utils';
+import { returnFalse } from '../../ClientUtils';
import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols';
+import { Doc, DocListCast, FieldType, Opt } from '../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData, DocViews } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
import { RefField } from '../../fields/RefField';
+import { toList } from '../../fields/Types';
import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util';
import { DocumentType } from '../documents/DocumentTypes';
-import { DocUtils } from '../documents/Documents';
-import { DocumentManager } from '../util/DocumentManager';
import { DragManager } from '../util/DragManager';
import { ObservableReactComponent } from './ObservableReactComponent';
-import { CollectionFreeFormView } from './collections/collectionFreeForm';
-import { DocumentView, OpenWhere } from './nodes/DocumentView';
-import { FieldViewProps, FocusViewOptions } from './nodes/FieldView';
-import { PinProps } from './nodes/trails';
+import { PinProps } from './PinFuncs';
+import { DocumentView } from './nodes/DocumentView';
+import { FocusViewOptions } from './nodes/FocusViewOptions';
+import { FieldViewProps } from './nodes/FieldView';
+import { OpenWhere } from './nodes/OpenWhere';
+// import { DocUtils } from '../documents/Documents';
/**
* Shared interface among all viewBox'es (ie, react classes that render the contents of a Doc)
* Many of these methods only make sense for specific viewBox'es, but they should be written to
* be as general as possible
*/
-export interface ViewBoxInterface {
- fieldKey?: string;
- annotationKey?: string;
+export class ViewBoxInterface<P> extends ObservableReactComponent<React.PropsWithChildren<P>> {
+ promoteCollection?: () => void; // moves contents of collection to parent
updateIcon?: () => void; // updates the icon representation of the document
getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
restoreView?: (viewSpec: Doc) => boolean;
@@ -41,12 +41,13 @@ export interface ViewBoxInterface {
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)
setKeyFrameEditing?: (set: boolean) => void; // 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)
+ playTrail?: (docs: Doc[]) => void;
playFrom?: (time: number, endTime?: number) => void;
Pause?: () => void; // pause a media document (eg, audio/video)
IsPlaying?: () => boolean; // is a media document playing
TogglePause?: (keep?: boolean) => void; // toggle media document playing state
setFocus?: () => void; // sets input focus to the componentView
- setData?: (data: Field | Promise<RefField | undefined>) => boolean;
+ setData?: (data: FieldType | Promise<RefField | undefined>) => boolean;
componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void;
dragConfig?: (dragData: DragManager.DocumentDragData) => void; // function to setup dragData in custom way (see TreeViews which add a tree view flag)
@@ -57,6 +58,8 @@ export interface ViewBoxInterface {
ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number };
search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
+ dontRegisterView?: () => boolean; // KeyValueBox's don't want to register their views
+ isUnstyledView?: () => boolean; // SchemaView and KeyValue are unstyled -- not titles, no opacity, no animations
}
/**
* DocComponent returns a React base class used by Doc views with accessors for unpacking the Document,layoutDoc, and dataDoc's
@@ -108,7 +111,7 @@ export function DocComponent<P extends DocComponentProps>() {
* Example views include: InkingStroke, FontIconBox, EquationBox, etc
*/
export function ViewBoxBaseComponent<P extends FieldViewProps>() {
- class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
+ class Component extends ViewBoxInterface<P> {
constructor(props: P) {
super(props);
makeObservable(this);
@@ -163,7 +166,7 @@ export function ViewBoxBaseComponent<P extends FieldViewProps>() {
* Example views include: PDFBox, ImageBox, MapBox, etc
*/
export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
- class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
+ class Component extends ViewBoxInterface<P> {
@observable _annotationKeySuffix = () => 'annotations';
@observable _isAnyChildContentActive = false;
@@ -214,11 +217,9 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
return this.fieldKey + (this._annotationKeySuffix() ? '_' + this._annotationKeySuffix() : '');
}
- @action.bound
- removeDocument(doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean, dontAddToRemoved?: boolean): boolean {
+ override removeDocument = (docIn: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean, dontAddToRemoved?: boolean): boolean => {
const effectiveAcl = GetEffectiveAcl(this.dataDoc);
- const indocs = doc instanceof Doc ? [doc] : doc;
- const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin);
+ const docs = toList(docIn).filter(fdoc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(fdoc) === AclAdmin);
// docs.forEach(doc => doc.annotationOn === this.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true));
const targetDataDoc = this.Document[DocData]; // this.dataDoc; // we want to write to the template, not the actual data doc
@@ -227,17 +228,17 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
if (toRemove.length !== 0) {
const recentlyClosed = this.Document !== Doc.MyRecentlyClosed ? Doc.MyRecentlyClosed : undefined;
- toRemove.forEach(doc => {
- leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
- Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc, true);
- doc.embedContainer = undefined;
- if (recentlyClosed && !dontAddToRemoved && doc.type !== DocumentType.LOADING) {
- Doc.AddDocToList(recentlyClosed, 'data', doc, undefined, true, true);
- Doc.RemoveEmbedding(doc, doc);
+ toRemove.forEach(rdoc => {
+ // leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
+ Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, rdoc, true);
+ rdoc.embedContainer = undefined;
+ if (recentlyClosed && !dontAddToRemoved && rdoc.type !== DocumentType.LOADING) {
+ Doc.AddDocToList(recentlyClosed, 'data', rdoc, undefined, true, true);
+ Doc.RemoveEmbedding(rdoc, rdoc);
}
});
if (targetDataDoc.isGroup && DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]).length < 2) {
- (DocumentManager.Instance.getFirstDocumentView(targetDataDoc)?.ComponentView as CollectionFreeFormView)?.promoteCollection();
+ Array.from(targetDataDoc[DocViews])[0]?.ComponentView?.promoteCollection?.();
} else {
this.isAnyChildContentActive() && this._props.select(false);
}
@@ -245,26 +246,25 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
}
return false;
- }
+ };
// this is called with the document that was dragged and the collection to move it into.
// if the target collection is the same as this collection, then the move will be allowed.
// otherwise, the document being moved must be able to be removed from its container before
// moving it into the target.
- @action.bound
- moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => {
+ moveDocument = (docs: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => {
if (Doc.AreProtosEqual(this._props.Document, targetCollection)) {
return true;
}
- const first = doc instanceof Doc ? doc : doc[0];
+ const first = toList(docs)[0];
if (!first?._dragOnlyWithinContainer && addDocument !== returnFalse) {
- return this.removeDocument(doc, annotationKey, false, true) && addDocument(doc, annotationKey);
+ return this.removeDocument(docs, annotationKey, false, true) && addDocument(docs, annotationKey);
}
return false;
};
- @action.bound
- addDocument = (doc: Doc | Doc[], annotationKey?: string): boolean => {
- const docs = doc instanceof Doc ? [doc] : doc;
- if (this._props.filterAddDocument?.(docs) === false || docs.find(doc => Doc.AreProtosEqual(doc, this.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.Document))) {
+
+ override addDocument = (docIn: Doc | Doc[], annotationKey?: string): boolean => {
+ const docs = toList(docIn);
+ if (this._props.filterAddDocument?.(docs) === false || docs.find(fdoc => Doc.AreProtosEqual(fdoc, this.Document) && Doc.LayoutField(fdoc) === Doc.LayoutField(this.Document))) {
return false;
}
const targetDataDoc = this.Document[DocData]; // this.dataDoc; // we want to write to the template, not the actual data doc
@@ -276,12 +276,12 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
const added = docs;
if (added.length) {
if ([AclAugment, AclEdit, AclAdmin].includes(effectiveAcl)) {
- added.forEach(doc => {
- doc._dragOnlyWithinContainer = undefined;
- if (annotationKey ?? this._annotationKeySuffix()) doc[DocData].annotationOn = this.Document;
- else doc[DocData].annotationOn = undefined;
- Doc.SetContainer(doc, this.Document);
- inheritParentAcls(targetDataDoc, doc, true);
+ added.forEach(adoc => {
+ adoc._dragOnlyWithinContainer = undefined;
+ if (annotationKey ?? this._annotationKeySuffix()) adoc[DocData].annotationOn = this.Document;
+ else adoc[DocData].annotationOn = undefined;
+ Doc.SetContainer(adoc, this.Document);
+ inheritParentAcls(targetDataDoc, adoc, true);
});
const annoDocs = Doc.Get(targetDataDoc, annotationKey ?? this.annotationKey, true) as List<Doc>; // get the dataDoc directly ... when using templates there may be some default items already there, but we can't change them, so we copy them below (should really be some kind of inheritance since the template contents could change)
@@ -295,7 +295,10 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
isAnyChildContentActive = () => this._isAnyChildContentActive;
- whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
+ whenChildContentsActiveChanged = action((isActive: boolean) => {
+ this._isAnyChildContentActive = isActive;
+ this._props.whenChildContentsActiveChanged(isActive);
+ });
}
return Component;
}
diff --git a/src/client/views/DocViewUtils.ts b/src/client/views/DocViewUtils.ts
new file mode 100644
index 000000000..1f5f29c7e
--- /dev/null
+++ b/src/client/views/DocViewUtils.ts
@@ -0,0 +1,21 @@
+/* eslint-disable prefer-destructuring */
+/* eslint-disable default-param-last */
+/* eslint-disable no-use-before-define */
+import { runInAction } from 'mobx';
+import { Doc, SetActiveAudioLinker } from '../../fields/Doc';
+import { DocUtils } from '../documents/DocUtils';
+import { FieldViewProps } from './nodes/FieldView';
+
+export namespace DocViewUtils {
+ export const ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = [];
+
+ export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) {
+ broadcastEvent && runInAction(() => { Doc.RecordingEvent += 1; }); // prettier-ignore
+ return ActiveRecordings.map(audio => {
+ const sourceDoc = getSourceDoc();
+ return sourceDoc && DocUtils.MakeLink(sourceDoc, audio.getAnchor(true) || audio.props.Document, { link_relationship: 'recording annotation:linked recording', link_description: 'recording timeline' });
+ });
+ }
+
+ SetActiveAudioLinker(MakeLinkToActiveAudio);
+}
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 15ce4c15f..487868169 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,37 +1,42 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { IconLookup, IconProp } from '@fortawesome/fontawesome-svg-core';
+import { faCalendarDays } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, makeObservable, observable, runInAction } from 'mobx';
+import { Popup } from 'browndash-components';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
+import { FaEdit } from 'react-icons/fa';
+import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
import { Doc } from '../../fields/Doc';
import { Cast, DocCast } from '../../fields/Types';
-import { DocUtils } from '../documents/Documents';
+import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils';
import { CalendarManager } from '../util/CalendarManager';
-import { DragManager, dropActionType } from '../util/DragManager';
-import { IsFollowLinkScript } from '../util/LinkFollower';
-import { SelectionManager } from '../util/SelectionManager';
+import { DictationManager } from '../util/DictationManager';
+import { DragManager } from '../util/DragManager';
+import { dropActionType } from '../util/DropActionTypes';
import { SharingManager } from '../util/SharingManager';
import { UndoManager, undoBatch } from '../util/UndoManager';
import './DocumentButtonBar.scss';
import { ObservableReactComponent } from './ObservableReactComponent';
-import { TabDocView } from './collections/TabDocView';
+import { PinProps } from './PinFuncs';
+import { TemplateMenu } from './TemplateMenu';
import { Colors } from './global/globalEnums';
import { LinkPopup } from './linking/LinkPopup';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
-import { DocumentView, DocumentViewInternal, OpenWhere } from './nodes/DocumentView';
+import { DocumentView } from './nodes/DocumentView';
+import { OpenWhere } from './nodes/OpenWhere';
import { DashFieldView } from './nodes/formattedText/DashFieldView';
-import { PinProps } from './nodes/trails';
-import { faCalendarDays } from '@fortawesome/free-solid-svg-icons';
-import { Popup } from 'browndash-components';
-import { TemplateMenu } from './TemplateMenu';
-import { FaEdit } from 'react-icons/fa';
@observer
export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (DocumentView | undefined)[]; stack?: any }> {
private _dragRef = React.createRef<HTMLDivElement>();
- @observable public static Instance: DocumentButtonBar;
+ // eslint-disable-next-line no-use-before-define
+ public static Instance: DocumentButtonBar;
constructor(props: any) {
super(props);
@@ -43,7 +48,6 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
return this._props.views()?.[0];
}
- @observable subFollow = '';
@computed
get followLinkButton() {
const targetDoc = this.view0?.Document;
@@ -58,8 +62,12 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
key={icon.toString()}
size="sm"
icon={icon}
- onPointerEnter={action(e => (this.subPin = allDocs ? 'All ' : ''))}
- onPointerLeave={action(e => (this.subPin = ''))}
+ onPointerEnter={action(() => {
+ this.subPin = allDocs ? 'All ' : '';
+ })}
+ onPointerLeave={action(() => {
+ this.subPin = '';
+ })}
onClick={e => {
this._props.views().forEach(dv => click(dv!.Document));
e.stopPropagation();
@@ -75,12 +83,14 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
<div
className="documentButtonBar-icon documentButtonBar-follow"
style={{ backgroundColor: followLink ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: followLink ? Colors.BLACK : Colors.WHITE }}
- onClick={undoBatch(e => this._props.views().map(view => view?.toggleFollowLink(undefined, false)))}>
+ onClick={undoBatch(() => this._props.views().map(view => view?.toggleFollowLink(undefined, false)))}>
<div className="documentButtonBar-followTypes">
{followBtn(
true,
- (doc: Doc) => (doc.followAllLinks = !doc.followAllLinks),
- (doc?: Doc) => (doc?.followAllLinks ? true : false),
+ (doc: Doc) => {
+ doc.followAllLinks = !doc.followAllLinks;
+ },
+ (doc?: Doc) => !!doc?.followAllLinks,
'window-maximize'
)}
</div>
@@ -98,22 +108,22 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
<div className="documentButtonBar-linkTypes">
<Tooltip title={<div>search for target</div>}>
<div className="documentButtonBar-button">
- <button style={{ backgroundColor: 'transparent', width: 35, height: 35, display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative' }} onPointerDown={this.toggleLinkSearch}>
- <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(1.5)' }} icon={'search'} size="lg" />
- <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(0.5)', transformOrigin: 'center', top: 9, left: 2 }} icon={'link'} size="lg" />
+ <button type="button" style={{ backgroundColor: 'transparent', width: 35, height: 35, display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative' }} onPointerDown={this.toggleLinkSearch}>
+ <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(1.5)' }} icon="search" size="lg" />
+ <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(0.5)', transformOrigin: 'center', top: 9, left: 2 }} icon="link" size="lg" />
</button>
</div>
</Tooltip>
<Tooltip title={<div>open linked trail</div>}>
<div className="documentButtonBar-button">
- <button style={{ backgroundColor: 'transparent', width: 35, height: 35, display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative' }} onPointerDown={this.toggleTrail}>
+ <button type="button" style={{ backgroundColor: 'transparent', width: 35, height: 35, display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative' }} onPointerDown={this.toggleTrail}>
<FontAwesomeIcon icon="taxi" size="lg" />
</button>
</div>
</Tooltip>
</div>
<div style={{ width: 25, height: 25 }}>
- <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
+ <DocumentLinksButton View={this.view0} AlwaysOn InMenu StartLink />
</div>
</div>
);
@@ -132,8 +142,12 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
key={icon.toString()}
size="sm"
icon={icon}
- onPointerEnter={action(e => (this.subEndLink = (pinLayout ? 'Layout' : '') + (pinLayout && pinContent ? ' &' : '') + (pinContent ? ' Content' : '')))}
- onPointerLeave={action(e => (this.subEndLink = ''))}
+ onPointerEnter={action(() => {
+ this.subEndLink = (pinLayout ? 'Layout' : '') + (pinLayout && pinContent ? ' &' : '') + (pinContent ? ' Content' : '');
+ })}
+ onPointerLeave={action(() => {
+ this.subEndLink = '';
+ })}
onClick={e => {
this.view0 &&
DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.view0.Document, true, this.view0, {
@@ -155,7 +169,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
{linkBtn(false, true, 'address-card')}
{linkBtn(true, true, 'id-card')}
</div>
- <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
+ <DocumentLinksButton View={this.view0} AlwaysOn InMenu StartLink={false} />
</div>
);
}
@@ -175,21 +189,22 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
key={icon.toString()}
size="sm"
icon={icon}
- onPointerEnter={action(
- e =>
- (this.subPin =
- (pinLayoutView ? 'Layout' : '') +
- (pinLayoutView && pinContentView ? ' &' : '') +
- (pinContentView ? ' Content View' : '') +
- (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : ''))
- )}
- onPointerLeave={action(e => (this.subPin = ''))}
+ onPointerEnter={action(() => {
+ this.subPin =
+ (pinLayoutView ? 'Layout' : '') +
+ (pinLayoutView && pinContentView ? ' &' : '') +
+ (pinContentView ? ' Content View' : '') +
+ (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : '');
+ })}
+ onPointerLeave={action(() => {
+ this.subPin = '';
+ })}
onClick={e => {
const docs = this._props
.views()
.filter(v => v)
.map(dv => dv!.Document);
- TabDocView.PinDoc(docs, {
+ DocumentView.PinDoc(docs, {
pinAudioPlay: true,
pinDocLayout: pinLayoutView,
pinData: { dataview: pinContentView },
@@ -204,7 +219,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
);
};
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 ${DocumentView.Selected().length > 1 ? 'multiple documents' : ''} to Trail`}</div>}>
<div
className="documentButtonBar-icon documentButtonBar-pin"
onClick={e => {
@@ -212,7 +227,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
.views()
.filter(v => v)
.map(dv => dv!.Document);
- TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: { dataview: e.altKey }, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
+ DocumentView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: { dataview: e.altKey }, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
e.stopPropagation();
}}>
<div className="documentButtonBar-pinTypes">
@@ -230,8 +245,8 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
get shareButton() {
const targetDoc = this.view0?.Document;
return !targetDoc ? null : (
- <Tooltip title={<div className="dash-tooltip">{'Open Sharing Manager'}</div>}>
- <div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={e => SharingManager.Instance.open(this.view0, targetDoc)}>
+ <Tooltip title={<div className="dash-tooltip">Open Sharing Manager</div>}>
+ <div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={() => SharingManager.Instance.open(this.view0, targetDoc)}>
<FontAwesomeIcon className="documentdecorations-icon" icon="users" />
</div>
</Tooltip>
@@ -242,8 +257,8 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
get menuButton() {
const targetDoc = this.view0?.Document;
return !targetDoc ? null : (
- <Tooltip title={<div className="dash-tooltip">{`Open Context Menu`}</div>}>
- <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => this.openContextMenu(e))}>
+ <Tooltip title={<div className="dash-tooltip">Open Context Menu</div>}>
+ <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, clickEv => this.openContextMenu(clickEv))}>
<FontAwesomeIcon className="documentdecorations-icon" icon="bars" />
</div>
</Tooltip>
@@ -258,8 +273,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
<div
className="documentButtonBar-icon"
style={{ color: 'white' }}
- onClick={e => {
- console.log('hi: ', CalendarManager.Instance);
+ onClick={() => {
CalendarManager.Instance.open(this.view0, targetDoc);
}}>
<FontAwesomeIcon className="documentdecorations-icon" icon={faCalendarDays as IconLookup} />
@@ -280,7 +294,18 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
style={{ backgroundColor: this._isRecording ? Colors.ERROR_RED : Colors.DARK_GRAY, color: Colors.WHITE }}
onPointerDown={action((e: React.PointerEvent) => {
this._isRecording = true;
- this._props.views().map(view => view && DocumentViewInternal.recordAudioAnnotation(view.dataDoc, view.LayoutFieldKey, stopFunc => (this._stopFunc = stopFunc), emptyFunction));
+ this._props.views().map(
+ view =>
+ view &&
+ DictationManager.recordAudioAnnotation(
+ view.dataDoc,
+ view.LayoutFieldKey,
+ stopFunc => {
+ this._stopFunc = stopFunc;
+ },
+ emptyFunction
+ )
+ );
const b = UndoManager.StartBatch('Recording');
setupMoveUpEvents(
this,
@@ -308,10 +333,10 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
if (this._dragRef.current) {
const dragDocView = this.view0!;
const dragData = new DragManager.DocumentDragData([dragDocView.Document]);
- const [left, top] = dragDocView.screenToContentsTransform().inverse().transformPoint(0, 0);
+ const origin = dragDocView.screenToContentsTransform().inverse().transformPoint(0, 0);
dragData.defaultDropAction = dropActionType.embed;
dragData.canEmbed = true;
- DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, { hideSource: false });
+ DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, origin[0], origin[1], { hideSource: false });
return true;
}
return false;
@@ -334,8 +359,19 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
@computed
get templateButton() {
return !this.view0 ? null : (
- <Tooltip title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embedding</div>} open={this._tooltipOpen} onClose={action(() => (this._tooltipOpen = false))} placement="bottom">
- <div className="documentButtonBar-linkFlyout" ref={this._dragRef} onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}>
+ <Tooltip
+ title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embedding</div>}
+ open={this._tooltipOpen}
+ onClose={action(() => {
+ this._tooltipOpen = false;
+ })}
+ placement="bottom">
+ <div
+ className="documentButtonBar-linkFlyout"
+ ref={this._dragRef}
+ onPointerEnter={action(() => {
+ !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true);
+ })}>
<Popup icon={<FaEdit />} popup={this.templateMenu} popupContainsPt={returnTrue} />
</div>
</Tooltip>
@@ -343,7 +379,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
}
openContextMenu = (e: PointerEvent) => {
- let child = SelectionManager.Views[0].ContentDiv!.children[0];
+ let child = DocumentView.Selected()[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;
@@ -363,17 +399,17 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
@observable _captureEndLinkLayout = false;
@action
- captureEndLinkLayout = (e: React.PointerEvent) => {
+ captureEndLinkLayout = () => {
this._captureEndLinkLayout = !this._captureEndLinkLayout;
};
@observable _captureEndLinkContent = false;
@action
- captureEndLinkContent = (e: React.PointerEvent) => {
+ captureEndLinkContent = () => {
this._captureEndLinkContent = !this._captureEndLinkContent;
};
@action
- captureEndLinkState = (e: React.PointerEvent) => {
+ captureEndLinkState = () => {
this._captureEndLinkContent = this._captureEndLinkLayout = !this._captureEndLinkLayout;
};
@@ -400,16 +436,11 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
return (
<div className="documentButtonBar">
<div className="documentButtonBar-button">
- <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} ShowCount={true} />
+ <DocumentLinksButton View={this.view0} AlwaysOn InMenu ShowCount />
</div>
{this._showLinkPopup ? (
<div style={{ position: 'absolute', zIndex: 1000 }}>
- <LinkPopup
- key="popup"
- linkCreated={link => (link.link_displayLine = !IsFollowLinkScript(this._props.views().lastElement()?.Document.onClick))}
- linkCreateAnchor={() => this._props.views().lastElement()?.ComponentView?.getAnchor?.(true)}
- linkFrom={() => this._props.views().lastElement()?.Document}
- />
+ <LinkPopup key="popup" linkCreated={emptyFunction} linkCreateAnchor={() => this._props.views().lastElement()?.ComponentView?.getAnchor?.(true)} linkFrom={() => this._props.views().lastElement()?.Document} />
</div>
) : (
<div className="documentButtonBar-button">{this.linkButton}</div>
@@ -417,11 +448,11 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
{DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== doc ? <div className="documentButtonBar-button">{this.endLinkButton} </div> : null}
<div className="documentButtonBar-button">{this.templateButton}</div>
- {!SelectionManager.Views?.some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>}
+ {!DocumentView.Selected().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">{this.shareButton}</div>}
<div className="documentButtonBar-button">{this.menuButton}</div>
</div>
);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2a44a9739..4262c2d57 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -5,19 +5,19 @@ import { action, computed, makeObservable, observable, runInAction } from 'mobx'
import { observer } from 'mobx-react';
import * as React from 'react';
import { FaUndo } from 'react-icons/fa';
-import { Utils, emptyFunction, lightOrDark, numberValue, returnFalse, setupMoveUpEvents } from '../../Utils';
+import { lightOrDark, returnFalse, setupMoveUpEvents } from '../../ClientUtils';
+import { Utils, emptyFunction, numberValue } from '../../Utils';
import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Field, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, DocData } from '../../fields/DocSymbols';
+import { Id } from '../../fields/FieldSymbols';
import { InkField } from '../../fields/InkField';
import { ScriptField } from '../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
import { GetEffectiveAcl } from '../../fields/util';
import { DocumentType } from '../documents/DocumentTypes';
import { Docs } from '../documents/Documents';
-import { DocumentManager } from '../util/DocumentManager';
import { DragManager } from '../util/DragManager';
-import { SelectionManager } from '../util/SelectionManager';
import { SettingsManager } from '../util/SettingsManager';
import { SnappingManager } from '../util/SnappingManager';
import { UndoManager } from '../util/UndoManager';
@@ -30,11 +30,11 @@ import { ObservableReactComponent } from './ObservableReactComponent';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { CollectionFreeFormView } from './collections/collectionFreeForm';
import { Colors } from './global/globalEnums';
-import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
+import { CollectionFreeFormDocumentView } from './nodes/CollectionFreeFormDocumentView';
+import { DocumentView } from './nodes/DocumentView';
import { ImageBox } from './nodes/ImageBox';
-import { KeyValueBox } from './nodes/KeyValueBox';
+import { OpenWhereMod } from './nodes/OpenWhere';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import { identity } from 'lodash';
interface DocumentDecorationsProps {
PanelWidth: number;
@@ -44,6 +44,7 @@ interface DocumentDecorationsProps {
}
@observer
export class DocumentDecorations extends ObservableReactComponent<DocumentDecorationsProps> {
+ // eslint-disable-next-line no-use-before-define
static Instance: DocumentDecorations;
private _resizeHdlId = '';
private _keyinput = React.createRef<HTMLInputElement>();
@@ -77,14 +78,14 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
let inputting = false;
if (this._titleControlString.startsWith('$')) {
const titleFieldKey = this._titleControlString.substring(1);
- if (SelectionManager.Views[0]?.Document[titleFieldKey] !== this._accumulatedTitle) {
+ if (DocumentView.Selected()[0]?.Document[titleFieldKey] !== this._accumulatedTitle) {
inputting = true;
}
}
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()?.screenToViewTransform().Rotate));
+ NumCast(DocumentView.Selected().lastElement()?.screenToViewTransform().Rotate));
(this._showNothing = !inputting && !DocumentButtonBar.Instance?._tooltipOpen && !(this.Bounds.x !== Number.MAX_VALUE && //
(this.Bounds.x > center.x+x || this.Bounds.r < center.x+x ||
this.Bounds.y > center.y+y || this.Bounds.b < center.y+y )));
@@ -106,7 +107,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
@computed get Bounds() {
return (SnappingManager.IsLinkFollowing || SnappingManager.ExploreMode) ?
{ x: 0, y: 0, r: 0, b: 0 }
- : SelectionManager.Views
+ : DocumentView.Selected()
.filter(dv => dv._props.renderDepth > 0)
.map(dv => dv.getBounds)
.reduce((bounds, rect) => !rect ? bounds
@@ -123,17 +124,19 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
this._titleControlString = this._accumulatedTitle;
} else if (this._titleControlString.startsWith('$')) {
if (this._accumulatedTitle.startsWith('-->#')) {
- SelectionManager.Docs.forEach(doc => (doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`)));
+ DocumentView.SelectedDocs().forEach(doc => {
+ doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`);
+ });
}
const titleFieldKey = this._titleControlString.substring(1);
UndoManager.RunInBatch(
() =>
titleFieldKey &&
- SelectionManager.Views.forEach(d => {
+ DocumentView.Selected().forEach(dv => {
if (titleFieldKey === 'title') {
- d.dataDoc.title_custom = !this._accumulatedTitle.startsWith('-');
+ dv.dataDoc.title_custom = !this._accumulatedTitle.startsWith('-');
}
- KeyValueBox.SetField(d.Document, titleFieldKey, this._accumulatedTitle);
+ Doc.SetField(dv.Document, titleFieldKey, this._accumulatedTitle);
}),
'edit title'
);
@@ -148,23 +151,23 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
};
onContainerDown = (e: React.PointerEvent) => {
- const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views[0].Document);
- if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) {
- setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), emptyFunction, emptyFunction);
+ const effectiveLayoutAcl = GetEffectiveAcl(DocumentView.Selected()[0].Document);
+ if (effectiveLayoutAcl === AclAdmin || effectiveLayoutAcl === AclEdit || effectiveLayoutAcl === AclAugment) {
+ setupMoveUpEvents(this, e, moveEv => this.onBackgroundMove(true, moveEv), emptyFunction, emptyFunction);
e.stopPropagation();
}
};
onTitleDown = (e: React.PointerEvent) => {
- const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views[0].Document);
- if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) {
+ const effectiveLayoutAcl = GetEffectiveAcl(DocumentView.SelectedDocs()[0]);
+ if (effectiveLayoutAcl === AclAdmin || effectiveLayoutAcl === AclEdit || effectiveLayoutAcl === AclAugment) {
setupMoveUpEvents(
this,
e,
- e => this.onBackgroundMove(true, e),
+ moveEv => this.onBackgroundMove(true, moveEv),
emptyFunction,
- action(e => {
- const selected = SelectionManager.Views.length === 1 ? SelectionManager.Docs[0] : undefined;
+ action(() => {
+ const selected = DocumentView.SelectedDocs().length === 1 ? DocumentView.SelectedDocs()[0] : undefined;
!this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('$') ? (selected && Field.toKeyValueString(selected, this._titleControlString.substring(1))) || '-unset-' : this._titleControlString);
this._editingTitle = true;
this._keyinput.current && setTimeout(this._keyinput.current.focus);
@@ -175,24 +178,21 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
};
onBackgroundDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, e => this.onBackgroundMove(false, e), emptyFunction, emptyFunction);
+ setupMoveUpEvents(this, e, moveEv => this.onBackgroundMove(false, moveEv), emptyFunction, emptyFunction);
e.stopPropagation();
};
@action
onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => {
- const dragDocView = SelectionManager.Views[0];
+ const dragDocView = DocumentView.Selected()[0];
const effectiveLayoutAcl = GetEffectiveAcl(dragDocView.Document);
- if (effectiveLayoutAcl != AclAdmin && effectiveLayoutAcl != AclEdit && effectiveLayoutAcl != AclAugment) {
+ if (effectiveLayoutAcl !== AclAdmin && effectiveLayoutAcl !== AclEdit && effectiveLayoutAcl !== AclAugment) {
return false;
}
const containers = new Set<Doc | undefined>();
- SelectionManager.Views.forEach(v => containers.add(DocCast(v.Document.embedContainer)));
+ DocumentView.Selected().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.Document),
- dragDocView._props.dropAction
- );
+ const dragData = new DragManager.DocumentDragData(DocumentView.SelectedDocs(), dragDocView._props.dropAction);
dragData.offset = dragDocView.screenToContentsTransform().transformDirection(e.x - left, e.y - top);
dragData.moveDocument = dragDocView._props.moveDocument;
dragData.removeDocument = dragDocView._props.removeDocument;
@@ -200,12 +200,14 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
dragData.canEmbed = dragTitle;
this._hidden = true;
DragManager.StartDocumentDrag(
- SelectionManager.Views.map(dv => dv.ContentDiv!),
+ DocumentView.Selected().map(dv => dv.ContentDiv!),
dragData,
e.x,
e.y,
{
- dragComplete: action(e => (this._hidden = false)),
+ dragComplete: action(() => {
+ this._hidden = false;
+ }),
hideSource: true,
}
);
@@ -215,22 +217,23 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
_deleteAfterIconify = false;
_iconifyBatch: UndoManager.Batch | undefined;
onCloseClick = (forceDeleteOrIconify: boolean | undefined) => {
- const views = SelectionManager.Views.filter(v => v && v._props.renderDepth > 0);
+ const views = DocumentView.Selected().filter(v => v && v._props.renderDepth > 0);
if (forceDeleteOrIconify === false && this._iconifyBatch) return;
- this._deleteAfterIconify = forceDeleteOrIconify || this._iconifyBatch ? true : false;
- var iconifyingCount = views.length;
+ this._deleteAfterIconify = !!(forceDeleteOrIconify || this._iconifyBatch);
+ let iconifyingCount = views.length;
const finished = action((force?: boolean) => {
if ((force || --iconifyingCount === 0) && this._iconifyBatch) {
if (this._deleteAfterIconify) {
views.forEach(iconView => {
- Doc.setNativeView(iconView.Document);
- if (iconView.Document.activeFrame) {
- iconView.Document.opacity = 0; // bcz: hacky ... allows inkMasks and other documents to be "turned off" without removing them from the animated collection which allows them to function properly in a presenation.
+ const iconViewDoc = iconView.Document;
+ Doc.setNativeView(iconViewDoc);
+ if (iconViewDoc.activeFrame) {
+ iconViewDoc.opacity = 0; // bcz: hacky ... allows inkMasks and other documents to be "turned off" without removing them from the animated collection which allows them to function properly in a presenation.
} else {
iconView._props.removeDocument?.(iconView.Document);
}
});
- views.forEach(SelectionManager.DeselectView);
+ views.forEach(DocumentView.DeselectView);
}
this._iconifyBatch?.end();
this._iconifyBatch = undefined;
@@ -240,6 +243,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
(document.activeElement as any).blur?.();
this._iconifyBatch = UndoManager.StartBatch(forceDeleteOrIconify ? 'delete selected docs' : 'iconifying');
} else {
+ // eslint-disable-next-line no-param-reassign
forceDeleteOrIconify = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes
}
@@ -248,70 +252,66 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
};
onMaximizeDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, () => DragManager.StartWindowDrag?.(e, [SelectionManager.Views.lastElement().Document]) ?? false, emptyFunction, this.onMaximizeClick, false, false);
+ setupMoveUpEvents(this, e, () => DragManager.StartWindowDrag?.(e, [DocumentView.SelectedDocs().lastElement()]) ?? false, emptyFunction, this.onMaximizeClick, false, false);
e.stopPropagation();
};
onMaximizeClick = (e: any): void => {
- const selectedDocs = SelectionManager.Views;
+ const selectedDocs = DocumentView.SelectedDocs();
if (selectedDocs.length) {
if (e.ctrlKey) {
// open an embedding in a new tab with Ctrl Key
- CollectionDockingView.AddSplit(Doc.BestEmbedding(selectedDocs[0].Document), OpenWhereMod.right);
+ CollectionDockingView.AddSplit(Doc.BestEmbedding(selectedDocs[0]), OpenWhereMod.right);
} else if (e.shiftKey) {
// open centered in a new workspace with Shift Key
- const embedding = Doc.MakeEmbedding(selectedDocs[0].Document);
+ const embedding = Doc.MakeEmbedding(selectedDocs[0]);
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].Document, OpenWhereMod.right);
+ CollectionDockingView.ToggleSplit(selectedDocs[0], OpenWhereMod.right);
} else {
- var openDoc = selectedDocs[0].Document;
+ let openDoc = selectedDocs[0];
if (openDoc.layout_fieldKey === 'layout_icon') {
openDoc = Doc.GetEmbeddings(openDoc).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(openDoc);
Doc.deiconifyView(openDoc);
}
- LightboxView.Instance.SetLightboxDoc(
- openDoc,
- undefined,
- selectedDocs.slice(1).map(view => view.Document)
- );
+ LightboxView.Instance.SetLightboxDoc(openDoc, undefined, selectedDocs.slice(1));
}
}
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
};
onIconifyClick = (): void => {
- SelectionManager.Views.forEach(dv => dv?.iconify());
- SelectionManager.DeselectAll();
+ DocumentView.Selected().forEach(dv => dv?.iconify());
+ DocumentView.DeselectAll();
};
- onSelectContainerDocClick = () => SelectionManager.Views?.[0]?.containerViewPath?.().lastElement()?.select(false);
+ onSelectContainerDocClick = () => DocumentView.Selected()?.[0]?.containerViewPath?.().lastElement()?.select(false);
/**
* sets up events when user clicks on the border radius editor
*/
@action
onRadiusDown = (e: React.PointerEvent): void => {
- SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement());
+ SnappingManager.SetIsResizing(DocumentView.SelectedDocs().lastElement()?.[Id]);
this._isRounding = true;
this._resizeUndo = UndoManager.StartBatch('DocDecs set radius');
setupMoveUpEvents(
this,
e,
- e => {
+ moveEv => {
const [x, y] = [this.Bounds.x + 3, this.Bounds.y + 3];
const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
- const dist = e.clientX < x && e.clientY < y ? 0 : Math.sqrt((e.clientX - x) * (e.clientX - x) + (e.clientY - y) * (e.clientY - y));
- SelectionManager.Docs.map(doc => {
+ const dist = moveEv.clientX < x && moveEv.clientY < y ? 0 : Math.sqrt((moveEv.clientX - x) * (moveEv.clientX - x) + (moveEv.clientY - y) * (moveEv.clientY - y));
+ DocumentView.SelectedDocs().forEach(doc => {
const docMax = Math.min(NumCast(doc.width) / 2, NumCast(doc.height) / 2);
const radius = Math.min(1, dist / maxDist) * docMax; // set radius based on ratio of drag distance to half diagonal distance of bounding box
doc._layout_borderRounding = `${radius}px`;
});
return false;
},
- action(e => {
+ action(() => {
SnappingManager.SetIsResizing(undefined);
this._isRounding = false;
this._resizeUndo?.end();
@@ -329,30 +329,31 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
e,
returnFalse, // don't care about move or up event,
emptyFunction, // just care about whether we get a click event
- e => UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => Doc.toggleLockedPosition(doc)), 'toggleBackground')
+ () => UndoManager.RunInBatch(() => DocumentView.Selected().forEach(dv => Doc.toggleLockedPosition(dv.Document)), 'toggleBackground')
);
e.stopPropagation();
};
setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => {
+ const selDoc = seldocview.Document;
const newloccentern = seldocview.screenToContentsTransform().transformPoint(rotCenter[0], rotCenter[1]);
const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2];
const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.Document._rotation) / 180) * Math.PI);
- seldocview.Document._rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
- seldocview.Document._rotation_centerY = final.y / NumCast(seldocview.layoutDoc._height);
+ selDoc._rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
+ selDoc._rotation_centerY = final.y / NumCast(seldocview.layoutDoc._height);
};
@action
onRotateCenterDown = (e: React.PointerEvent): void => {
this._isRotating = true;
- const seldocview = SelectionManager.Views[0];
+ const seldocview = DocumentView.Selected()[0];
setupMoveUpEvents(
this,
e,
- (e: PointerEvent, down: number[], delta: number[]) => // return false to keep getting events
+ (moveEv: PointerEvent, down: number[], delta: number[]) => // return false to keep getting events
this.setRotateCenter(seldocview, [this.rotCenter[0] + delta[0], this.rotCenter[1] + delta[1]]) as any as boolean,
- action(e => (this._isRotating = false)), // upEvent
- action(e => (seldocview.Document._rotation_centerX = seldocview.Document._rotation_centerY = 0)),
+ action(() => { this._isRotating = false; }), // upEvent
+ action(() => { seldocview.Document._rotation_centerX = seldocview.Document._rotation_centerY = 0; }),
true
); // prettier-ignore
e.stopPropagation();
@@ -363,11 +364,11 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
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 = DocumentView.Selected().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 seldocview = DocumentView.Selected()[0];
+ DocumentView.Selected().forEach(dv => {
const accumRot = (NumCast(dv.Document._rotation) / 180) * Math.PI;
const localRotCtr = dv.screenToViewTransform().transformPoint(rcScreen.X, rcScreen.Y);
const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.Document.width) / 2, localRotCtr[1] - NumCast(dv.Document.height) / 2];
@@ -376,7 +377,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
infos.set(dv.Document, { unrotatedDocPos, startRotCtr, accumRot });
});
const infoRot = (angle: number, isAbs = false) => {
- SelectionManager.Views.forEach(
+ DocumentView.Selected().forEach(
action(dv => {
const { unrotatedDocPos, startRotCtr, accumRot } = infos.get(dv.Document)!;
const endRotCtr = Utils.rotPt(startRotCtr.x, startRotCtr.y, isAbs ? angle : accumRot + angle);
@@ -390,9 +391,9 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
setupMoveUpEvents(
this,
e,
- (e: PointerEvent, down: number[], delta: number[]) => {
- const previousPoint = { X: e.clientX, Y: e.clientY };
- const movedPoint = { X: e.clientX - delta[0], Y: e.clientY - delta[1] };
+ (moveEv: PointerEvent, down: number[], delta: number[]) => {
+ const previousPoint = { X: moveEv.clientX, Y: moveEv.clientY };
+ const movedPoint = { X: moveEv.clientX - delta[0], Y: moveEv.clientY - delta[1] };
const deltaAng = InkStrokeProperties.angleChange(movedPoint, previousPoint, rcScreen);
if (selectedInk.length) {
deltaAng && InkStrokeProperties.Instance.rotateInk(selectedInk, deltaAng, rcScreen);
@@ -418,13 +419,15 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
this._isRotating = false;
rotateUndo?.end();
}), // upEvent
- action(e => (this._showRotCenter = !this._showRotCenter)) // clickEvent
+ action(() => {
+ this._showRotCenter = !this._showRotCenter;
+ }) // clickEvent
);
};
@action
onPointerDown = (e: React.PointerEvent): void => {
- SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement()); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
+ SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
e.stopPropagation();
const id = (this._resizeHdlId = e.currentTarget.className);
@@ -436,7 +439,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
};
this._resizeUndo = UndoManager.StartBatch('drag resizing');
this._snapPt = { x: e.pageX, y: e.pageY };
- SelectionManager.Views.forEach(docView => docView.CollectionFreeFormView?.dragStarting(false, false));
+ DocumentView.Selected().forEach(docView => CollectionFreeFormView.from(docView)?.dragStarting(false, false));
};
projectDragToAspect = (e: PointerEvent, docView: DocumentView, fixedAspect: number) => {
@@ -453,12 +456,12 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
const tl = docView.screenToContentsTransform().inverse().transformPoint(0, 0);
return project([e.clientX + this._offset.x, e.clientY + this._offset.y], tl, [tl[0] + fixedAspect, tl[1] + 1]);
};
- onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
- const first = SelectionManager.Views[0];
+ onPointerMove = (e: PointerEvent): boolean => {
+ const first = DocumentView.Selected()[0];
const effectiveAcl = GetEffectiveAcl(first.Document);
- if (!(effectiveAcl == AclAdmin || effectiveAcl == AclEdit || effectiveAcl == AclAugment)) return false;
+ if (!(effectiveAcl === AclAdmin || effectiveAcl === AclEdit || effectiveAcl === AclAugment)) return false;
if (!first) return false;
- var fixedAspect = Doc.NativeAspect(first.layoutDoc);
+ const fixedAspect = Doc.NativeAspect(first.layoutDoc);
const dragHdl = this._resizeHdlId.split(' ')[0].replace('documentDecorations-', '').replace('Resizer', '');
const thisPt = // do snapping of drag point
@@ -471,12 +474,12 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
!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.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 =>
+ e.ctrlKey && (DocumentView.Selected().forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions()));
+ const hasFixedAspect = DocumentView.Selected().map(dv => dv.Document).some(this.hasFixedAspect);
+ const scaleAspect = {x:scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y};
+ DocumentView.Selected().forEach(docView =>
this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey:e.ctrlKey })); // prettier-ignore
- await new Promise<any>(res => setTimeout(() => res(this._interactionLock = undefined)));
+ await new Promise<any>(res => { setTimeout(() => { res(this._interactionLock = undefined)})});
}); // prettier-ignore
return false;
@@ -513,7 +516,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
const doc = docView.Document;
if (doc.isGroup) {
DocListCast(doc.data)
- .map(member => DocumentManager.Instance.getDocumentView(member, docView)!)
+ .map(member => DocumentView.getDocumentView(member, docView)!)
.forEach(member => this.resizeView(member, refPt, scale, opts));
doc.xPadding = NumCast(doc.xPadding) * scale.x;
doc.yPadding = NumCast(doc.yPadding) * scale.y;
@@ -553,10 +556,11 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
doc._layout_modificationDate = new DateField();
if (scale.y !== 1) {
- docView.layoutDoc._layout_autoHeight = undefined;
+ const docLayout = docView.layoutDoc;
+ docLayout._layout_autoHeight = undefined;
if (docView.layoutDoc._layout_autoHeight) {
// if autoHeight is still on because of a prototype
- docView.layoutDoc._layout_autoHeight = false; // then don't inherit, but explicitly set it to false
+ docLayout._layout_autoHeight = false; // then don't inherit, but explicitly set it to false
}
}
}
@@ -586,15 +590,17 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
};
@action
- onPointerUp = (e: PointerEvent): void => {
+ onPointerUp = (): void => {
SnappingManager.SetIsResizing(undefined);
SnappingManager.clearSnapLines();
this._resizeHdlId = '';
this._resizeUndo?.end();
// detect layout_autoHeight gesture and apply
- SelectionManager.Views.forEach(view => NumCast(view.Document._height) < 20 && (view.layoutDoc._layout_autoHeight = true));
- //need to change points for resize, or else rotation/control points will fail.
+ DocumentView.Selected().forEach(view => {
+ NumCast(view.Document._height) < 20 && (view.layoutDoc._layout_autoHeight = true);
+ });
+ // need to change points for resize, or else rotation/control points will fail.
this._inkDragDocs
.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
.forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
@@ -610,18 +616,18 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
@computed
get selectionTitle(): string {
- if (SelectionManager.Views.length === 1) {
- const selected = SelectionManager.Views[0];
+ if (DocumentView.Selected().length === 1) {
+ const selected = DocumentView.Selected()[0];
if (this._titleControlString.startsWith('$')) {
- return Field.toJavascriptString(selected.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
+ return Field.toJavascriptString(selected.Document[this._titleControlString.substring(1)] as FieldType) || '-unset-';
}
return this._accumulatedTitle;
}
- return SelectionManager.Views.length > 1 ? '-multiple-' : '-unset-';
+ return DocumentView.Selected().length > 1 ? '-multiple-' : '-unset-';
}
@computed get rotCenter() {
- const lastView = SelectionManager.Views.lastElement();
+ const lastView = DocumentView.Selected().lastElement();
if (lastView) {
const invXf = lastView.screenToContentsTransform().inverse();
const seldoc = lastView.layoutDoc;
@@ -633,7 +639,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
render() {
const { b, r, x, y } = this.Bounds;
- const seldocview = SelectionManager.Views.lastElement();
+ const seldocview = DocumentView.Selected().lastElement();
if (SnappingManager.IsDragging || r - x < 1 || x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(r) || isNaN(b) || isNaN(x) || isNaN(y)) {
setTimeout(
action(() => {
@@ -648,7 +654,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
const acl = GetEffectiveAcl(!this._showLayoutAcl ? Doc.GetProto(seldocview.Document) : seldocview.Document);
const docShareMode = HierarchyMapping.get(acl)!.name;
const shareMode = StrCast(docShareMode);
- var shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image;
+ const shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image;
// hide the decorations if the parent chooses to hide it or if the document itself hides it
const hideDecorations = SnappingManager.IsResizing || seldocview._props.hideDecorations || seldocview.Document.layout_hideDecorations;
@@ -662,7 +668,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
hideDecorations ||
seldocview._props.hideOpenButton ||
seldocview.Document.layout_hideOpenButton ||
- SelectionManager.Views.some(docView => docView.Document._dragOnlyWithinContainer || docView.Document.isGroup || docView.Document.layout_hideOpenButton) ||
+ DocumentView.Selected().some(docView => docView.Document._dragOnlyWithinContainer || docView.Document.isGroup || docView.Document.layout_hideOpenButton) ||
this._isRounding ||
this._isRotating;
const hideDeleteButton =
@@ -672,13 +678,13 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
this._isRotating ||
seldocview._props.hideDeleteButton ||
seldocview.Document.hideDeleteButton ||
- SelectionManager.Views.some(docView => {
+ DocumentView.Selected().some(docView => {
const collectionAcl = docView.containerViewPath?.()?.lastElement() ? GetEffectiveAcl(docView.containerViewPath?.().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">
- <div className={`documentDecorations-${key}Button`} onContextMenu={e => e.preventDefault()} onPointerDown={pointerDown ?? (e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => click!(e)))}>
+ <div className={`documentDecorations-${key}Button`} onContextMenu={e => e.preventDefault()} onPointerDown={pointerDown ?? (e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, clickEv => click!(clickEv)))}>
<FontAwesomeIcon icon={icon as any} />
</div>
</Tooltip>
@@ -686,8 +692,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
const bounds = this.ClippedBounds;
const useLock = bounds.r - bounds.x > 135;
- const useRotation = !hideResizers && seldocview.Document.type !== DocumentType.EQUATION && seldocview.CollectionFreeFormDocumentView; // when do we want an object to not rotate?
- const rotation = SelectionManager.Views.length == 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0;
+ const useRotation = !hideResizers && seldocview.Document.type !== DocumentType.EQUATION && CollectionFreeFormDocumentView.from(seldocview); // when do we want an object to not rotate?
+ const rotation = DocumentView.Selected().length === 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0;
// Radius constants
const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView;
@@ -727,11 +733,13 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
name="dynbox"
autoComplete="on"
value={hideTitle ? '' : this._accumulatedTitle}
- onBlur={action((e: React.FocusEvent) => {
+ onBlur={action(() => {
this._editingTitle = false;
this.titleBlur();
})}
- onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
+ onChange={action(e => {
+ !hideTitle && (this._accumulatedTitle = e.target.value);
+ })}
onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
onPointerDown={e => e.stopPropagation()}
/>
@@ -755,7 +763,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
);
const centery = hideTitle ? 0 : this._titleHeight;
const transformOrigin = `${50}% calc(50% + ${centery / 2}px)`;
- const freeformDoc = SelectionManager.Views.some(v => v.CollectionFreeFormDocumentView);
+ const freeformDoc = DocumentView.Selected().some(v => CollectionFreeFormDocumentView.from(v));
return (
<div className="documentDecorations" style={{ display: this._showNothing && !freeformDoc ? 'none' : undefined }}>
<div
@@ -768,7 +776,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
transformOrigin,
background: SnappingManager.ShiftKey ? undefined : 'yellow',
pointerEvents: SnappingManager.ShiftKey || SnappingManager.IsResizing ? 'none' : 'all',
- display: SelectionManager.Views.length <= 1 || hideDecorations ? 'none' : undefined,
+ display: DocumentView.Selected().length <= 1 || hideDecorations ? 'none' : undefined,
transform: `rotate(${rotation}deg)`,
}}
onPointerDown={this.onBackgroundDown}
@@ -789,8 +797,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
display: hideDeleteButton && hideTitle && hideOpenButton ? 'none' : undefined,
}}
onPointerDown={this.onContainerDown}>
- {hideDeleteButton ? null : topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')}
- {hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')}
+ {hideDeleteButton ? null : topBtn('close', 'times', undefined, () => this.onCloseClick(true), 'Close')}
+ {hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, () => this.onCloseClick(undefined), 'Minimize')}
{titleArea}
{hideOpenButton ? <div /> : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection)')}
</div>
@@ -825,7 +833,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
style={{
transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
}}>
- <DocumentButtonBar views={() => SelectionManager.Views} />
+ <DocumentButtonBar views={() => DocumentView.Selected()} />
</div>
)}
</div>
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 85e893e19..684b948af 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { action, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -70,7 +72,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
constructor(props: EditableProps) {
super(props);
makeObservable(this);
- this._editing = this._props.editing ? true : false;
+ this._editing = !!this._props.editing;
}
componentDidMount(): void {
@@ -166,7 +168,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
this._props.menuCallback(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y);
break;
}
-
+ // eslint-disable-next-line no-fallthrough
default:
if (this._props.textCallback?.(e.key)) {
e.stopPropagation();
@@ -186,7 +188,6 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
this._editing = true;
this._props.isEditingCallback?.(true);
}
- // e.stopPropagation();
}
};
@@ -223,6 +224,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
renderEditor() {
return this._props.autosuggestProps ? (
<Autosuggest
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props.autosuggestProps.autosuggestProps}
inputProps={{
className: 'editableView-input',
@@ -241,12 +243,13 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
) : this._props.oneLine !== false && this._props.GetValue()?.toString().indexOf('\n') === -1 ? (
<input
className="editableView-input"
- ref={r => (this._inputref = r)}
+ ref={r => { this._inputref = r; }} // prettier-ignore
style={{ display: this._props.display, overflow: 'auto', fontSize: this._props.fontSize, minWidth: 20, background: this._props.background }}
placeholder={this._props.placeholder}
onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)}
defaultValue={this._props.GetValue()}
- autoFocus={true}
+ // eslint-disable-next-line jsx-a11y/no-autofocus
+ autoFocus
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onPointerDown={this.stopPropagation}
@@ -256,12 +259,13 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
) : (
<textarea
className="editableView-input"
- ref={r => (this._inputref = r)}
+ ref={r => { this._inputref = r; }} // prettier-ignore
style={{ display: this._props.display, overflow: 'auto', fontSize: this._props.fontSize, minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20, background: this._props.background }}
placeholder={this._props.placeholder}
onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)}
defaultValue={this._props.GetValue()}
- autoFocus={true}
+ // eslint-disable-next-line jsx-a11y/no-autofocus
+ autoFocus
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onPointerDown={this.stopPropagation}
@@ -304,7 +308,10 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
fontStyle: this._props.fontStyle,
fontSize: this._props.fontSize,
}}>
- {this._props.fieldContents ? <FieldView {...this._props.fieldContents} /> : this.props.contents ? this._props.contents?.valueOf() : ''}
+ {
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ this._props.fieldContents ? <FieldView {...this._props.fieldContents} /> : this.props.contents ? this._props.contents?.valueOf() : ''
+ }
</span>
</div>
);
diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx
index 6a5c2cb4c..0ea0ebd83 100644
--- a/src/client/views/FieldsDropdown.tsx
+++ b/src/client/views/FieldsDropdown.tsx
@@ -13,7 +13,7 @@ import Select from 'react-select';
import { Doc } from '../../fields/Doc';
import { DocOptions, FInfo } from '../documents/Documents';
import { SearchUtil } from '../util/SearchUtil';
-import { SettingsManager } from '../util/SettingsManager';
+import { SnappingManager } from '../util/SnappingManager';
import './FilterPanel.scss';
import { ObservableReactComponent } from './ObservableReactComponent';
@@ -64,40 +64,40 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
return (
<Select
styles={{
- control: (baseStyles, state) => ({
+ control: (baseStyles /* , state */) => ({
...baseStyles,
minHeight: '5px',
maxHeight: '30px',
- color: SettingsManager.userColor,
- backgroundColor: SettingsManager.userBackgroundColor,
+ color: SnappingManager.userColor,
+ backgroundColor: SnappingManager.userBackgroundColor,
padding: 0,
margin: 0,
}),
- singleValue: (baseStyles, state) => ({
+ singleValue: (baseStyles /* , state */) => ({
...baseStyles,
- color: SettingsManager.userColor,
- background: SettingsManager.userBackgroundColor,
+ color: SnappingManager.userColor,
+ background: SnappingManager.userBackgroundColor,
}),
- placeholder: (baseStyles, state) => ({
+ placeholder: (baseStyles /* , state */) => ({
...baseStyles,
- color: SettingsManager.userColor,
- background: SettingsManager.userBackgroundColor,
+ color: SnappingManager.userColor,
+ background: SnappingManager.userBackgroundColor,
}),
- input: (baseStyles, state) => ({
+ input: (baseStyles /* , state */) => ({
...baseStyles,
padding: 0,
margin: 0,
- color: SettingsManager.userColor,
+ color: SnappingManager.userColor,
background: 'transparent',
}),
option: (baseStyles, state) => ({
...baseStyles,
- color: SettingsManager.userColor,
- background: !state.isFocused ? SettingsManager.userBackgroundColor : SettingsManager.userVariantColor,
+ color: SnappingManager.userColor,
+ background: !state.isFocused ? SnappingManager.userBackgroundColor : SnappingManager.userVariantColor,
}),
- menuList: (baseStyles, state) => ({
+ menuList: (baseStyles /* , state */) => ({
...baseStyles,
- backgroundColor: SettingsManager.userBackgroundColor,
+ backgroundColor: SnappingManager.userBackgroundColor,
}),
}}
placeholder={typeof this._props.placeholder === 'string' ? this._props.placeholder : this._props.placeholder?.()}
@@ -106,12 +106,14 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
onChange={val => this._props.selectFunc((val as any as { value: string; label: string }).value)}
onKeyDown={e => {
if (e.key === 'Enter') {
- runInAction(() => this._props.selectFunc((this._newField = (e.nativeEvent.target as any)?.value)));
+ runInAction(() => {
+ this._props.selectFunc((this._newField = (e.nativeEvent.target as any)?.value));
+ });
}
e.stopPropagation();
}}
onMenuClose={this._props.menuClose}
- closeMenuOnSelect={true}
+ closeMenuOnSelect
value={this._props.showPlaceholder ? null : undefined}
/>
);
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 0521c4a4b..c97edd7f0 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -1,19 +1,22 @@
+/* eslint-disable react/jsx-props-no-spreading */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { action, computed, makeObservable, observable, ObservableMap } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider';
import { AiOutlineMinusSquare, AiOutlinePlusSquare } from 'react-icons/ai';
import { CiCircleRemove } from 'react-icons/ci';
-import { Doc, DocListCast, Field, LinkedTo, StrListCast } from '../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, LinkedTo, StrListCast } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { RichTextField } from '../../fields/RichTextField';
-import { DocumentManager } from '../util/DocumentManager';
import { SearchUtil } from '../util/SearchUtil';
-import { SettingsManager } from '../util/SettingsManager';
+import { SnappingManager } from '../util/SnappingManager';
import { undoable } from '../util/UndoManager';
import { FieldsDropdown } from './FieldsDropdown';
import './FilterPanel.scss';
+import { DocumentView } from './nodes/DocumentView';
import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components';
import { ObservableReactComponent } from './ObservableReactComponent';
@@ -37,7 +40,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
return this._props.Document;
}
@computed get targetDocChildKey() {
- const targetView = DocumentManager.Instance.getFirstDocumentView(this.Document);
+ const targetView = DocumentView.getFirstDocumentView(this.Document);
return targetView?.ComponentView?.annotationKey ?? targetView?.ComponentView?.fieldKey ?? 'data';
}
@computed get targetDocChildren() {
@@ -58,7 +61,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
@computed get mapActiveFiltersToFacets() {
const filters = new Map<string, string>();
- //this.targetDoc.docFilters
+ // this.targetDoc.docFilters
this.activeFilters.map(filter => filters.set(filter.split(Doc.FilterSep)[1], filter.split(Doc.FilterSep)[0]));
return filters;
}
@@ -73,7 +76,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
// ["#tags", "width", "height"]
//
@computed get activeFacetHeaders() {
- const activeHeaders = new Array();
+ const activeHeaders = [] as string[];
this.activeFilters.map(filter => activeHeaders.push(filter.split(Doc.FilterSep)[0]));
return activeHeaders;
@@ -83,19 +86,20 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
const valueSet = new Set<string>(childFilters.map(filter => filter.split(Doc.FilterSep)[1]));
let rtFields = 0;
let subDocs = childDocs;
- let gatheredDocs = [] as Doc[];
+ const gatheredDocs = [] as Doc[];
if (subDocs.length > 0) {
let newarray: Doc[] = [];
while (subDocs.length > 0) {
newarray = [];
+ // eslint-disable-next-line no-loop-func
subDocs.forEach(t => {
gatheredDocs.push(t);
const facetVal = t[facetKey];
if (facetVal instanceof RichTextField || typeof facetVal === 'string') rtFields++;
- facetVal !== undefined && valueSet.add(Field.toString(facetVal as Field));
- (facetVal === true || facetVal == false) && valueSet.add(Field.toString(!facetVal));
+ facetVal !== undefined && valueSet.add(Field.toString(facetVal as FieldType));
+ (facetVal === true || facetVal === false) && valueSet.add(Field.toString(!facetVal));
const fieldKey = Doc.LayoutFieldKey(t);
- const annos = !Field.toString(Doc.LayoutField(t) as Field).includes('CollectionView');
+ const annos = !Field.toString(Doc.LayoutField(t) as FieldType).includes('CollectionView');
DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
});
@@ -115,7 +119,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
// @observable _chosenFacets = new ObservableMap<string, 'text' | 'checkbox' | 'slider' | 'range'>();
@observable _chosenFacetsCollapse = new ObservableMap<string, boolean>();
- @observable _collapseReturnKeys = new Array();
+ @observable _collapseReturnKeys = [] as string[];
// this computed function gets the active filters and maps them to their headers
//
@@ -130,11 +134,11 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
const facetValues = FilterPanel.gatherFieldValues(this.targetDocChildren, facetHeader, StrListCast(this.Document.childFilters));
let nonNumbers = 0;
- let minVal = Number.MAX_VALUE,
- maxVal = -Number.MAX_VALUE;
- facetValues.strings.map(val => {
+ let minVal = Number.MAX_VALUE;
+ let maxVal = -Number.MAX_VALUE;
+ facetValues.strings.forEach(val => {
const num = val ? Number(val) : Number.NaN;
- if (Number.isNaN(num)) {
+ if (isNaN(num)) {
val && nonNumbers++;
} else {
minVal = Math.min(num, minVal);
@@ -144,14 +148,14 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
if (facetHeader === 'text') {
return { facetHeader, renderType: 'text' };
- } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) {
+ }
+ if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) {
const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1));
const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05)));
- const ranged = Doc.readDocRangeFilter(this.Document, facetHeader); // not the filter range, but the zooomed in range on the filter
- return { facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged ? ranged : [extendedMinVal, extendedMaxVal] };
- } else {
- return { facetHeader, renderType: 'checkbox' };
+ const ranged: number[] | undefined = Doc.readDocRangeFilter(this.Document, facetHeader); // not the filter range, but the zooomed in range on the filter
+ return { facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged || [extendedMinVal, extendedMaxVal] };
}
+ return { facetHeader, renderType: 'checkbox' };
})
);
}
@@ -171,17 +175,17 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
sortingCurrentFacetValues = (facetHeader: string) => {
this._collapseReturnKeys.splice(0);
- Array.from(this.activeRenderedFacetInfos.keys()).map(renderInfo => {
+ Array.from(this.activeRenderedFacetInfos.keys()).forEach(renderInfo => {
if (renderInfo.renderType === 'range' && renderInfo.facetHeader === facetHeader && renderInfo.range) {
- this._collapseReturnKeys.push(renderInfo.range.map(number => number.toFixed(2)));
+ this._collapseReturnKeys.push(...renderInfo.range.map(number => number.toFixed(2)));
}
});
- for (var key of this.facetValues(facetHeader)) {
+ this.facetValues(facetHeader).forEach(key => {
if (this.mapActiveFiltersToFacets.get(key)) {
this._collapseReturnKeys.push(key);
}
- }
+ });
return <div className=" filterbox-collpasedAndActive">{this._collapseReturnKeys.join(', ')}</div>;
};
@@ -198,7 +202,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
);
else
allCollectionDocs.forEach(child => {
- const fieldVal = child[facetHeader] as Field;
+ const fieldVal = child[facetHeader] as FieldType;
if (!(fieldVal instanceof List)) {
// currently we have no good way of filtering based on a field that is a list
set.add(Field.toString(fieldVal));
@@ -209,7 +213,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
let nonNumbers = 0;
- facetValues.map(val => Number.isNaN(Number(val)) && nonNumbers++);
+ facetValues.map(val => isNaN(Number(val)) && nonNumbers++);
return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2));
};
@@ -218,7 +222,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
<div className="filterBox-treeView">
<div className="filterBox-select">
<div style={{ width: '100%' }}>
- <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder={true} placeholder="add a filter" addedFields={['acl-Guest', LinkedTo]} />
+ <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder placeholder="add a filter" addedFields={['acl_Guest', LinkedTo]} />
</div>
{/* THE FOLLOWING CODE SHOULD BE DEVELOPER FOR BOOLEAN EXPRESSION (AND / OR) */}
{/* <div className="filterBox-select-bool">
@@ -234,34 +238,31 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
<div className="filterBox-tree" key="tree">
{Array.from(this.activeRenderedFacetInfos.keys()).map(
- (
- renderInfo // iterato over activeFacetRenderInfos ==> renderInfo which you can renderInfo.facetHeader
- ) => (
+ // iterate over activeFacetRenderInfos ==> renderInfo which you can renderInfo.facetHeader
+ renderInfo => (
<div>
<div className="filterBox-facetHeader">
<div className="filterBox-facetHeader-Header"> </div>
{renderInfo.facetHeader.charAt(0).toUpperCase() + renderInfo.facetHeader.slice(1)}
-
<div
className="filterBox-facetHeader-collapse"
- onClick={action(e => {
+ onClick={action(() => {
const collapseBoolValue = this._chosenFacetsCollapse.get(renderInfo.facetHeader);
this._chosenFacetsCollapse.set(renderInfo.facetHeader, !collapseBoolValue);
})}>
{this._chosenFacetsCollapse.get(renderInfo.facetHeader) ? <AiOutlinePlusSquare /> : <AiOutlineMinusSquare />}
</div>
-
<div
className="filterBox-facetHeader-remove"
- onClick={action(e => {
+ onClick={action(() => {
if (renderInfo.facetHeader === 'text') {
Doc.setDocFilter(this.Document, renderInfo.facetHeader, 'match', 'remove');
} else {
- for (var key of this.facetValues(renderInfo.facetHeader)) {
+ this.facetValues(renderInfo.facetHeader).forEach((key: string) => {
if (this.mapActiveFiltersToFacets.get(key)) {
Doc.setDocFilter(this.Document, renderInfo.facetHeader, key, 'remove');
}
- }
+ });
}
this._selectedFacetHeaders.delete(renderInfo.facetHeader);
this._chosenFacetsCollapse.delete(renderInfo.facetHeader);
@@ -292,15 +293,15 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
return (
<input
key={this.Document[Id]}
- placeholder={'enter text to match'}
+ placeholder="enter text to match"
defaultValue={
StrListCast(this.Document._childFilters)
.find(filter => filter.split(Doc.FilterSep)[0] === facetHeader)
?.split(Doc.FilterSep)[1]
}
- style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}
+ style={{ color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}
onBlur={undoable(e => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')}
- onKeyDown={e => e.key === 'Enter' && undoable(e => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')(e)}
+ onKeyDown={e => e.key === 'Enter' && undoable(() => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')()}
/>
);
case 'checkbox':
@@ -312,7 +313,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
style={{ width: 20, marginLeft: 20 }}
checked={['check', 'exists'].includes(
StrListCast(this.Document._childFilters)
- .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] == facetValue)
+ .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] === facetValue)
?.split(Doc.FilterSep)[2] ?? ''
)}
type={type}
@@ -324,55 +325,56 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
});
case 'range':
- const domain = renderInfoDomain;
- const range = renderInfoRange;
- if (domain) {
- return (
- <div className="sliderBox-outerDiv" style={{ width: '95%', height: 45, float: 'right' }}>
- <Slider
- mode={2}
- step={Math.min(1, 0.1 * (domain[1] - domain[0]))}
- domain={[domain[0], domain[1]]} // -1000, 1000
- rootStyle={{ position: 'relative', width: '100%' }}
- onChange={values => Doc.setDocRangeFilter(this.Document, facetHeader, values)}
- values={renderInfoRange!}>
- <Rail>{railProps => <TooltipRail {...railProps} />}</Rail>
- <Handles>
- {({ handles, activeHandleID, getHandleProps }) => (
- <div className="slider-handles">
- {handles.map((handle, i) => {
- // const value = i === 0 ? defaultValues[0] : defaultValues[1];
- return (
+ {
+ const domain = renderInfoDomain;
+ if (domain) {
+ return (
+ <div className="sliderBox-outerDiv" style={{ width: '95%', height: 45, float: 'right' }}>
+ <Slider
+ mode={2}
+ step={Math.min(1, 0.1 * (domain[1] - domain[0]))}
+ domain={[domain[0], domain[1]]} // -1000, 1000
+ rootStyle={{ position: 'relative', width: '100%' }}
+ onChange={values => Doc.setDocRangeFilter(this.Document, facetHeader, values)}
+ values={renderInfoRange!}>
+ <Rail>{railProps => <TooltipRail {...railProps} />}</Rail>
+ <Handles>
+ {({ handles, activeHandleID, getHandleProps }) => (
+ <div className="slider-handles">
+ {handles.map(handle => (
+ // const value = i === 0 ? defaultValues[0] : defaultValues[1];
<div>
<Handle key={handle.id} handle={handle} domain={domain} isActive={handle.id === activeHandleID} getHandleProps={getHandleProps} />
</div>
- );
- })}
- </div>
- )}
- </Handles>
- <Tracks left={false} right={false}>
- {({ tracks, getTrackProps }) => (
- <div className="slider-tracks">
- {tracks.map(({ id, source, target }) => (
- <Track key={id} source={source} target={target} disabled={false} getTrackProps={getTrackProps} />
- ))}
- </div>
- )}
- </Tracks>
- <Ticks count={5}>
- {({ ticks }) => (
- <div className="slider-ticks">
- {ticks.map(tick => (
- <Tick key={tick.id} tick={tick} count={ticks.length} format={(val: number) => val.toString()} />
- ))}
- </div>
- )}
- </Ticks>
- </Slider>
- </div>
- );
+ ))}
+ </div>
+ )}
+ </Handles>
+ <Tracks left={false} right={false}>
+ {({ tracks, getTrackProps }) => (
+ <div className="slider-tracks">
+ {tracks.map(({ id, source, target }) => (
+ <Track key={id} source={source} target={target} disabled={false} getTrackProps={getTrackProps} />
+ ))}
+ </div>
+ )}
+ </Tracks>
+ <Ticks count={5}>
+ {({ ticks }) => (
+ <div className="slider-ticks">
+ {ticks.map(tick => (
+ <Tick key={tick.id} tick={tick} count={ticks.length} format={(val: number) => val.toString()} />
+ ))}
+ </div>
+ )}
+ </Ticks>
+ </Slider>
+ </div>
+ );
+ }
}
+ break;
+ default:
// case 'range'
// return <Slider ...
@@ -386,5 +388,6 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
// <dimain changing handles >
// <?div
}
+ return undefined;
}
}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 86b9f5e40..7246f0ba0 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -2,17 +2,8 @@ import * as fitCurve from 'fit-curve';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../Utils';
-import { Doc, Opt } from '../../fields/Doc';
-import { InkData, InkTool } from '../../fields/InkField';
-import { BoolCast, NumCast } from '../../fields/Types';
-import MobileInkOverlay from '../../mobile/MobileInkOverlay';
-import { GestureUtils } from '../../pen-gestures/GestureUtils';
-import { MobileInkOverlayContent } from '../../server/Message';
-import { InteractionUtils } from '../util/InteractionUtils';
-import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { Transform } from '../util/Transform';
-import './GestureOverlay.scss';
+import { returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
import {
ActiveArrowEnd,
ActiveArrowScale,
@@ -22,32 +13,44 @@ import {
ActiveInkBezierApprox,
ActiveInkColor,
ActiveInkWidth,
+ Doc,
+ Opt,
SetActiveArrowStart,
SetActiveDash,
SetActiveFillColor,
SetActiveInkColor,
SetActiveInkWidth,
-} from './InkingStroke';
+} from '../../fields/Doc';
+import { InkData, InkTool } from '../../fields/InkField';
+import { NumCast } from '../../fields/Types';
+// import MobileInkOverlay from '../../mobile/MobileInkOverlay';
+import { Gestures } from '../../pen-gestures/GestureTypes';
+import { GestureUtils } from '../../pen-gestures/GestureUtils';
+// import { MobileInkOverlayContent } from '../../server/Message';
+import { InteractionUtils } from '../util/InteractionUtils';
+import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { Transform } from '../util/Transform';
+import './GestureOverlay.scss';
import { ObservableReactComponent } from './ObservableReactComponent';
-import { checkInksToGroup } from './global/globalScripts';
import { DocumentView } from './nodes/DocumentView';
+export enum ToolglassTools {
+ InkToText = 'inktotext',
+ IgnoreGesture = 'ignoregesture',
+ RadialMenu = 'radialmenu',
+ None = 'none',
+}
interface GestureOverlayProps {
isActive: boolean;
}
@observer
export class GestureOverlay extends ObservableReactComponent<React.PropsWithChildren<GestureOverlayProps>> {
+ // eslint-disable-next-line no-use-before-define
static Instance: GestureOverlay;
+ // eslint-disable-next-line no-use-before-define
static Instances: GestureOverlay[] = [];
- public static set RecognizeGestures(active) {
- Doc.UserDoc().recognizeGestures = active;
- }
- public static get RecognizeGestures() {
- return BoolCast(Doc.UserDoc().recognizeGestures);
- }
-
- @observable public InkShape: Opt<GestureUtils.Gestures> = undefined;
+ @observable public InkShape: Opt<Gestures> = undefined;
@observable public SavedColor?: string = undefined;
@observable public SavedWidth?: number = undefined;
@observable public Tool: ToolglassTools = ToolglassTools.None;
@@ -55,9 +58,6 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
@observable private _thumbX?: number = undefined;
@observable private _thumbY?: number = undefined;
- @observable private _selectedIndex: number = -1;
- @observable private _menuX: number = -300;
- @observable private _menuY: number = -300;
@observable private _pointerY?: number = undefined;
@observable private _points: { X: number; Y: number }[] = [];
@observable private _strokes: InkData[] = [];
@@ -65,8 +65,6 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
@observable private _clipboardDoc?: JSX.Element = undefined;
@observable private _possibilities: JSX.Element[] = [];
- public static DownDocView: DocumentView | undefined;
-
@computed private get height(): number {
return 2 * Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 100, 100);
}
@@ -74,7 +72,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
return this.Tool !== ToolglassTools.None;
}
- @observable private showMobileInkOverlay: boolean = false;
+ // @observable private showMobileInkOverlay: boolean = false;
private _overlayRef = React.createRef<HTMLDivElement>();
private _d1: Doc | undefined;
@@ -111,15 +109,14 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
this._points.push({ X: e.clientX, Y: e.clientY });
if (this._points.length > 1) {
- const B = this.svgBounds;
const initialPoint = this._points[0];
const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height;
const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
switch (this.Tool) {
- case ToolglassTools.RadialMenu:
- return true;
- }
+ case ToolglassTools.RadialMenu: return true;
+ default:
+ } // prettier-ignore
}
}
return false;
@@ -128,21 +125,21 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
@action primCreated() {
if (!this.KeepPrimitiveMode) {
this.InkShape = undefined;
- //get out of ink mode after each stroke=
- //if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor);
+ // get out of ink mode after each stroke=
+ // if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor);
Doc.ActiveTool = InkTool.None;
// SetActiveArrowStart('none');
// SetActiveArrowEnd('none');
}
}
@action
- onPointerUp = (e: PointerEvent) => {
- GestureOverlay.DownDocView = undefined;
+ onPointerUp = () => {
+ DocumentView.DownDocView = undefined;
if (this._points.length > 1) {
const B = this.svgBounds;
const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
- //if any of the shape is activated in the CollectionFreeFormViewChrome
+ // if any of the shape is activated in the CollectionFreeFormViewChrome
if (this.InkShape) {
this.makeBezierPolygon(this.InkShape, false);
this.dispatchGesture(this.InkShape);
@@ -151,20 +148,21 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
// if we're not drawing in a toolglass try to recognize as gesture
else {
// need to decide when to turn gestures back on
- const result = points.length > 2 && GestureUtils.GestureRecognizer.Recognize(new Array(points));
+ const result = points.length > 2 && GestureUtils.GestureRecognizer.Recognize([points]);
let actionPerformed = false;
- if (GestureOverlay.RecognizeGestures && result && result.Score > 0.7) {
+ if (Doc.UserDoc().recognizeGestures && result && result.Score > 0.7) {
switch (result.Name) {
- case GestureUtils.Gestures.Line:
- case GestureUtils.Gestures.Triangle:
- case GestureUtils.Gestures.Rectangle:
- case GestureUtils.Gestures.Circle:
+ case Gestures.Line:
+ case Gestures.Triangle:
+ case Gestures.Rectangle:
+ case Gestures.Circle:
this.makeBezierPolygon(result.Name, true);
actionPerformed = this.dispatchGesture(result.Name);
break;
- case GestureUtils.Gestures.Scribble:
+ case Gestures.Scribble:
console.log('scribble');
break;
+ default:
}
}
@@ -178,21 +176,20 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
const controlPoints: { X: number; Y: number }[] = [];
const bezierCurves = (fitCurve as any)(newPoints, 10);
- for (const curve of bezierCurves) {
+ Array.from(bezierCurves).forEach((curve: any) => {
controlPoints.push({ X: curve[0][0], Y: curve[0][1] });
controlPoints.push({ X: curve[1][0], Y: curve[1][1] });
controlPoints.push({ X: curve[2][0], Y: curve[2][1] });
controlPoints.push({ X: curve[3][0], Y: curve[3][1] });
- }
+ });
const dist = Math.sqrt(
(controlPoints[0].X - controlPoints.lastElement().X) * (controlPoints[0].X - controlPoints.lastElement().X) + (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y)
);
+ // eslint-disable-next-line prefer-destructuring
if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0];
this._points.length = 0;
this._points.push(...controlPoints);
- this.dispatchGesture(GestureUtils.Gestures.Stroke);
- // TODO: nda - check inks to group here
- checkInksToGroup();
+ this.dispatchGesture(Gestures.Stroke);
}
}
}
@@ -202,34 +199,34 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
makeBezierPolygon = (shape: string, gesture: boolean) => {
const xs = this._points.map(p => p.X);
const ys = this._points.map(p => p.Y);
- var right = Math.max(...xs);
- var left = Math.min(...xs);
- var bottom = Math.max(...ys);
- var top = Math.min(...ys);
+ let right = Math.max(...xs);
+ let left = Math.min(...xs);
+ let bottom = Math.max(...ys);
+ let top = Math.min(...ys);
const firstx = this._points[0].X;
const firsty = this._points[0].Y;
- var lastx = this._points[this._points.length - 2].X;
- var lasty = this._points[this._points.length - 2].Y;
- var fourth = (lastx - firstx) / 4;
+ let lastx = this._points[this._points.length - 2].X;
+ let lasty = this._points[this._points.length - 2].Y;
+ let fourth = (lastx - firstx) / 4;
if (isNaN(fourth) || fourth === 0) {
fourth = 0.01;
}
- var m = (lasty - firsty) / (lastx - firstx);
+ let m = (lasty - firsty) / (lastx - firstx);
if (isNaN(m) || m === 0) {
m = 0.01;
}
- const b = firsty - m * firstx;
+ // const b = firsty - m * firstx;
if (shape === 'noRec') {
return false;
}
if (!gesture) {
- //if shape options is activated in inkOptionMenu
- //take second to last point because _point[length-1] is _points[0]
+ // if shape options is activated in inkOptionMenu
+ // take second to last point because _point[length-1] is _points[0]
right = this._points[this._points.length - 2].X;
left = this._points[0].X;
bottom = this._points[this._points.length - 2].Y;
top = this._points[0].Y;
- if (shape !== GestureUtils.Gestures.Arrow && shape !== GestureUtils.Gestures.Line && shape !== GestureUtils.Gestures.Circle) {
+ if (shape !== Gestures.Arrow && shape !== Gestures.Line && shape !== Gestures.Circle) {
if (left > right) {
const temp = right;
right = left;
@@ -244,7 +241,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
}
this._points.length = 0;
switch (shape) {
- case GestureUtils.Gestures.Rectangle:
+ case Gestures.Rectangle:
this._points.push({ X: left, Y: top });
this._points.push({ X: left, Y: top });
this._points.push({ X: right, Y: top });
@@ -267,7 +264,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
break;
- case GestureUtils.Gestures.Triangle:
+ case Gestures.Triangle:
this._points.push({ X: left, Y: bottom });
this._points.push({ X: left, Y: bottom });
@@ -285,39 +282,40 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
this._points.push({ X: left, Y: bottom });
break;
- case GestureUtils.Gestures.Circle:
- // Approximation of a circle using 4 Bézier curves in which the constant "c" reduces the maximum radial drift to 0.019608%,
- // making the curves indistinguishable from a circle.
- // Source: https://spencermortensen.com/articles/bezier-circle/
- const c = 0.551915024494;
- const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
- const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
- const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
-
- // Dividing the circle into four equal sections, and fitting each section to a cubic Bézier curve.
- this._points.push({ X: centerX, Y: centerY + radius });
- this._points.push({ X: centerX + c * radius, Y: centerY + radius });
- this._points.push({ X: centerX + radius, Y: centerY + c * radius });
- this._points.push({ X: centerX + radius, Y: centerY });
-
- this._points.push({ X: centerX + radius, Y: centerY });
- this._points.push({ X: centerX + radius, Y: centerY - c * radius });
- this._points.push({ X: centerX + c * radius, Y: centerY - radius });
- this._points.push({ X: centerX, Y: centerY - radius });
-
- this._points.push({ X: centerX, Y: centerY - radius });
- this._points.push({ X: centerX - c * radius, Y: centerY - radius });
- this._points.push({ X: centerX - radius, Y: centerY - c * radius });
- this._points.push({ X: centerX - radius, Y: centerY });
-
- this._points.push({ X: centerX - radius, Y: centerY });
- this._points.push({ X: centerX - radius, Y: centerY + c * radius });
- this._points.push({ X: centerX - c * radius, Y: centerY + radius });
- this._points.push({ X: centerX, Y: centerY + radius });
-
+ case Gestures.Circle:
+ {
+ // Approximation of a circle using 4 Bézier curves in which the constant "c" reduces the maximum radial drift to 0.019608%,
+ // making the curves indistinguishable from a circle.
+ // Source: https://spencermortensen.com/articles/bezier-circle/
+ const c = 0.551915024494;
+ const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
+ const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
+ const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
+
+ // Dividing the circle into four equal sections, and fitting each section to a cubic Bézier curve.
+ this._points.push({ X: centerX, Y: centerY + radius });
+ this._points.push({ X: centerX + c * radius, Y: centerY + radius });
+ this._points.push({ X: centerX + radius, Y: centerY + c * radius });
+ this._points.push({ X: centerX + radius, Y: centerY });
+
+ this._points.push({ X: centerX + radius, Y: centerY });
+ this._points.push({ X: centerX + radius, Y: centerY - c * radius });
+ this._points.push({ X: centerX + c * radius, Y: centerY - radius });
+ this._points.push({ X: centerX, Y: centerY - radius });
+
+ this._points.push({ X: centerX, Y: centerY - radius });
+ this._points.push({ X: centerX - c * radius, Y: centerY - radius });
+ this._points.push({ X: centerX - radius, Y: centerY - c * radius });
+ this._points.push({ X: centerX - radius, Y: centerY });
+
+ this._points.push({ X: centerX - radius, Y: centerY });
+ this._points.push({ X: centerX - radius, Y: centerY + c * radius });
+ this._points.push({ X: centerX - c * radius, Y: centerY + radius });
+ this._points.push({ X: centerX, Y: centerY + radius });
+ }
break;
- case GestureUtils.Gestures.Line:
+ case Gestures.Line:
if (Math.abs(firstx - lastx) < 10 && Math.abs(firsty - lasty) > 10) {
lastx = firstx;
}
@@ -330,28 +328,32 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
this._points.push({ X: lastx, Y: lasty });
this._points.push({ X: lastx, Y: lasty });
break;
- case GestureUtils.Gestures.Arrow:
- const x1 = left;
- const y1 = top;
- const x2 = right;
- const y2 = bottom;
- const L1 = Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + Math.pow(Math.abs(y1 - y2), 2));
- const L2 = L1 / 5;
- const angle = 0.785398;
- const x3 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) + (y1 - y2) * Math.sin(angle));
- const y3 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) - (x1 - x2) * Math.sin(angle));
- const x4 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle));
- const y4 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) + (x1 - x2) * Math.sin(angle));
- this._points.push({ X: x1, Y: y1 });
- this._points.push({ X: x2, Y: y2 });
- this._points.push({ X: x3, Y: y3 });
- this._points.push({ X: x4, Y: y4 });
- this._points.push({ X: x2, Y: y2 });
+ case Gestures.Arrow:
+ {
+ const x1 = left;
+ const y1 = top;
+ const x2 = right;
+ const y2 = bottom;
+ const L1 = Math.sqrt(Math.abs(x1 - x2) ** 2 + Math.abs(y1 - y2) ** 2);
+ const L2 = L1 / 5;
+ const angle = 0.785398;
+ const x3 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) + (y1 - y2) * Math.sin(angle));
+ const y3 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) - (x1 - x2) * Math.sin(angle));
+ const x4 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle));
+ const y4 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) + (x1 - x2) * Math.sin(angle));
+ this._points.push({ X: x1, Y: y1 });
+ this._points.push({ X: x2, Y: y2 });
+ this._points.push({ X: x3, Y: y3 });
+ this._points.push({ X: x4, Y: y4 });
+ this._points.push({ X: x2, Y: y2 });
+ }
+ break;
+ default:
}
return false;
};
- dispatchGesture = (gesture: GestureUtils.Gestures, stroke?: InkData, text?: any) => {
+ dispatchGesture = (gesture: Gestures, stroke?: InkData, text?: any) => {
const points = (stroke ?? this._points).slice();
return (
document.elementFromPoint(points[0].X, points[0].Y)?.dispatchEvent(
@@ -384,14 +386,14 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
}
get elements() {
- const selView = GestureOverlay.DownDocView;
+ const selView = DocumentView.DownDocView;
const width = Number(ActiveInkWidth()) * NumCast(selView?.Document._freeform_scale, 1); // * (selView?.screenToViewTransform().Scale || 1);
const rect = this._overlayRef.current?.getBoundingClientRect();
- const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(this._points, true);
- B.left = B.left - width / 2;
- B.right = B.right + width / 2;
+ const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; // this.getBounds(this._points, true);
+ B.left -= width / 2;
+ B.right += width / 2;
B.top = B.top - width / 2 - (rect?.y || 0);
- B.bottom = B.bottom + width / 2;
+ B.bottom += width / 2;
B.width += width;
B.height += width;
const fillColor = ActiveFillColor();
@@ -401,8 +403,9 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
this._palette,
[
this._strokes.map((l, i) => {
- const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(l, true);
+ const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; // this.getBounds(l, true);
return (
+ // eslint-disable-next-line react/no-array-index-key
<svg key={i} width={b.width} height={b.height} style={{ top: 0, left: 0, transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}>
{InteractionUtils.CreatePolyline(
l,
@@ -414,7 +417,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
'miter',
'round',
ActiveInkBezierApprox(),
- 'none' /*ActiveFillColor()*/,
+ 'none' /* ActiveFillColor() */,
ActiveArrowStart(),
ActiveArrowEnd(),
ActiveArrowScale(),
@@ -441,7 +444,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
'miter',
'round',
'',
- 'none' /*ActiveFillColor()*/,
+ 'none' /* ActiveFillColor() */,
ActiveArrowStart(),
ActiveArrowEnd(),
ActiveArrowScale(),
@@ -491,15 +494,10 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
this._clipboardDoc = undefined;
};
- @action
- enableMobileInkOverlay = (content: MobileInkOverlayContent) => {
- this.showMobileInkOverlay = content.enableOverlay;
- };
-
render() {
return (
<div className="gestureOverlay-cont" style={{ pointerEvents: this._props.isActive ? 'all' : 'none' }} ref={this._overlayRef} onPointerDown={this.onPointerDown}>
- {this.showMobileInkOverlay ? <MobileInkOverlay /> : null}
+ {/* {this.showMobileInkOverlay ? <MobileInkOverlay /> : null} */}
{this.elements}
<div
@@ -529,19 +527,14 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
}
}
-// export class
-
-export enum ToolglassTools {
- InkToText = 'inktotext',
- IgnoreGesture = 'ignoregesture',
- RadialMenu = 'radialmenu',
- None = 'none',
-}
-
ScriptingGlobals.add('GestureOverlay', GestureOverlay);
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setToolglass(tool: any) {
- runInAction(() => (GestureOverlay.Instance.Tool = tool));
+ runInAction(() => {
+ GestureOverlay.Instance.Tool = tool;
+ });
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setPen(width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) {
runInAction(() => {
GestureOverlay.Instance.SavedColor = ActiveInkColor();
@@ -554,6 +547,7 @@ ScriptingGlobals.add(function setPen(width: any, color: any, fill: any, arrowSta
SetActiveDash(dash);
});
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function resetPen() {
runInAction(() => {
SetActiveInkColor(GestureOverlay.Instance.SavedColor ?? 'rgb(0, 0, 0)');
@@ -561,8 +555,9 @@ ScriptingGlobals.add(function resetPen() {
});
}, 'resets the pen tool');
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function createText(text: any, x: any, y: any) {
- GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Text, [{ X: x, Y: y }], text);
+ GestureOverlay.Instance.dispatchGesture(Gestures.Text, [{ X: x, Y: y }], text);
},
'creates a text document with inputted text and coordinates',
'(text: any, x: any, y: any)'
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 2f64ea28c..c73da35bc 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -8,11 +8,10 @@ import { Cast, PromiseValue } from '../../fields/Types';
import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { DragManager } from '../util/DragManager';
import { GroupManager } from '../util/GroupManager';
-import { SelectionManager } from '../util/SelectionManager';
import { SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
import { SnappingManager } from '../util/SnappingManager';
-import { UndoManager } from '../util/UndoManager';
+import { UndoManager, undoable } from '../util/UndoManager';
import { ContextMenu } from './ContextMenu';
import { DocumentDecorations } from './DocumentDecorations';
import { InkStrokeProperties } from './InkStrokeProperties';
@@ -21,24 +20,26 @@ import { MainView } from './MainView';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline';
import { CollectionFreeFormView } from './collections/collectionFreeForm';
+import { CollectionFreeFormDocumentView } from './nodes/CollectionFreeFormDocumentView';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
-import { OpenWhereMod } from './nodes/DocumentView';
+import { DocumentView } from './nodes/DocumentView';
+import { OpenWhereMod } from './nodes/OpenWhere';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { AnchorMenu } from './pdf/AnchorMenu';
const modifiers = ['control', 'meta', 'shift', 'alt'];
-type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo;
type KeyControlInfo = {
preventDefault: boolean;
stopPropagation: boolean;
};
-
-export let CtrlKey = false;
+type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo;
export class KeyManager {
- public static Instance: KeyManager = new KeyManager();
+ // eslint-disable-next-line no-use-before-define
+ public static Instance: KeyManager;
private router = new Map<string, KeyHandler>();
constructor() {
+ KeyManager.Instance = this;
const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0;
// SHIFT CONTROL ALT META
@@ -47,10 +48,20 @@ export class KeyManager {
this.router.set(isMac ? '0100' : '0010', this.alt);
this.router.set(isMac ? '1001' : '1100', this.ctrl_shift);
this.router.set('1000', this.shift);
+
+ window.removeEventListener('keydown', KeyManager.Instance.handleModifiers, true);
+ window.addEventListener('keydown', KeyManager.Instance.handleModifiers, true);
+ window.removeEventListener('keyup', KeyManager.Instance.unhandleModifiers);
+ window.addEventListener('keyup', KeyManager.Instance.unhandleModifiers);
+ window.removeEventListener('keydown', KeyManager.Instance.handle);
+ window.addEventListener('keydown', KeyManager.Instance.handle);
+ window.removeEventListener('keyup', KeyManager.Instance.unhandle);
+ window.addEventListener('keyup', KeyManager.Instance.unhandle);
+ window.addEventListener('paste', KeyManager.Instance.paste as any);
}
- public unhandle = action((e: KeyboardEvent) => {
- e.key === 'Control' && (CtrlKey = false);
+ public unhandle = action((/* e: KeyboardEvent */) => {
+ /* empty */
});
public handleModifiers = action((e: KeyboardEvent) => {
if (e.shiftKey) SnappingManager.SetShiftKey(true);
@@ -65,11 +76,9 @@ export class KeyManager {
public handle = action((e: KeyboardEvent) => {
// accumulate buffer of characters to insert in a new text note. once the note is created, it will stop keyboard events from reaching this function.
- if (FormattedTextBox.SelectOnLoadChar) FormattedTextBox.SelectOnLoadChar = FormattedTextBox.SelectOnLoadChar + (e.key === 'Enter' ? '\n' : e.key);
- e.key === 'Control' && (CtrlKey = true);
- //if (!Doc.noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.UPDATE_SERVER_CACHE(true);
+ if (FormattedTextBox.SelectOnLoadChar) FormattedTextBox.SelectOnLoadChar += e.key === 'Enter' ? '\n' : e.key;
const keyname = e.key && e.key.toLowerCase();
- this.handleGreedy(keyname);
+ this.handleGreedy(/* keyname */);
if (modifiers.includes(keyname)) {
return;
@@ -89,14 +98,11 @@ export class KeyManager {
control.preventDefault && e.preventDefault();
});
- private handleGreedy = action((keyname: string) => {
- switch (keyname) {
- }
- });
+ private handleGreedy = action((/* keyname: string */) => {});
nudge = (x: number, y: number, label: string) => {
- const nudgeable = SelectionManager.Views.some(dv => dv.CollectionFreeFormDocumentView?.nudge);
- nudgeable && UndoManager.RunInBatch(() => SelectionManager.Views.map(dv => dv.CollectionFreeFormDocumentView?.nudge(x, y)), label);
+ const nudgeable = DocumentView.Selected().some(dv => CollectionFreeFormDocumentView.from(dv)?.nudge);
+ nudgeable && UndoManager.RunInBatch(() => DocumentView.Selected().map(dv => CollectionFreeFormDocumentView.from(dv)?.nudge(x, y)), label);
return { stopPropagation: nudgeable, preventDefault: nudgeable };
};
@@ -104,67 +110,62 @@ export class KeyManager {
switch (keyname) {
case 'u':
if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
- const ungroupings = SelectionManager.Views;
- UndoManager.RunInBatch(() => ungroupings.map(dv => (dv.layoutDoc.group = undefined)), 'ungroup');
- SelectionManager.DeselectAll();
+ const ungroupings = DocumentView.Selected();
+ undoable(() => () => ungroupings.forEach(dv => { dv.layoutDoc.group = undefined; }), 'ungroup');
+ DocumentView.DeselectAll();
}
break;
case 'g':
if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
- const selected = SelectionManager.Views;
- const collectionView = selected.reduce((col, dv) => (col === null || dv.CollectionFreeFormView === col ? dv.CollectionFreeFormView : undefined), null as null | undefined | CollectionFreeFormView);
- if (collectionView) {
- UndoManager.RunInBatch(() =>
- collectionView._marqueeViewRef.current?.collection(e, true, SelectionManager.Docs)
- , 'grouping');
- break;
- }
+ const selected = DocumentView.Selected();
+ const cv = selected.reduce((col, dv) => (!col || CollectionFreeFormView.from(dv) === col ? CollectionFreeFormView.from(dv) : undefined), undefined as undefined | CollectionFreeFormView);
+ cv && undoable(() => cv._marqueeViewRef.current?.collection(e, true, DocumentView.SelectedDocs()), 'grouping');
}
break;
case ' ':
// MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI
break;
- case 'escape':
- DocumentLinksButton.StartLink = undefined;
- DocumentLinksButton.StartLinkView = undefined;
- InkStrokeProperties.Instance._controlButton = false;
- Doc.ActiveTool = InkTool.None;
- DragManager.CompleteWindowDrag?.(true);
- var doDeselect = true;
- if (SnappingManager.IsDragging) {
- DragManager.AbortDrag();
- }
- if (CollectionDockingView.Instance?.HasFullScreen) {
- CollectionDockingView.Instance?.CloseFullScreen();
- }
- if (CollectionStackedTimeline.SelectingRegions.size) {
- CollectionStackedTimeline.StopSelecting();
- doDeselect = false;
- } else {
- doDeselect = !ContextMenu.Instance.closeMenu();
- }
- if (doDeselect) {
- SelectionManager.DeselectAll();
- LightboxView.Instance.SetLightboxDoc(undefined);
+ case 'escape': {
+ DocumentLinksButton.StartLink = undefined;
+ DocumentLinksButton.StartLinkView = undefined;
+ InkStrokeProperties.Instance._controlButton = false;
+ Doc.ActiveTool = InkTool.None;
+ DragManager.CompleteWindowDrag?.(true);
+ let doDeselect = true;
+ if (SnappingManager.IsDragging) {
+ DragManager.AbortDrag();
+ }
+ if (CollectionDockingView.Instance?.HasFullScreen) {
+ CollectionDockingView.Instance?.CloseFullScreen();
+ }
+ if (CollectionStackedTimeline.SelectingRegions.size) {
+ CollectionStackedTimeline.StopSelecting();
+ doDeselect = false;
+ } else {
+ doDeselect = !ContextMenu.Instance.closeMenu();
+ }
+ if (doDeselect) {
+ DocumentView.DeselectAll();
+ LightboxView.Instance.SetLightboxDoc(undefined);
+ }
+ // DictationManager.Controls.stop();
+ GoogleAuthenticationManager.Instance.cancel();
+ SharingManager.Instance.close();
+ if (!GroupManager.Instance.isOpen) SettingsManager.Instance.closeMgr();
+ GroupManager.Instance.close();
+ window.getSelection()?.empty();
+ document.body.focus();
}
- // DictationManager.Controls.stop();
- GoogleAuthenticationManager.Instance.cancel();
- SharingManager.Instance.close();
- if (!GroupManager.Instance.isOpen) SettingsManager.Instance.close();
- GroupManager.Instance.close();
- window.getSelection()?.empty();
- document.body.focus();
break;
- case 'enter': {
- //DocumentDecorations.Instance.onCloseClick(false);
+ case 'enter':
+ // DocumentDecorations.Instance.onCloseClick(false);
break;
- }
case 'delete':
case 'backspace':
if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
if (LightboxView.LightboxDoc) {
LightboxView.Instance.SetLightboxDoc(undefined);
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
} else if (!window.getSelection()?.toString()) DocumentDecorations.Instance.onCloseClick(true);
return { stopPropagation: true, preventDefault: true };
}
@@ -173,6 +174,7 @@ export class KeyManager {
case 'arrowright': return this.nudge(1,0, 'nudge right');
case 'arrowup': return this.nudge(0, -1, 'nudge up');
case 'arrowdown': return this.nudge(0, 1, 'nudge down');
+ default:
} // prettier-ignore
return {
@@ -189,17 +191,18 @@ export class KeyManager {
case 'arrowdown': return this.nudge(0, 10, 'nudge down');
case 'u' :
if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
- UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => (doc.group = undefined)), 'unggroup');
- SelectionManager.DeselectAll();
+ UndoManager.RunInBatch(() => DocumentView.Selected().map(dv => dv.Document).forEach(doc => {doc.group = undefined}), 'unggroup');
+ DocumentView.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');
- SelectionManager.DeselectAll();
+ UndoManager.RunInBatch(() => DocumentView.Selected().forEach(dv => {dv.Document.group = randomGroup}), 'group');
+ DocumentView.DeselectAll();
}
break;
+ default:
} // prettier-ignore
return {
@@ -215,8 +218,9 @@ export class KeyManager {
switch (keyname) {
case 'ƒ':
case 'f':
- const dv = SelectionManager.Views?.[0];
- UndoManager.RunInBatch(() => dv.CollectionFreeFormDocumentView?.float(), 'float');
+ UndoManager.RunInBatch(() => CollectionFreeFormDocumentView.from(DocumentView.Selected()?.[0])?.float(), 'float');
+ break;
+ default:
}
return {
@@ -251,20 +255,24 @@ export class KeyManager {
PromiseValue(Cast(Doc.UserDoc()['tabs-button-tools'], Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
break;
case 'i':
- const importBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImports);
- if (importBtn) {
- MainView.Instance.selectMenu(importBtn);
+ {
+ const importBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImports);
+ if (importBtn) {
+ MainView.Instance.selectMenu(importBtn);
+ }
}
break;
case 's':
- const trailsBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyTrails);
- if (trailsBtn) {
- MainView.Instance.selectMenu(trailsBtn);
+ {
+ const trailsBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyTrails);
+ if (trailsBtn) {
+ MainView.Instance.selectMenu(trailsBtn);
+ }
}
break;
case 'f':
- if (SelectionManager.Views.length === 1 && SelectionManager.Views[0].ComponentView?.search) {
- SelectionManager.Views[0].ComponentView?.search?.('', false, false);
+ if (DocumentView.Selected().length === 1 && DocumentView.Selected()[0].ComponentView?.search) {
+ DocumentView.Selected()[0].ComponentView?.search?.('', false, false);
} else {
const searchBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MySearcher);
if (searchBtn) {
@@ -283,14 +291,14 @@ export class KeyManager {
break;
case 'y':
if (Doc.ActivePage !== 'home') {
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
UndoManager.Redo();
}
stopPropagation = false;
break;
case 'z':
if (Doc.ActivePage !== 'home') {
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
UndoManager.Undo();
}
stopPropagation = false;
@@ -306,11 +314,17 @@ export class KeyManager {
preventDefault = false;
break;
case 'x':
- if (SelectionManager.Views.length) {
+ if (DocumentView.Selected().length) {
const bds = DocumentDecorations.Instance.Bounds;
- const pt = SelectionManager.Views[0].screenToViewTransform().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 = DocumentView.Selected()[0] //
+ .screenToViewTransform()
+ .transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
+ const text =
+ `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + //
+ DocumentView.Selected()
+ .map(dv => dv.Document[Id])
+ .join(':'); // prettier-ignore
+ DocumentView.Selected().length && navigator.clipboard.writeText(text);
DocumentDecorations.Instance.onCloseClick(true);
stopPropagation = false;
preventDefault = false;
@@ -319,13 +333,20 @@ 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].screenToViewTransform().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 = DocumentView.Selected()[0]
+ .screenToViewTransform()
+ .transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2); // prettier-ignore
+ const text =
+ `__DashCloneId(${pt?.[0] || 0},${pt?.[1] || 0}):` +
+ DocumentView.SelectedDocs()
+ .map(doc => doc[Id])
+ .join(':'); // prettier-ignore
+ DocumentView.Selected().length && navigator.clipboard.writeText(text);
stopPropagation = false;
}
preventDefault = false;
break;
+ default:
}
return {
@@ -339,7 +360,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 = DocumentView.Selected().lastElement()?.ComponentView?.addDocument;
if (addDocument && (plain.startsWith('__DashDocId(') || clone)) {
Doc.Paste(docids.slice(1), clone, addDocument);
}
@@ -360,6 +381,7 @@ export class KeyManager {
case 'p':
Doc.ActiveTool = InkTool.Write;
break;
+ default:
}
return {
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index edc6b404b..a49923ffb 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -2,18 +2,19 @@ import * as React from 'react';
import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../fields/Doc';
-import { ControlPoint, InkData, PointData } from '../../fields/InkField';
+import { ControlPoint, InkData } from '../../fields/InkField';
import { List } from '../../fields/List';
import { listSpec } from '../../fields/Schema';
import { Cast } from '../../fields/Types';
-import { returnFalse, setupMoveUpEvents } from '../../Utils';
-import { SelectionManager } from '../util/SelectionManager';
+import { returnFalse, setupMoveUpEvents } from '../../ClientUtils';
import { UndoManager } from '../util/UndoManager';
import { Colors } from './global/globalEnums';
import { InkingStroke } from './InkingStroke';
import { InkStrokeProperties } from './InkStrokeProperties';
import { SnappingManager } from '../util/SnappingManager';
import { ObservableReactComponent } from './ObservableReactComponent';
+import { PointData } from '../../pen-gestures/GestureTypes';
+import { DocumentView } from './nodes/DocumentView';
export interface InkControlProps {
inkDoc: Doc;
@@ -48,7 +49,7 @@ export class InkControlPtHandles extends ObservableReactComponent<InkControlProp
*/
@action
onControlDown = (e: React.PointerEvent, controlIndex: number): void => {
- const ptFromScreen = this._props.inkView.ptFromScreen;
+ const { ptFromScreen } = this._props.inkView;
if (ptFromScreen) {
const order = controlIndex % 4;
const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this._props.inkCtrlPoints.length) % this._props.inkCtrlPoints.length;
@@ -60,7 +61,7 @@ export class InkControlPtHandles extends ObservableReactComponent<InkControlProp
setupMoveUpEvents(
this,
e,
- action((e: PointerEvent, down: number[], delta: number[]) => {
+ action((moveEv: PointerEvent, down: number[], delta: number[]) => {
if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] });
const inkMoveStart = ptFromScreen({ X: 0, Y: 0 });
@@ -75,9 +76,9 @@ export class InkControlPtHandles extends ObservableReactComponent<InkControlProp
this._props.inkView.controlUndo = undefined;
UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
}),
- action((e: PointerEvent, doubleTap: boolean | undefined) => {
+ action((moveEv: PointerEvent, doubleTap: boolean | undefined) => {
const equivIndex = controlIndex === 0 ? this._props.inkCtrlPoints.length - 1 : controlIndex === this._props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
- if (doubleTap || e.button === 2) {
+ if (doubleTap || moveEv.button === 2) {
if (!brokenIndices?.includes(equivIndex) && !brokenIndices?.includes(controlIndex)) {
if (brokenIndices) brokenIndices.push(controlIndex);
else this._props.inkDoc.brokenInkIndices = new List<number>([controlIndex]);
@@ -128,7 +129,9 @@ export class InkControlPtHandles extends ObservableReactComponent<InkControlProp
* Changes the current selected control point.
*/
@action
- changeCurrPoint = (i: number) => (InkStrokeProperties.Instance._currentPoint = i);
+ changeCurrPoint = (i: number) => {
+ InkStrokeProperties.Instance._currentPoint = i;
+ };
render() {
// Accessing the current ink's data and extracting all control points.
@@ -176,7 +179,7 @@ export class InkControlPtHandles extends ObservableReactComponent<InkControlProp
};
return (
<svg>
- {!nearestScreenPt ? null : <circle key={'npt'} cx={nearestScreenPt.X} cy={nearestScreenPt.Y} r={this._props.screenSpaceLineWidth * 2} fill={'#00007777'} stroke={'#00007777'} strokeWidth={0} pointerEvents="none" />}
+ {!nearestScreenPt ? null : <circle key="npt" cx={nearestScreenPt.X} cy={nearestScreenPt.Y} r={this._props.screenSpaceLineWidth * 2} fill="#00007777" stroke="#00007777" strokeWidth={0} pointerEvents="none" />}
{sreenCtrlPoints.map(control => hdl(control, this._overControl !== control.I ? 1 : 3 / 2, Colors.WHITE))}
</svg>
);
@@ -207,22 +210,22 @@ export class InkEndPtHandles extends ObservableReactComponent<InkEndProps> {
setupMoveUpEvents(
this,
e,
- action(e => {
+ action(moveEv => {
if (this._throttle++ % 2 !== 0) return false;
if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('stretch ink');
// compute stretch factor by finding scaling along axis between start and end points
const p1 = pt1();
const p2 = pt2();
const v1 = { X: p1.X - p2.X, Y: p1.Y - p2.Y };
- const v2 = { X: e.clientX - p2.X, Y: e.clientY - p2.Y };
+ const v2 = { X: moveEv.clientX - p2.X, Y: moveEv.clientY - p2.Y };
const v1len = Math.sqrt(v1.X * v1.X + v1.Y * v1.Y);
const v2len = Math.sqrt(v2.X * v2.X + v2.Y * v2.Y);
const scaling = v2len / v1len;
const v1n = { X: v1.X / v1len, Y: v1.Y / v1len };
const v2n = { X: v2.X / v2len, Y: v2.Y / v2len };
const angle = Math.acos(v1n.X * v2n.X + v1n.Y * v2n.Y) * Math.sign(v1.X * v2.Y - v2.X * v1.Y);
- InkStrokeProperties.Instance.stretchInk(SelectionManager.Views, scaling, p2, v1n, e.shiftKey);
- InkStrokeProperties.Instance.rotateInk(SelectionManager.Views, angle, pt2()); // bcz: call pt2() func here because pt2 will have changed from previous stretchInk call
+ InkStrokeProperties.Instance.stretchInk(DocumentView.Selected(), scaling, p2, v1n, moveEv.shiftKey);
+ InkStrokeProperties.Instance.rotateInk(DocumentView.Selected(), angle, pt2()); // bcz: call pt2() func here because pt2 will have changed from previous stretchInk call
return false;
}),
action(() => {
@@ -243,10 +246,14 @@ export class InkEndPtHandles extends ObservableReactComponent<InkEndProps> {
cy={pt?.Y}
r={this._props.screenSpaceLineWidth * 2}
fill={this._overStart ? '#aaaaaa' : '#99999977'}
- stroke={'#00007777'}
+ stroke="#00007777"
strokeWidth={0}
- onPointerLeave={action(() => (this._overStart = false))}
- onPointerEnter={action(() => (this._overStart = true))}
+ onPointerLeave={action(() => {
+ this._overStart = false;
+ })}
+ onPointerEnter={action(() => {
+ this._overStart = true;
+ })}
onPointerDown={dragFunc}
pointerEvents="all"
/>
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 52ea89cde..3920ecc2a 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -1,20 +1,22 @@
import { Bezier } from 'bezier-js';
+import * as _ from 'lodash';
import { action, makeObservable, observable, reaction, runInAction } from 'mobx';
import { Doc, NumListCast, Opt } from '../../fields/Doc';
-import { InkData, InkField, InkTool, PointData } from '../../fields/InkField';
+import { InkData, InkField, InkTool } from '../../fields/InkField';
import { List } from '../../fields/List';
import { listSpec } from '../../fields/Schema';
import { Cast, NumCast } from '../../fields/Types';
+import { PointData } from '../../pen-gestures/GestureTypes';
import { Point } from '../../pen-gestures/ndollar';
import { DocumentType } from '../documents/DocumentTypes';
-import { FitOneCurve } from '../util/bezierFit';
-import { DocumentManager } from '../util/DocumentManager';
import { undoBatch } from '../util/UndoManager';
+import { FitOneCurve } from '../util/bezierFit';
import { InkingStroke } from './InkingStroke';
+import { CollectionFreeFormView } from './collections/collectionFreeForm';
import { DocumentView } from './nodes/DocumentView';
-import * as _ from 'lodash';
export class InkStrokeProperties {
+ // eslint-disable-next-line no-use-before-define
static _Instance: InkStrokeProperties | undefined;
public static get Instance() {
return this._Instance || new InkStrokeProperties();
@@ -28,11 +30,15 @@ export class InkStrokeProperties {
makeObservable(this);
reaction(
() => this._controlButton,
- button => button && (Doc.ActiveTool = InkTool.None)
+ button => {
+ button && (Doc.ActiveTool = InkTool.None);
+ }
);
reaction(
() => Doc.ActiveTool,
- tool => tool !== InkTool.None && (this._controlButton = false)
+ tool => {
+ tool !== InkTool.None && (this._controlButton = false);
+ }
);
}
@@ -46,7 +52,7 @@ export class InkStrokeProperties {
func: (view: DocumentView, ink: InkData, ptsXscale: number, ptsYscale: number, inkStrokeWidth: number) => { X: number; Y: number }[] | undefined,
requireCurrPoint: boolean = false
) => {
- var appliedFunc = false;
+ let appliedFunc = false;
(strokes instanceof DocumentView ? [strokes] : strokes)?.forEach(
action(inkView => {
if (!requireCurrPoint || this._currentPoint !== -1) {
@@ -85,7 +91,7 @@ export class InkStrokeProperties {
*/
@undoBatch
addPoints = (inkView: DocumentView, t: number, i: number, controls: { X: number; Y: number }[]) => {
- this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
+ this.applyFunction(inkView, (view: DocumentView /* , ink: InkData */) => {
const doc = view.Document;
const array = [controls[i], controls[i + 1], controls[i + 2], controls[i + 3]];
const newsegs = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).split(t);
@@ -94,7 +100,9 @@ export class InkStrokeProperties {
// Updating the indices of the control points whose handle tangency has been broken.
doc.brokenInkIndices = new List(Cast(doc.brokenInkIndices, listSpec('number'), []).map(control => (control > i ? control + 4 : control)));
- runInAction(() => (this._currentPoint = -1));
+ runInAction(() => {
+ this._currentPoint = -1;
+ });
return controls;
});
@@ -126,8 +134,8 @@ export class InkStrokeProperties {
*/
getNewHandlePoints = (C: PointData[], D: PointData[], newControl: PointData) => {
const [m, n] = [C.length, D.length];
- let handleSizeA = Math.sqrt(Math.pow(newControl.X - C[0].X, 2) + Math.pow(newControl.Y - C[0].Y, 2));
- let handleSizeB = Math.sqrt(Math.pow(D[n - 1].X - newControl.X, 2) + Math.pow(D[n - 1].Y - newControl.Y, 2));
+ let handleSizeA = Math.sqrt((newControl.X - C[0].X) ** 2 + (newControl.Y - C[0].Y) ** 2);
+ let handleSizeB = Math.sqrt((D[n - 1].X - newControl.X) ** 2 + (D[n - 1].Y - newControl.Y) ** 2);
// Scaling adjustments to improve the ratio between the magnitudes of the two handle lines.
// (Ensures that the new point added doesn't augment the inital shape of the curve much).
if (handleSizeA < 75 && handleSizeB < 75) {
@@ -167,13 +175,13 @@ export class InkStrokeProperties {
const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
const splicedPoints = ink.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
const samples: Point[] = [];
- var startDir = { x: 0, y: 0 };
- var endDir = { x: 0, y: 0 };
- for (var i = 0; i < splicedPoints.length / 4; i++) {
+ let startDir = { x: 0, y: 0 };
+ let endDir = { x: 0, y: 0 };
+ for (let i = 0; i < splicedPoints.length / 4; i++) {
const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
if (i === 0) startDir = bez.derivative(0);
if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1);
- for (var t = 0; t < (i === splicedPoints.length / 4 - 1 ? 1 + 1e-7 : 1); t += 0.05) {
+ for (let t = 0; t < (i === splicedPoints.length / 4 - 1 ? 1 + 1e-7 : 1); t += 0.05) {
const pt = bez.compute(t);
samples.push(new Point(pt.x, pt.y));
}
@@ -186,7 +194,9 @@ export class InkStrokeProperties {
}
}
doc.brokenInkIndices = new List(brokenIndices.map(control => (control >= this._currentPoint ? control - 4 : control)));
- runInAction(() => (this._currentPoint = -1));
+ runInAction(() => {
+ this._currentPoint = -1;
+ });
return newPoints.length < 4 ? undefined : newPoints;
},
true
@@ -200,7 +210,7 @@ export class InkStrokeProperties {
*/
@undoBatch
rotateInk = (inkStrokes: DocumentView[], angle: number, scrpt: PointData) => {
- this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData, xScale: number, yScale: number, inkStrokeWidth: number) => {
+ this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData, xScale: number, yScale: number /* , inkStrokeWidth: number */) => {
const inkCenterPt = view.ComponentView?.ptFromScreen?.(scrpt);
return !inkCenterPt
? ink
@@ -247,37 +257,37 @@ export class InkStrokeProperties {
const closed = InkingStroke.IsClosed(ink);
const brokenIndices = Cast(inkView.Document.brokenInkIndices, listSpec('number'), []);
if (origInk && this._currentPoint > 0 && this._currentPoint < ink.length - 1 && brokenIndices.findIndex(value => value === controlIndex) === -1) {
- const cpt_before = ink[controlIndex];
- const cpt = { X: cpt_before.X + deltaX, Y: cpt_before.Y + deltaY };
+ const cptBefore = ink[controlIndex];
+ const cpt = { X: cptBefore.X + deltaX, Y: cptBefore.Y + deltaY };
const newink = origInk.slice();
const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
const splicedPoints = origInk.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
const { nearestT, nearestSeg } = InkStrokeProperties.nearestPtToStroke(splicedPoints, cpt);
- if ((nearestSeg === 0 && nearestT < 1e-1) || (nearestSeg === 4 && 1 - nearestT < 1e-1)) return ink.slice();
+ if ((nearestSeg === 0 && nearestT < 1e-1) || (nearestSeg === 4 && 1 - nearestT < 1e-1) || nearestSeg < 0) return ink.slice();
const samplesLeft: Point[] = [];
const samplesRight: Point[] = [];
- var startDir = { x: 0, y: 0 };
- var endDir = { x: 0, y: 0 };
- for (var i = 0; i < nearestSeg / 4 + 1; i++) {
+ let startDir = { x: 0, y: 0 };
+ let endDir = { x: 0, y: 0 };
+ for (let i = 0; i < nearestSeg / 4 + 1; i++) {
const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
if (i === 0) startDir = bez.derivative(_.isEqual(bez.derivative(0), { x: 0, y: 0, t: 0 }) ? 1e-8 : 0);
if (i === nearestSeg / 4) endDir = bez.derivative(nearestT);
- for (var t = 0; t < (i === nearestSeg / 4 ? nearestT + 0.05 : 1); t += 0.05) {
+ for (let t = 0; t < (i === nearestSeg / 4 ? nearestT + 0.05 : 1); t += 0.05) {
const pt = bez.compute(i !== nearestSeg / 4 ? t : Math.min(nearestT, t));
samplesLeft.push(new Point(pt.x, pt.y));
}
}
- var { finalCtrls } = FitOneCurve(samplesLeft, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
- for (var i = nearestSeg / 4; i < splicedPoints.length / 4; i++) {
+ let { finalCtrls } = FitOneCurve(samplesLeft, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
+ for (let i = nearestSeg / 4; i < splicedPoints.length / 4; i++) {
const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
if (i === nearestSeg / 4) startDir = bez.derivative(nearestT);
if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(_.isEqual(bez.derivative(1), { x: 0, y: 0, t: 1 }) ? 1 - 1e-8 : 1);
- for (var t = i === nearestSeg / 4 ? nearestT : 0; t < (i === nearestSeg / 4 ? 1 + 0.05 + 1e-7 : 1 + 1e-7); t += 0.05) {
+ for (let t = i === nearestSeg / 4 ? nearestT : 0; t < (i === nearestSeg / 4 ? 1 + 0.05 + 1e-7 : 1 + 1e-7); t += 0.05) {
const pt = bez.compute(Math.min(1, t));
samplesRight.push(new Point(pt.x, pt.y));
}
}
- const { finalCtrls: rightCtrls, error: errorRight } = FitOneCurve(samplesRight, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
+ const { finalCtrls: rightCtrls /* , error: errorRight */ } = FitOneCurve(samplesRight, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
finalCtrls = finalCtrls.concat(rightCtrls);
newink.splice(this._currentPoint - 4, 8, ...finalCtrls);
return newink;
@@ -307,11 +317,12 @@ export class InkStrokeProperties {
});
public static nearestPtToStroke(ctrlPoints: { X: number; Y: number }[], refInkSpacePt: { X: number; Y: number }, excludeSegs?: number[]) {
- var distance = Number.MAX_SAFE_INTEGER;
- var nearestT = -1;
- var nearestSeg = -1;
- var nearestPt = { X: 0, Y: 0 };
- for (var i = 0; i < ctrlPoints.length - 3; i += 4) {
+ let distance = Number.MAX_SAFE_INTEGER;
+ let nearestT = -1;
+ let nearestSeg = -1;
+ let nearestPt = { X: 0, Y: 0 };
+ for (let i = 0; i < ctrlPoints.length - 3; i += 4) {
+ // eslint-disable-next-line no-continue
if (excludeSegs?.includes(i)) continue;
const array = [ctrlPoints[i], ctrlPoints[i + 1], ctrlPoints[i + 2], ctrlPoints[i + 3]];
const point = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).project({ x: refInkSpacePt.X, y: refInkSpacePt.Y });
@@ -369,17 +380,18 @@ export class InkStrokeProperties {
};
snapToAllCurves = (screenDragPt: { X: number; Y: number }, inkView: DocumentView, snapData: { nearestPt: { X: number; Y: number }; distance: number }, ink: InkData, controlIndex: number) => {
- const containingCollection = inkView.CollectionFreeFormView;
+ const containingCollection = CollectionFreeFormView.from(inkView);
const containingDocView = containingCollection?.DocumentView?.();
containingCollection?.childDocs
.filter(doc => doc.type === DocumentType.INK)
.forEach(doc => {
- const testInkView = DocumentManager.Instance.getDocumentView(doc, containingDocView);
+ const testInkView = DocumentView.getDocumentView(doc, containingDocView);
const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt, doc === inkView.Document ? this.excludeSelfSnapSegs(ink, controlIndex) : []);
if (snapped && snapped.distance < snapData.distance) {
const snappedInkPt = doc === inkView.Document ? snapped.nearestPt : inkView.ComponentView?.ptFromScreen?.(testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt) ?? { X: 0, Y: 0 }); // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space
if (snappedInkPt) {
+ // eslint-disable-next-line no-param-reassign
snapData = { nearestPt: snappedInkPt, distance: snapped.distance };
}
}
@@ -406,6 +418,7 @@ export class InkStrokeProperties {
inkCopy[handleIndexB] = this.rotatePoint(handleB, controlPoint, angleDifference);
return inkCopy;
}
+ return undefined;
});
};
@@ -430,7 +443,9 @@ export class InkStrokeProperties {
const magnitudeB = Math.sqrt(vectorB.X * vectorB.X + vectorB.Y * vectorB.Y);
if (magnitudeA === 0 || magnitudeB === 0) return 0;
// Normalizing the vectors.
+ // eslint-disable-next-line no-param-reassign
vectorA = { X: vectorA.X / magnitudeA, Y: vectorA.Y / magnitudeA };
+ // eslint-disable-next-line no-param-reassign
vectorB = { X: vectorB.X / magnitudeB, Y: vectorB.Y / magnitudeB };
return Math.acos(vectorB.X * vectorA.X + vectorB.Y * vectorA.Y);
}
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index c20399698..577acc4d1 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -6,18 +6,18 @@ import { HandleLine, HandlePoint, InkData } from '../../fields/InkField';
import { List } from '../../fields/List';
import { listSpec } from '../../fields/Schema';
import { Cast } from '../../fields/Types';
-import { emptyFunction, setupMoveUpEvents } from '../../Utils';
-import { Transform } from '../util/Transform';
+import { emptyFunction } from '../../Utils';
+import { setupMoveUpEvents } from '../../ClientUtils';
import { UndoManager } from '../util/UndoManager';
import { Colors } from './global/globalEnums';
import { InkingStroke } from './InkingStroke';
import { InkStrokeProperties } from './InkStrokeProperties';
+
export interface InkHandlesProps {
inkDoc: Doc;
inkView: InkingStroke;
screenCtrlPoints: InkData;
screenSpaceLineWidth: number;
- ScreenToLocalTransform: () => Transform;
}
@observer
@@ -37,9 +37,9 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
setupMoveUpEvents(
this,
e,
- action((e: PointerEvent, down: number[], delta: number[]) => {
+ action((moveEv: PointerEvent, down: number[], delta: number[]) => {
if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('DocDecs move tangent');
- if (e.altKey) this.onBreakTangent(controlIndex);
+ if (moveEv.altKey) this.onBreakTangent(controlIndex);
const inkMoveEnd = this.props.inkView.ptFromScreen({ X: delta[0], Y: delta[1] });
const inkMoveStart = this.props.inkView.ptFromScreen({ X: 0, Y: 0 });
this.docView && InkStrokeProperties.Instance.moveTangentHandle(this.docView, -(inkMoveEnd.X - inkMoveStart.X), -(inkMoveEnd.Y - inkMoveStart.Y), handleIndex, oppositeHandleIndex, controlIndex);
@@ -100,11 +100,12 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
tangentLines.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 });
}
}
- const screenSpaceLineWidth = this.props.screenSpaceLineWidth;
+ const { screenSpaceLineWidth } = this.props;
return (
<>
{tangentHandles.map((pts, i) => (
+ // eslint-disable-next-line react/no-array-index-key
<svg height="10" width="10" key={`hdl${i}`}>
<circle
cx={pts.X}
@@ -128,12 +129,13 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
x2={x2}
y2={y2}
stroke={Colors.MEDIUM_BLUE}
- strokeDasharray={'1 1'}
+ strokeDasharray="1 1"
strokeWidth={1}
display={pts.dot1 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._currentPoint ? 'inherit' : 'none'}
/>
);
return (
+ // eslint-disable-next-line react/no-array-index-key
<svg height="100" width="100" key={`line${i}`}>
{tangentLine(pts.X1, pts.Y1, pts.X2, pts.Y2)}
{tangentLine(pts.X2, pts.Y2, pts.X3, pts.Y3)}
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index 7e2b334af..1ed8de1be 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -6,7 +6,6 @@
// 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';
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 51baaa23e..f497ca447 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -23,30 +23,33 @@
import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { DashColor, returnFalse, setupMoveUpEvents } from '../../ClientUtils';
import { Doc } from '../../fields/Doc';
import { InkData, InkField } from '../../fields/InkField';
import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
import { TraceMobx } from '../../fields/util';
-import { DashColor, returnFalse, setupMoveUpEvents } from '../../Utils';
import { CognitiveServices } from '../cognitive_services/CognitiveServices';
import { Docs } from '../documents/Documents';
+import { DocumentType } from '../documents/DocumentTypes';
import { InteractionUtils } from '../util/InteractionUtils';
import { SnappingManager } from '../util/SnappingManager';
import { UndoManager } from '../util/UndoManager';
import { ContextMenu } from './ContextMenu';
-import { ViewBoxAnnotatableComponent, ViewBoxBaseComponent, ViewBoxInterface } from './DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from './DocComponent';
import { Colors } from './global/globalEnums';
import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
import './InkStroke.scss';
import { InkStrokeProperties } from './InkStrokeProperties';
import { InkTangentHandles } from './InkTangentHandles';
import { FieldView, FieldViewProps } from './nodes/FieldView';
-import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import { PinProps, PresBox } from './nodes/trails';
-import { StyleProp } from './StyleProvider';
+import { FormattedTextBox, FormattedTextBoxProps } from './nodes/formattedText/FormattedTextBox';
+import { PinDocView, PinProps } from './PinFuncs';
+import { StyleProp } from './StyleProp';
+
const { INK_MASK_SIZE } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
+
@observer
-export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
+export class InkingStroke extends ViewBoxAnnotatableComponent<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)
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(InkingStroke, fieldStr);
@@ -65,7 +68,9 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
this._props.setContentViewBox?.(this);
this._disposers.selfDisper = reaction(
() => this._props.isSelected(), // react to stroke being deselected by turning off ink handles
- selected => !selected && (InkStrokeProperties.Instance._controlButton = false)
+ selected => {
+ !selected && (InkStrokeProperties.Instance._controlButton = false);
+ }
);
}
componentWillUnmount() {
@@ -88,7 +93,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
if (anchor) {
anchor.backgroundColor = 'transparent';
addAsAnnotation && this.addDocument(anchor);
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), inkable: true } }, this.Document);
+ PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), inkable: true } }, this.Document);
return anchor;
}
return this.Document;
@@ -140,7 +145,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
e,
!isEditing
? returnFalse
- : action((e: PointerEvent, down: number[], delta: number[]) => {
+ : action((moveEv: PointerEvent, down: number[], delta: number[]) => {
if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
const inkMoveEnd = this.ptFromScreen({ X: delta[0], Y: delta[1] });
const inkMoveStart = this.ptFromScreen({ X: 0, Y: 0 });
@@ -155,7 +160,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
this.controlUndo = undefined;
UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
}),
- action((e: PointerEvent, doubleTap: boolean | undefined) => {
+ action((moveEv: PointerEvent, doubleTap: boolean | undefined) => {
if (doubleTap) {
InkStrokeProperties.Instance._controlButton = true;
InkStrokeProperties.Instance._currentPoint = -1;
@@ -167,7 +172,9 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
}),
isEditing,
isEditing,
- action(() => wasSelected && (InkStrokeProperties.Instance._currentPoint = -1))
+ action(() => {
+ wasSelected && (InkStrokeProperties.Instance._currentPoint = -1);
+ })
);
};
@@ -325,32 +332,34 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
false
)}
<InkControlPtHandles inkView={this} inkDoc={inkDoc} inkCtrlPoints={inkData} screenCtrlPoints={this.screenCtrlPts} nearestScreenPt={this.nearestScreenPt} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
- <InkTangentHandles inkView={this} inkDoc={inkDoc} screenCtrlPoints={this.screenCtrlPts} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} ScreenToLocalTransform={this.ScreenToLocalBoxXf} />
+ <InkTangentHandles inkView={this} inkDoc={inkDoc} screenCtrlPoints={this.screenCtrlPts} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
</div>
);
};
- _subContentView: ViewBoxInterface | undefined;
- setSubContentView = (doc: ViewBoxInterface) => (this._subContentView = doc);
- @computed get fillColor() {
+ _subContentView: ViewBoxInterface<FormattedTextBoxProps> | undefined;
+ setSubContentView = (box: ViewBoxInterface<FormattedTextBoxProps>) => {
+ this._subContentView = box;
+ };
+ @computed get fillColor(): string {
const isInkMask = BoolCast(this.layoutDoc.stroke_isInkMask);
return isInkMask ? DashColor(StrCast(this.layoutDoc.fillColor, 'transparent')).blacken(0).rgb().toString() : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FillColor) ?? 'transparent';
}
@computed get strokeColor() {
const { inkData } = this.inkScaledData();
- const fillColor = this.fillColor;
+ const { fillColor } = this;
return !InkingStroke.IsClosed(inkData) && fillColor && fillColor !== 'transparent' ? fillColor : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) ?? StrCast(this.layoutDoc.color);
}
render() {
TraceMobx();
- const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData();
+ const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY } = this.inkScaledData();
const startMarker = StrCast(this.layoutDoc.stroke_startMarker);
const endMarker = StrCast(this.layoutDoc.stroke_endMarker);
const markerScale = NumCast(this.layoutDoc.stroke_markerScale, 1);
const closed = InkingStroke.IsClosed(inkData);
const isInkMask = BoolCast(this.layoutDoc.stroke_isInkMask);
- const fillColor = this.fillColor;
+ const { fillColor } = this;
// bcz: Hack!! Not really sure why, but having fractional values for width/height of mask ink strokes causes the dragging clone (see DragManager) to be offset from where it should be.
if (isInkMask && (this.layoutDoc._width !== Math.round(NumCast(this.layoutDoc._width)) || this.layoutDoc._height !== Math.round(NumCast(this.layoutDoc._height)))) {
@@ -387,12 +396,11 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
1.0,
false,
undefined,
- undefined,
- color === 'transparent' ? highlightColor : undefined
+ undefined
);
const higlightMargin = Math.min(12, Math.max(2, 0.3 * inkStrokeWidth));
// Invisible polygonal line that enables the ink to be selected by the user.
- const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, mask: boolean = false) =>
+ const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, mask: boolean = false): any =>
InteractionUtils.CreatePolyline(
inkData,
inkLeft,
@@ -415,22 +423,29 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
0.0,
false,
downHdlr,
- mask,
- highlightColor
+ mask
);
// bootsrap 3 style sheet sets line height to be 20px for default 14 point font size.
// this attempts to figure out the lineHeight ratio by inquiring the body's lineHeight and dividing by the fontsize which should yield 1.428571429
// see: https://bibwild.wordpress.com/2019/06/10/bootstrap-3-to-4-changes-in-how-font-size-line-height-and-spacing-is-done-or-what-happened-to-line-height-computed/
- const lineHeightGuess = +getComputedStyle(document.body).lineHeight.replace('px', '') / +getComputedStyle(document.body).fontSize.replace('px', '');
+ // const lineHeightGuess = +getComputedStyle(document.body).lineHeight.replace('px', '') / +getComputedStyle(document.body).fontSize.replace('px', '');
const interactions = {
- onPointerLeave: action(() => (this._nearestScrPt = undefined)),
+ onPointerLeave: action(() => {
+ this._nearestScrPt = undefined;
+ }),
onPointerMove: this._props.isSelected() ? this.onPointerMove : undefined,
onClick: (e: React.MouseEvent) => this._handledClick && e.stopPropagation(),
onContextMenu: () => {
const cm = ContextMenu.Instance;
!Doc.noviceMode && cm?.addItem({ description: 'Recognize Writing', event: this.analyzeStrokes, icon: 'paint-brush' });
cm?.addItem({ description: 'Toggle Mask', event: () => InkingStroke.toggleMask(this.dataDoc), icon: 'paint-brush' });
- cm?.addItem({ description: 'Edit Points', event: action(() => (InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton)), icon: 'paint-brush' });
+ cm?.addItem({
+ description: 'Edit Points',
+ event: action(() => {
+ InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton;
+ }),
+ icon: 'paint-brush',
+ });
},
};
return (
@@ -438,10 +453,11 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
<svg
className="inkStroke"
style={{
- transform: isInkMask ? `rotate(-${NumCast(this._props.CollectionFreeFormDocumentView?.()._props.rotation ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+ transform: isInkMask ? `rotate(-${NumCast(this._props.LocalRotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
// mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
cursor: this._props.isSelected() ? 'default' : undefined,
}}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...interactions}>
{clickableLine(this.onPointerDown, isInkMask)}
{isInkMask ? null : inkLine}
@@ -455,18 +471,19 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
width: NumCast(this.layoutDoc._width),
transform: `scale(${this._props.NativeDimScaling?.() || 1})`,
transformOrigin: 'top left',
- //top: (this._props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this._props.NativeDimScaling?.() || 1)) / 2,
+ // top: (this._props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this._props.NativeDimScaling?.() || 1)) / 2,
}}>
<FormattedTextBox
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setHeight={undefined}
setContentViewBox={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
yPadding={10}
xPadding={10}
fieldKey="text"
- //dontRegisterView={true}
- noSidebar={true}
- dontScale={true}
+ // dontRegisterView={true}
+ noSidebar
+ dontScale
isContentActive={this._props.isContentActive}
/>
</div>
@@ -475,67 +492,17 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
);
}
}
-
-export function SetActiveInkWidth(width: string): void {
- !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width);
-}
-export function SetActiveBezierApprox(bezier: string): void {
- ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier);
-}
-export function SetActiveInkColor(value: string) {
- ActiveInkPen() && (ActiveInkPen().activeInkColor = value);
-}
-export function SetActiveIsInkMask(value: boolean) {
- ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value);
-}
-export function SetActiveInkHideTextLabels(value: boolean) {
- ActiveInkPen() && (ActiveInkPen().activeInkHideTextLabels = value);
-}
-export function SetActiveFillColor(value: string) {
- ActiveInkPen() && (ActiveInkPen().activeFillColor = value);
-}
-export function SetActiveArrowStart(value: string) {
- ActiveInkPen() && (ActiveInkPen().activeArrowStart = value);
-}
-export function SetActiveArrowEnd(value: string) {
- ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value);
-}
-export function SetActiveArrowScale(value: number) {
- ActiveInkPen() && (ActiveInkPen().activeArrowScale = value);
-}
-export function SetActiveDash(dash: string): void {
- !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash);
-}
-export function ActiveInkPen(): Doc {
- return Doc.UserDoc();
-}
-export function ActiveInkColor(): string {
- return StrCast(ActiveInkPen()?.activeInkColor, 'black');
-}
-export function ActiveFillColor(): string {
- return StrCast(ActiveInkPen()?.activeFillColor, '');
-}
-export function ActiveIsInkMask(): boolean {
- return BoolCast(ActiveInkPen()?.activeIsInkMask, false);
-}
-export function ActiveInkHideTextLabels(): boolean {
- return BoolCast(ActiveInkPen().activeInkHideTextLabels, false);
-}
-export function ActiveArrowStart(): string {
- return StrCast(ActiveInkPen()?.activeArrowStart, '');
-}
-export function ActiveArrowEnd(): string {
- return StrCast(ActiveInkPen()?.activeArrowEnd, '');
-}
-export function ActiveArrowScale(): number {
- return NumCast(ActiveInkPen()?.activeArrowScale, 1);
-}
-export function ActiveDash(): string {
- return StrCast(ActiveInkPen()?.activeDash, '0');
-}
-export function ActiveInkWidth(): number {
- return Number(ActiveInkPen()?.activeInkWidth);
-}
-export function ActiveInkBezierApprox(): string {
- return StrCast(ActiveInkPen()?.activeInkBezier);
-}
+Docs.Prototypes.TemplateMap.set(DocumentType.INK, {
+ // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method
+ layout: { view: InkingStroke, dataField: 'stroke' },
+ options: {
+ acl: '',
+ systemIcon: 'BsFillPencilFill', //
+ _layout_nativeDimEditable: true,
+ _layout_reflowVertical: true,
+ _layout_reflowHorizontal: true,
+ layout_hideDecorationTitle: true, // don't show title when selected
+ _layout_fitWidth: false,
+ layout_isSvg: true,
+ },
+});
diff --git a/src/client/views/KeyphraseQueryView.tsx b/src/client/views/KeyphraseQueryView.tsx
index e996fc946..81f004010 100644
--- a/src/client/views/KeyphraseQueryView.tsx
+++ b/src/client/views/KeyphraseQueryView.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
import { observer } from 'mobx-react';
import * as React from 'react';
import './KeyphraseQueryView.scss';
@@ -9,28 +10,21 @@ export interface KP_Props {
@observer
export class KeyphraseQueryView extends React.Component<KP_Props> {
- constructor(props: KP_Props) {
- super(props);
- }
-
render() {
- const kps = this.props.keyphrases.toString();
const keyterms = this.props.keyphrases.split(',');
return (
<div>
<h5>Select queries to send:</h5>
<form>
- {keyterms.map((kp: string) => {
- //return (<p>{"-" + kp}</p>);
- return (
- <p>
- <label>
- <input name="query" type="radio" />
- <span>{kp}</span>
- </label>
- </p>
- );
- })}
+ {keyterms.map((kp: string) => (
+ // return (<p>{"-" + kp}</p>);
+ <p>
+ <label>
+ <input name="query" type="radio" />
+ <span>{kp}</span>
+ </label>
+ </p>
+ ))}
</form>
</div>
);
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index ef4b5b4ca..12d899388 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -1,19 +1,18 @@
+/* eslint-disable no-use-before-define */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Toggle, ToggleType, Type } from 'browndash-components';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../Utils';
-import { Doc, DocListCast, FieldResult, Opt } from '../../fields/Doc';
+import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
+import { CreateLinkToActiveAudio, Doc, DocListCast, FieldResult, Opt } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { InkTool } from '../../fields/InkField';
-import { Cast, NumCast } from '../../fields/Types';
-import { DocUtils } from '../documents/Documents';
-import { DocumentManager } from '../util/DocumentManager';
-import { LinkManager } from '../util/LinkManager';
-import { SelectionManager } from '../util/SelectionManager';
-import { SettingsManager } from '../util/SettingsManager';
+import { Cast, NumCast, toList } from '../../fields/Types';
import { SnappingManager } from '../util/SnappingManager';
import { Transform } from '../util/Transform';
import { GestureOverlay } from './GestureOverlay';
@@ -21,9 +20,8 @@ import './LightboxView.scss';
import { ObservableReactComponent } from './ObservableReactComponent';
import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider';
import { CollectionDockingView } from './collections/CollectionDockingView';
-import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline';
-import { TabDocView } from './collections/TabDocView';
-import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
+import { DocumentView } from './nodes/DocumentView';
+import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere';
interface LightboxViewProps {
PanelWidth: number;
@@ -42,6 +40,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
*/
public static Contains(view?:DocumentView) { return view && LightboxView.Instance?._docView && (view.containerViewPath?.() ?? []).concat(view).includes(LightboxView.Instance?._docView); } // prettier-ignore
public static get LightboxDoc() { return LightboxView.Instance?._doc; } // prettier-ignore
+ // eslint-disable-next-line no-use-before-define
static Instance: LightboxView;
private _path: {
doc: Opt<Doc>; //
@@ -71,14 +70,21 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
@action
public SetLightboxDoc(doc: Opt<Doc>, target?: Doc, future?: Doc[], layoutTemplate?: Doc | string) {
const lightDoc = this._doc;
- lightDoc && lightDoc !== doc && savedKeys.forEach(key => (lightDoc[key] = this._savedState[key]));
+ lightDoc &&
+ lightDoc !== doc &&
+ savedKeys.forEach(key => {
+ lightDoc[key] = this._savedState[key];
+ });
this._savedState = {};
if (doc) {
- lightDoc !== doc && savedKeys.map(key => (this._savedState[key] = Doc.Get(doc, key, true)));
- const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement();
+ lightDoc !== doc &&
+ savedKeys.forEach(key => {
+ this._savedState[key] = Doc.Get(doc, key, true);
+ });
+ const l = CreateLinkToActiveAudio(() => doc).lastElement();
l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
- CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.());
+ DocumentView.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.());
this._history.push({ doc, target });
} else {
this._future = [];
@@ -86,14 +92,14 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
Doc.ActiveTool = InkTool.None;
SnappingManager.SetExploreMode(false);
}
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
if (future) {
this._future.push(
...(this._doc ? [this._doc] : []),
...future
.slice()
.sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow))
- .sort((a, b) => LinkManager.Links(a).length - LinkManager.Links(b).length)
+ .sort((a, b) => Doc.Links(a).length - Doc.Links(b).length)
);
}
this._doc = doc;
@@ -106,8 +112,9 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
return true;
}
- public AddDocTab = (doc: Doc, location: OpenWhere, layoutTemplate?: Doc | string) =>
- this.SetLightboxDoc(
+ public AddDocTab = (docs: Doc | Doc[], location: OpenWhere, layoutTemplate?: Doc | string) => {
+ const doc = toList(docs).lastElement();
+ return this.SetLightboxDoc(
doc,
undefined,
[...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...this._future].sort(
@@ -115,19 +122,22 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
),
layoutTemplate
);
+ };
@action
next = () => {
const lightDoc = this._doc;
if (!lightDoc) return;
const target = (this._docTarget = this._future.pop());
- const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target);
+ const targetDocView = target && DocumentView.getLightboxDocumentView(target);
if (targetDocView && target) {
- const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement();
+ const l = CreateLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement();
l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
- DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
+ DocumentView.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
if (this._history.lastElement().target !== target) this._history.push({ doc: lightDoc, target });
} else if (!target && this._path.length) {
- savedKeys.forEach(key => (lightDoc[key] = this._savedState[key]));
+ savedKeys.forEach(key => {
+ lightDoc[key] = this._savedState[key];
+ });
this._path.pop();
} else {
this.SetLightboxDoc(target);
@@ -141,10 +151,10 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
return;
}
const { doc, target } = this._history.lastElement();
- const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc);
+ const docView = DocumentView.getLightboxDocumentView(target || doc);
if (docView) {
this._docTarget = target;
- target && DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
+ target && DocumentView.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
} else {
this.SetLightboxDoc(doc, target);
}
@@ -162,8 +172,8 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
if (this._docTarget) {
const fieldKey = Doc.LayoutFieldKey(this._docTarget);
const contents = [...DocListCast(this._docTarget[fieldKey]), ...DocListCast(this._docTarget[fieldKey + '_annotations'])];
- const links = LinkManager.Links(this._docTarget)
- .map(link => LinkManager.getOppositeAnchor(link, this._docTarget!)!)
+ const links = Doc.Links(this._docTarget)
+ .map(link => Doc.getOppositeAnchor(link, this._docTarget!)!)
.filter(doc => doc);
this.SetLightboxDoc(this._docTarget, undefined, contents.length ? contents : links);
}
@@ -177,8 +187,12 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
this.SetLightboxDoc(undefined);
}
};
- toggleFitWidth = () => this._doc && (this._doc._layout_fitWidth = !this._doc._layout_fitWidth);
- togglePen = () => (Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen);
+ toggleFitWidth = () => {
+ this._doc && (this._doc._layout_fitWidth = !this._doc._layout_fitWidth);
+ };
+ togglePen = () => {
+ Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen;
+ };
toggleExplore = () => SnappingManager.SetExploreMode(!SnappingManager.ExploreMode);
lightboxDoc = () => this._doc;
@@ -188,39 +202,37 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
lightboxDocTemplate = () => this._layoutTemplate;
future = () => this._future;
- renderNavBtn = (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: IconProp, display: any, click: () => void, color?: string) => {
- return (
+ renderNavBtn = (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: IconProp, display: any, click: () => void, color?: string) => (
+ <div
+ className="lightboxView-navBtn-frame"
+ style={{
+ display: display ? '' : 'none',
+ left,
+ width: bottom !== undefined ? undefined : Math.min(this._props.PanelWidth / 4, this._props.maxBorder[0]),
+ bottom,
+ }}>
<div
- className="lightboxView-navBtn-frame"
- style={{
- display: display ? '' : 'none',
- left,
- width: bottom !== undefined ? undefined : Math.min(this._props.PanelWidth / 4, this._props.maxBorder[0]),
- bottom,
+ className="lightboxView-navBtn"
+ title={color}
+ style={{ top, color: SnappingManager.userColor, background: undefined }}
+ onClick={e => {
+ e.stopPropagation();
+ click();
}}>
- <div
- className="lightboxView-navBtn"
- title={color}
- style={{ top, color: SettingsManager.userColor, background: undefined }}
- onClick={e => {
- e.stopPropagation();
- click();
- }}>
- <div style={{ height: 10 }}>{color}</div>
- <FontAwesomeIcon icon={icon} size="3x" />
- </div>
+ <div style={{ height: 10 }}>{color}</div>
+ <FontAwesomeIcon icon={icon} size="3x" />
</div>
- );
- };
+ </div>
+ );
render() {
- let downx = 0,
- downy = 0;
+ let downx = 0;
+ let downy = 0;
const toggleBtn = (classname: string, tooltip: string, toggleBackground: any, icon: IconProp, icon2: IconProp | string, onClick: () => void) => (
<div className={classname}>
<Toggle
tooltip={tooltip}
- color={SettingsManager.userColor}
- background={toggleBackground ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor}
+ color={SnappingManager.userColor}
+ background={toggleBackground ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
toggleType={ToggleType.BUTTON}
type={Type.TERT}
icon={<FontAwesomeIcon icon={toggleBackground ? icon : (icon2 as IconProp) || icon} size="sm" />}
@@ -234,12 +246,12 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
return !this._doc ? null : (
<div
className="lightboxView-frame"
- style={{ background: SettingsManager.userBackgroundColor }}
+ style={{ background: SnappingManager.userBackgroundColor }}
onPointerDown={e => {
downx = e.clientX;
downy = e.clientY;
}}
- onClick={e => Utils.isClick(e.clientX, e.clientY, downx, downy, Date.now()) && this.SetLightboxDoc(undefined)}>
+ onClick={e => ClientUtils.isClick(e.clientX, e.clientY, downx, downy, Date.now()) && this.SetLightboxDoc(undefined)}>
<div
className="lightboxView-contents"
style={{
@@ -248,12 +260,14 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
width: this.lightboxWidth(),
height: this.lightboxHeight(),
clipPath: `path('${Doc.UserDoc().renderStyle === 'comic' ? wavyBorderPath(this.lightboxWidth(), this.lightboxHeight()) : undefined}')`,
- background: SettingsManager.userBackgroundColor,
+ background: SnappingManager.userBackgroundColor,
}}>
- <GestureOverlay isActive={true}>
+ <GestureOverlay isActive>
<DocumentView
key={this._doc.title + this._doc[Id]} // this makes a new DocumentView when the document changes which makes link following work, otherwise no DocView is registered for the new Doc
- ref={action((r: DocumentView | null) => (this._docView = r !== null ? r : undefined))}
+ ref={action((r: DocumentView | null) => {
+ this._docView = r !== null ? r : undefined;
+ })}
Document={this._doc}
PanelWidth={this.lightboxWidth}
PanelHeight={this.lightboxHeight}
@@ -263,7 +277,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
styleProvider={DefaultStyleProvider}
ScreenToLocalTransform={this.lightboxScreenToLocal}
renderDepth={0}
- suppressSetHeight={this._doc._layout_fitWidth ? true : false}
+ suppressSetHeight={!!this._doc._layout_fitWidth}
containerViewPath={returnEmptyDoclist}
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
@@ -272,8 +286,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
removeDocument={undefined}
whenChildContentsActiveChanged={emptyFunction}
addDocTab={this.AddDocTab}
- pinToPres={TabDocView.PinDoc}
- onBrowseClickScript={DocumentView.exploreMode}
+ pinToPres={DocumentView.PinDoc}
focus={emptyFunction}
/>
</GestureOverlay>
@@ -300,6 +313,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
}
interface LightboxTourBtnProps {
navBtn: (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: IconProp, display: any, click: () => void, color?: string) => JSX.Element;
+ // eslint-disable-next-line react/no-unused-prop-types
future: () => Opt<Doc[]>;
stepInto: () => void;
lightboxDoc: () => Opt<Doc>;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 17c21326d..259ffbbc5 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable no-new */
// if ((module as any).hot) {
// (module as any).hot.accept();
// }
@@ -5,16 +6,22 @@
import * as dotenv from 'dotenv'; // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
-import { AssignAllExtensions } from '../../extensions/General/Extensions';
+import { AssignAllExtensions } from '../../extensions/Extensions';
+import { Doc } from '../../fields/Doc';
import { FieldLoader } from '../../fields/FieldLoader';
+import { BranchingTrailManager } from '../util/BranchingTrailManager';
import { CurrentUserUtils } from '../util/CurrentUserUtils';
+import { LinkFollower } from '../util/LinkFollower';
import { PingManager } from '../util/PingManager';
import { ReplayMovements } from '../util/ReplayMovements';
import { TrackMovements } from '../util/TrackMovements';
+import { KeyManager } from './GlobalKeyHandler';
+import { MainView } from './MainView';
import { CollectionView } from './collections/CollectionView';
+import { CollectionFreeFormInfoUI } from './collections/collectionFreeForm/CollectionFreeFormInfoUI';
import './global/globalScripts';
-import { MainView } from './MainView';
-import { BranchingTrailManager } from '../util/BranchingTrailManager';
+import { KeyValueBox } from './nodes/KeyValueBox';
+
dotenv.config();
AssignAllExtensions();
@@ -56,6 +63,12 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' };
new ReplayMovements();
new BranchingTrailManager({});
new PingManager();
+ new KeyManager();
+
+ // iniitialize plugin apis
+ CollectionFreeFormInfoUI.Init();
+ LinkFollower.Init();
+ KeyValueBox.Init();
root.render(<MainView />);
}, 0);
})();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 2fb588e36..637824c31 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable node/no-unpublished-import */
import { library } from '@fortawesome/fontawesome-svg-core';
import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons';
import * as far from '@fortawesome/free-regular-svg-icons';
@@ -6,11 +7,14 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+// eslint-disable-next-line import/no-relative-packages
import '../../../node_modules/browndash-components/dist/styles/global.min.css';
-import { Utils, emptyFunction, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../Utils';
-import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
-import { DocCast, StrCast } from '../../fields/Types';
+import { ClientUtils, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
+import { Doc, DocListCast, GetDocFromUrl, Opt } from '../../fields/Doc';
+import { DocData, DocViews } from '../../fields/DocSymbols';
+import { Id } from '../../fields/FieldSymbols';
+import { DocCast, StrCast, toList } from '../../fields/Types';
import { DocServer } from '../DocServer';
import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
@@ -18,13 +22,14 @@ import { Docs } from '../documents/Documents';
import { CalendarManager } from '../util/CalendarManager';
import { CaptureManager } from '../util/CaptureManager';
import { DocumentManager } from '../util/DocumentManager';
-import { DragManager, dropActionType } from '../util/DragManager';
+import { DragManager } from '../util/DragManager';
+import { dropActionType } from '../util/DropActionTypes';
import { GroupManager } from '../util/GroupManager';
import { HistoryUtil } from '../util/History';
import { Hypothesis } from '../util/HypothesisUtils';
+import { UPDATE_SERVER_CACHE } from '../util/LinkManager';
import { RTFMarkup } from '../util/RTFMarkup';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { SelectionManager } from '../util/SelectionManager';
import { ServerStats } from '../util/ServerStats';
import { SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
@@ -37,7 +42,6 @@ import { DashboardView } from './DashboardView';
import { DictationOverlay } from './DictationOverlay';
import { DocumentDecorations } from './DocumentDecorations';
import { GestureOverlay } from './GestureOverlay';
-import { KeyManager } from './GlobalKeyHandler';
import { LightboxView } from './LightboxView';
import './MainView.scss';
import { ObservableReactComponent } from './ObservableReactComponent';
@@ -50,32 +54,35 @@ import { CollectionDockingView } from './collections/CollectionDockingView';
import { CollectionMenu } from './collections/CollectionMenu';
import { TabDocView } from './collections/TabDocView';
import './collections/TreeView.scss';
+import { CollectionFreeFormView } 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 { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp';
import { DocButtonState } from './nodes/DocumentLinksButton';
-import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from './nodes/DocumentView';
-import { ImageBox, ImageEditorData as ImageEditor } from './nodes/ImageBox';
+import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from './nodes/DocumentView';
+import { ImageEditorData as ImageEditor } from './nodes/ImageBox';
import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview';
import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu';
import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu';
+import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere';
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 { AnchorMenu } from './pdf/AnchorMenu';
import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
import { TopBar } from './topbar/TopBar';
-const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
+
const _global = (window /* browser */ || global) /* node */ as any;
+const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
@observer
export class MainView extends ObservableReactComponent<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: MainView;
public static Live: boolean = false;
private _docBtnRef = React.createRef<HTMLDivElement>();
@@ -117,8 +124,12 @@ export class MainView extends ObservableReactComponent<{}> {
}
@observable mainDoc: Opt<Doc> = undefined;
@computed private get mainContainer() {
- if (window.location.pathname.startsWith('/doc/') && Doc.CurrentUserEmail === 'guest') {
- DocServer.GetRefField(window.location.pathname.substring('/doc/'.length)).then(main => runInAction(() => (this.mainDoc = main as Doc)));
+ if (window.location.pathname.startsWith('/doc/') && ClientUtils.CurrentUserEmail() === 'guest') {
+ DocServer.GetRefField(window.location.pathname.substring('/doc/'.length)).then(main =>
+ runInAction(() => {
+ this.mainDoc = main as Doc;
+ })
+ );
return this.mainDoc;
}
return this.userDoc ? Doc.ActiveDashboard : Doc.GuestDashboard;
@@ -148,15 +159,16 @@ export class MainView extends ObservableReactComponent<{}> {
leftMenuHeight = () => this._dashUIHeight;
leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth;
leftMenuFlyoutHeight = () => this._dashUIHeight;
- propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, SettingsManager.Instance?.propertiesWidth || 0));
+ propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, SnappingManager.PropertiesWidth || 0));
propertiesHeight = () => this._dashUIHeight;
mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth() - this.leftMenuFlyoutWidth();
mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight();
componentDidMount() {
+ // Utils.TraceConsoleLog();
reaction(
// when a multi-selection occurs, remove focus from all active elements to allow keyboad input to go only to global key manager to act upon selection
- () => SelectionManager.Views.slice(),
+ () => DocumentView.Selected().slice(),
views => views.length > 1 && (document.activeElement as any)?.blur !== undefined && (document.activeElement as any)!.blur()
);
const scriptTag = document.createElement('script');
@@ -165,7 +177,11 @@ export class MainView extends ObservableReactComponent<{}> {
scriptTag.async = true;
scriptTag.defer = true;
document.body.appendChild(scriptTag);
- document.getElementById('root')?.addEventListener('scroll', e => (ele => (ele.scrollLeft = ele.scrollTop = 0))(document.getElementById('root')!));
+ document.getElementById('root')?.addEventListener('scroll', () =>
+ (ele => {
+ ele.scrollLeft = ele.scrollTop = 0;
+ })(document.getElementById('root')!)
+ );
const ele = document.getElementById('loader');
const prog = document.getElementById('dash-progress');
if (ele && prog) {
@@ -174,7 +190,9 @@ export class MainView extends ObservableReactComponent<{}> {
prog.style.transition = '1s';
prog.style.width = '100%';
}, 0);
- setTimeout(() => (ele.outerHTML = ''), 1000);
+ setTimeout(() => {
+ ele.outerHTML = '';
+ }, 1000);
}
this._sidebarContent.proto = undefined;
if (!MainView.Live) {
@@ -202,7 +220,7 @@ export class MainView extends ObservableReactComponent<{}> {
'text_scrollHeight',
'text_height',
'hidden',
- //'type_collection',
+ // 'type_collection',
'chromeHidden',
'currentFrame',
]); // can play with these fields on someone else's
@@ -212,37 +230,29 @@ export class MainView extends ObservableReactComponent<{}> {
tag.src = 'https://www.youtube.com/iframe_api';
const firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
- window.removeEventListener('keydown', KeyManager.Instance.handleModifiers, true);
- window.addEventListener('keydown', KeyManager.Instance.handleModifiers, true);
- window.removeEventListener('keyup', KeyManager.Instance.unhandleModifiers);
- window.addEventListener('keyup', KeyManager.Instance.unhandleModifiers);
- window.removeEventListener('keydown', KeyManager.Instance.handle);
- window.addEventListener('keydown', KeyManager.Instance.handle);
- window.removeEventListener('keyup', KeyManager.Instance.unhandle);
- window.addEventListener('keyup', KeyManager.Instance.unhandle);
- window.addEventListener('paste', KeyManager.Instance.paste as any);
document.addEventListener('dash', (e: any) => {
// event used by chrome plugin to tell Dash which document to focus on
- const id = FormattedTextBox.GetDocFromUrl(e.detail);
- DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentManager.Instance.showDocument(doc, { willPan: false }) : null));
+ const id = GetDocFromUrl(e.detail);
+ DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentView.showDocument(doc, { willPan: false }) : null));
});
document.addEventListener('linkAnnotationToDash', Hypothesis.linkListener);
this.initEventListeners();
}
componentWillUnMount() {
- window.removeEventListener('keyup', KeyManager.Instance.unhandle);
- window.removeEventListener('keydown', KeyManager.Instance.handle);
- window.removeEventListener('pointerdown', this.globalPointerDown, true);
- window.removeEventListener('pointermove', this.globalPointerMove, true);
- window.removeEventListener('pointerup', this.globalPointerClick, true);
- window.removeEventListener('paste', KeyManager.Instance.paste as any);
- document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener);
+ // window.removeEventListener('keyup', KeyManager.Instance.unhandle);
+ // window.removeEventListener('keydown', KeyManager.Instance.handle);
+ // window.removeEventListener('pointerdown', this.globalPointerDown, true);
+ // window.removeEventListener('pointermove', this.globalPointerMove, true);
+ // window.removeEventListener('pointerup', this.globalPointerClick, true);
+ // window.removeEventListener('paste', KeyManager.Instance.paste as any);
+ // document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener);
}
constructor(props: any) {
super(props);
makeObservable(this);
+ CollectionDockingView.setTabJSXComponent(TabDocView);
DocumentViewInternal.addDocTabFunc = MainView.addDocTabFunc_impl;
MainView.Instance = this;
DashboardView._urlState = HistoryUtil.parseUrl(window.location) || ({} as any);
@@ -531,7 +541,7 @@ export class MainView extends ObservableReactComponent<{}> {
}
private longPressTimer: NodeJS.Timeout | undefined;
- globalPointerClick = action((e: any) => {
+ globalPointerClick = action(() => {
this.longPressTimer && clearTimeout(this.longPressTimer);
DocumentView.LongPress = false;
});
@@ -541,7 +551,9 @@ export class MainView extends ObservableReactComponent<{}> {
globalPointerDown = action((e: PointerEvent) => {
DocumentView.LongPress = false;
this.longPressTimer = setTimeout(
- action(() => (DocumentView.LongPress = true)),
+ action(() => {
+ DocumentView.LongPress = true;
+ }),
1000
);
DocumentManager.removeOverlayViews();
@@ -560,7 +572,7 @@ export class MainView extends ObservableReactComponent<{}> {
});
initEventListeners = () => {
- window.addEventListener('beforeunload', DocServer.UPDATE_SERVER_CACHE);
+ window.addEventListener('beforeunload', UPDATE_SERVER_CACHE);
window.addEventListener('drop', e => e.preventDefault(), false); // prevent default behavior of navigating to a new web page
window.addEventListener('dragover', e => e.preventDefault(), false);
document.addEventListener('pointerdown', this.globalPointerDown, true);
@@ -572,7 +584,7 @@ export class MainView extends ObservableReactComponent<{}> {
if (!e.cancelBubble) {
const pathstr = (e as any)?.path?.map((p: any) => p.classList?.toString()).join();
if (pathstr?.includes('libraryFlyout')) {
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
}
}
},
@@ -608,8 +620,8 @@ export class MainView extends ObservableReactComponent<{}> {
waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined);
headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1);
mainScreenToLocalXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.topOfMainDocContent, 1);
- addHeaderDoc = (doc: Doc | Doc[], annotationKey?: string) => (doc instanceof Doc ? [doc] : doc).reduce((done, doc) => Doc.AddDocToList(this.headerBarDoc, 'data', doc), true);
- removeHeaderDoc = (doc: Doc | Doc[], annotationKey?: string) => (doc instanceof Doc ? [doc] : doc).reduce((done, doc) => Doc.RemoveDocFromList(this.headerBarDoc, 'data', doc), true);
+ addHeaderDoc = (docs: Doc | Doc[]) => toList(docs).reduce((done, doc) => Doc.AddDocToList(this.headerBarDoc, 'data', doc), true);
+ removeHeaderDoc = (docs: Doc | Doc[]) => toList(docs).reduce((done, doc) => Doc.RemoveDocFromList(this.headerBarDoc, 'data', doc), true);
@computed get headerBarDocView() {
return (
<div className="mainView-headerBar" style={{ height: this.headerBarDocHeight() }}>
@@ -626,10 +638,10 @@ export class MainView extends ObservableReactComponent<{}> {
isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events)
isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive
ScreenToLocalTransform={this.headerBarScreenXf}
- childHideResizeHandles={true}
+ childHideResizeHandles
childDragAction={dropActionType.move}
- dontRegisterView={true}
- hideResizeHandles={true}
+ dontRegisterView
+ hideResizeHandles
PanelWidth={this.headerBarDocWidth}
PanelHeight={this.headerBarDocHeight}
renderDepth={0}
@@ -665,7 +677,7 @@ export class MainView extends ObservableReactComponent<{}> {
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
- suppressSetHeight={true}
+ suppressSetHeight
renderDepth={this._hideUI ? 0 : -1}
/>
</>
@@ -674,7 +686,7 @@ export class MainView extends ObservableReactComponent<{}> {
@computed get dockingContent() {
return (
- <GestureOverlay isActive={LightboxView.LightboxDoc ? false : true}>
+ <GestureOverlay isActive={!LightboxView.LightboxDoc}>
<div
key="docking"
className={`mainView-dockingContent${this._leftMenuFlyoutWidth ? '-flyout' : ''}`}
@@ -698,9 +710,16 @@ export class MainView extends ObservableReactComponent<{}> {
setupMoveUpEvents(
this,
e,
- action(e => ((SettingsManager.Instance.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false)),
- action(() => SettingsManager.Instance.propertiesWidth < 5 && (SettingsManager.Instance.propertiesWidth = 0)),
- action(() => (SettingsManager.Instance.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0)),
+ action(() => {
+ SnappingManager.SetPropertiesWidth(Math.max(0, this._dashUIWidth - e.clientX));
+ return !SnappingManager.PropertiesWidth;
+ }),
+ action(() => {
+ SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0);
+ }),
+ action(() => {
+ SnappingManager.SetPropertiesWidth(this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0);
+ }),
false
);
};
@@ -710,7 +729,10 @@ export class MainView extends ObservableReactComponent<{}> {
setupMoveUpEvents(
this,
e,
- action(e => ((this._leftMenuFlyoutWidth = Math.max(e.clientX - 58, 0)) ? false : false)),
+ action(ev => {
+ this._leftMenuFlyoutWidth = Math.max(ev.clientX - 58, 0);
+ return false;
+ }),
() => this._leftMenuFlyoutWidth < 5 && this.closeFlyout(),
this.closeFlyout
);
@@ -718,7 +740,8 @@ export class MainView extends ObservableReactComponent<{}> {
sidebarScreenToLocal = () => new Transform(0, -this.topOfSidebarDoc, 1);
mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0);
- static addDocTabFunc_impl = (doc: Doc, location: OpenWhere): boolean => {
+ static addDocTabFunc_impl = (docs: Doc | Doc[], location: OpenWhere): boolean => {
+ const doc = toList(docs).lastElement();
const whereFields = location.split(':');
const keyValue = whereFields.includes(OpenWhereMod.keyvalue);
const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none;
@@ -735,7 +758,7 @@ export class MainView extends ObservableReactComponent<{}> {
@computed get flyout() {
return !this._leftMenuFlyoutWidth ? (
- <div key="flyout" className={`mainView-libraryFlyout-out`}>
+ <div key="flyout" className="mainView-libraryFlyout-out">
{this.docButtons}
</div>
) : (
@@ -745,7 +768,7 @@ export class MainView extends ObservableReactComponent<{}> {
Document={this._sidebarContent.proto || this._sidebarContent}
addDocument={undefined}
addDocTab={DocumentViewInternal.addDocTabFunc}
- pinToPres={TabDocView.PinDoc}
+ pinToPres={DocumentView.PinDoc}
containerViewPath={returnEmptyDoclist}
styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem || this._sidebarContent.proto === Doc.MyTrails ? DashboardStyleProvider : DefaultStyleProvider}
removeDocument={returnFalse}
@@ -768,7 +791,7 @@ export class MainView extends ObservableReactComponent<{}> {
@computed get leftMenuPanel() {
return (
- <div key="menu" className="mainView-leftMenuPanel" style={{ background: SettingsManager.userBackgroundColor, display: LightboxView.LightboxDoc ? 'none' : undefined }}>
+ <div key="menu" className="mainView-leftMenuPanel" style={{ background: SnappingManager.userBackgroundColor, display: LightboxView.LightboxDoc ? 'none' : undefined }}>
<DocumentView
Document={Doc.MyLeftSidebarMenu}
addDocument={undefined}
@@ -800,7 +823,7 @@ export class MainView extends ObservableReactComponent<{}> {
if (willOpen) {
switch ((this._panelContent = title)) {
case 'Settings':
- SettingsManager.Instance.open();
+ SettingsManager.Instance.openMgr();
break;
case 'Help':
break;
@@ -821,9 +844,9 @@ export class MainView extends ObservableReactComponent<{}> {
{this.flyout}
<div
className="mainView-libraryHandle"
- style={{ background: SettingsManager.userBackgroundColor, left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? 'none' : undefined }}
+ style={{ background: SnappingManager.userBackgroundColor, left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? 'none' : undefined }}
onPointerDown={this.onFlyoutPointerDown}>
- <FontAwesomeIcon icon="chevron-left" color={SettingsManager.userColor} style={{ opacity: '50%' }} size="sm" />
+ <FontAwesomeIcon icon="chevron-left" color={SnappingManager.userColor} style={{ opacity: '50%' }} size="sm" />
</div>
<div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)` }}>
{this.dockingContent}
@@ -833,16 +856,14 @@ export class MainView extends ObservableReactComponent<{}> {
className={`mainView-propertiesDragger${this.propertiesWidth() < 10 ? '-minified' : ''}`}
key="props"
onPointerDown={this.onPropertiesPointerDown}
- style={{ background: SettingsManager.userVariantColor, right: this.propertiesWidth() - 1 }}>
- <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={SettingsManager.userColor} size="sm" />
+ style={{ background: SnappingManager.userVariantColor, right: this.propertiesWidth() - 1 }}>
+ <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={SnappingManager.userColor} size="sm" />
</div>
)}
- <div className="properties-container" style={{ width: this.propertiesWidth(), color: SettingsManager.userColor }}>
- {
- <div style={{ display: this.propertiesWidth() < 10 ? 'none' : undefined }}>
- <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={DocumentViewInternal.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />
- </div>
- }
+ <div className="properties-container" style={{ width: this.propertiesWidth(), color: SnappingManager.userColor }}>
+ <div style={{ display: this.propertiesWidth() < 10 ? 'none' : undefined }}>
+ <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={DocumentViewInternal.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />
+ </div>
</div>
</div>
</div>
@@ -879,32 +900,32 @@ export class MainView extends ObservableReactComponent<{}> {
// generate the wrong value from getClientRectangle() -- specifically they return an 'x' that is the flyout's width greater than it should be.
// interactively adjusting the flyout fixes the problem. So does programmatically changing the value after a timeout to something *fractionally* different (ie, 1.5, not 1);)
this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250;
- //setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5)));
+ // setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5)));
this._sidebarContent.proto = DocCast(button.target);
- SettingsManager.Instance.SetLastPressedBtn(button);
+ SnappingManager.SetLastPressedBtn(button[Id]);
});
closeFlyout = action(() => {
- SettingsManager.Instance.SetLastPressedBtn(undefined);
+ SnappingManager.SetLastPressedBtn('');
this._panelContent = 'none';
this._sidebarContent.proto = undefined;
this._leftMenuFlyoutWidth = 0;
});
- remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && !doc.dragOnlyWithinContainer && Doc.RemoveDocFromList(Doc.MyDockedBtns, 'data', doc), true);
- moveButtonDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(doc) && addDocument(doc);
- addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.MyDockedBtns, 'data', doc), true);
+ remButtonDoc = (docs: Doc | Doc[]) => toList(docs).reduce((flg: boolean, doc) => flg && !doc.dragOnlyWithinContainer && Doc.RemoveDocFromList(Doc.MyDockedBtns, 'data', doc), true);
+ moveButtonDoc = (docs: Doc | Doc[], targetCol: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(docs) && addDocument(docs);
+ addButtonDoc = (docs: Doc | Doc[]) => toList(docs).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.MyDockedBtns, 'data', doc), true);
buttonBarXf = () => {
if (!this._docBtnRef.current) return Transform.Identity();
- const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current);
+ const { scale, translateX, translateY } = ClientUtils.GetScreenTransform(this._docBtnRef.current);
return new Transform(-translateX, -translateY, 1 / scale);
};
@computed get docButtons() {
return !Doc.MyDockedBtns ? null : (
- <div className="mainView-docButtons" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }} ref={this._docBtnRef}>
+ <div className="mainView-docButtons" style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor }} ref={this._docBtnRef}>
<CollectionLinearView
Document={Doc.MyDockedBtns}
docViewPath={returnEmptyDocViewList}
@@ -930,22 +951,26 @@ export class MainView extends ObservableReactComponent<{}> {
childFiltersByRanges={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
/>
- {['watching', 'recording'].includes(StrCast(this.userDoc?.presentationMode)) ? <div style={{ border: '.5rem solid green', padding: '5px' }}>{StrCast(this.userDoc?.presentationMode)}</div> : <></>}
+ {['watching', 'recording'].includes(StrCast(this.userDoc?.presentationMode)) ? <div style={{ border: '.5rem solid green', padding: '5px' }}>{StrCast(this.userDoc?.presentationMode)}</div> : null}
</div>
);
}
@computed get snapLines() {
- const dragged = DragManager.docsBeingDragged.lastElement() ?? SelectionManager.Docs.lastElement();
- const dragPar = dragged ? DocumentManager.Instance.getDocumentView(dragged)?.CollectionFreeFormView : undefined;
+ const dragged = DragManager.docsBeingDragged.lastElement() ?? DocumentView.SelectedDocs().lastElement();
+ const dragPar = dragged ? CollectionFreeFormView.from(Array.from(dragged[DocViews]).lastElement()) : undefined;
return !dragPar?.layoutDoc.freeform_snapLines ? null : (
<div className="mainView-snapLines">
<svg style={{ width: '100%', height: '100%' }}>
- {SnappingManager.HorizSnapLines.map((l, i) => (
- <line key={i} x1="0" y1={l} x2="2000" y2={l} stroke={lightOrDark(dragPar.layoutDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray={'2 2'} />
- ))}
- {SnappingManager.VertSnapLines.map((l, i) => (
- <line key={i} y1={this.topOfMainDocContent.toString()} x1={l} y2="2000" x2={l} stroke={lightOrDark(dragPar.layoutDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray={'2 2'} />
- ))}
+ {[
+ ...SnappingManager.HorizSnapLines.map((l, i) => (
+ // eslint-disable-next-line react/no-array-index-key
+ <line key={'horiz' + i} x1="0" y1={l} x2="2000" y2={l} stroke={lightOrDark(dragPar.layoutDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray="2 2" />
+ )),
+ ...SnappingManager.VertSnapLines.map((l, i) => (
+ // eslint-disable-next-line react/no-array-index-key
+ <line key={'vert' + i} y1={this.topOfMainDocContent.toString()} x1={l} y2="2000" x2={l} stroke={lightOrDark(dragPar.layoutDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray="2 2" />
+ )),
+ ]}
</svg>
</div>
);
@@ -962,13 +987,14 @@ export class MainView extends ObservableReactComponent<{}> {
values="1 0 0 0 0
0 0 0 0 0
0 0 0 0 0
- 0 0 0 1 0"></feColorMatrix>
- <feGaussianBlur in="color" stdDeviation="4" result="blur"></feGaussianBlur>
- <feOffset in="blur" dx="0" dy="0" result="offset"></feOffset>
+ 0 0 0 1 0"
+ />
+ <feGaussianBlur in="color" stdDeviation="4" result="blur" />
+ <feOffset in="blur" dx="0" dy="0" result="offset" />
<feMerge>
- <feMergeNode in="bg"></feMergeNode>
- <feMergeNode in="offset"></feMergeNode>
- <feMergeNode in="SourceGraphic"></feMergeNode>
+ <feMergeNode in="bg" />
+ <feMergeNode in="offset" />
+ <feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
@@ -976,16 +1002,28 @@ export class MainView extends ObservableReactComponent<{}> {
);
}
+ togglePropertiesFlyout = () => {
+ if (MainView.Instance.propertiesWidth() > 0) {
+ SnappingManager.SetPropertiesWidth(0);
+ } else {
+ SnappingManager.SetPropertiesWidth(300);
+ }
+ };
+
lightboxMaxBorder = [200, 50];
render() {
return (
<div
className="mainView-container"
style={{
- color: SettingsManager.userColor,
- background: SettingsManager.userBackgroundColor,
+ color: SnappingManager.userColor,
+ background: SnappingManager.userBackgroundColor,
}}
- onScroll={() => (ele => (ele.scrollTop = ele.scrollLeft = 0))(document.getElementById('root')!)}
+ onScroll={() =>
+ (ele => {
+ ele.scrollTop = ele.scrollLeft = 0;
+ })(document.getElementById('root')!)
+ }
ref={r => {
r &&
new _global.ResizeObserver(
@@ -1010,23 +1048,31 @@ export class MainView extends ObservableReactComponent<{}> {
<ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
{this._hideUI ? null : <TopBar />}
<LinkDescriptionPopup />
- {DocButtonState.Instance.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocButtonState.Instance.LinkEditorDocView = undefined))} docView={DocButtonState.Instance.LinkEditorDocView} /> : null}
- {LinkInfo.Instance?.LinkInfo ? <LinkDocPreview {...LinkInfo.Instance.LinkInfo} /> : null}
-
+ {DocButtonState.Instance.LinkEditorDocView ? (
+ <LinkMenu
+ clearLinkEditor={action(() => {
+ DocButtonState.Instance.LinkEditorDocView = undefined;
+ })}
+ docView={DocButtonState.Instance.LinkEditorDocView}
+ />
+ ) : null}
+ {LinkInfo.Instance?.LinkInfo ? (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ <LinkDocPreview {...LinkInfo.Instance.LinkInfo} />
+ ) : null}
{((page: string) => {
// prettier-ignore
switch (page) {
- default:
- case 'dashboard': return (<>
+ case 'home': return <DashboardView />;
+ case 'dashboard':
+ default: return (<>
<div key="dashdiv" style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 2001 }}>
- <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} toggleTopBar={this.toggleTopBar} topBarHeight={this.headerBarHeightFunc}/>
+ <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} togglePropertiesFlyout={this.togglePropertiesFlyout} toggleTopBar={this.toggleTopBar} topBarHeight={this.headerBarHeightFunc}/>
</div>
{this.mainDashboardArea}
</> );
- case 'home': return <DashboardView />;
}
})(Doc.ActivePage)}
-
<PreviewCursor />
<TaskCompletionBox />
<ContextMenu />
@@ -1040,7 +1086,7 @@ export class MainView extends ObservableReactComponent<{}> {
{/* <InkTranscription /> */}
{this.snapLines}
<LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={this.lightboxMaxBorder} />
- <OverlayView />
+ {LightboxView.LightboxDoc ? null : <OverlayView />}
<GPTPopup key="gptpopup" />
<SchemaCSVPopUp key="schemacsvpopup" />
<GenerativeFill imageEditorOpen={ImageEditor.Open} imageEditorSource={ImageEditor.Source} imageRootDoc={ImageEditor.RootDoc} addDoc={ImageEditor.AddDoc} />
@@ -1050,15 +1096,19 @@ export class MainView extends ObservableReactComponent<{}> {
}
}
-ScriptingGlobals.add(function selectMainMenu(doc: Doc, title: string) {
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function selectMainMenu(doc: Doc) {
MainView.Instance.selectMenu(doc);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function createNewPresentation() {
return MainView.Instance.createNewPresentation();
}, 'creates a new presentation when called');
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function openPresentation(pres: Doc) {
return MainView.Instance.openPresentation(pres);
}, 'creates a new presentation when called');
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function createNewFolder() {
return MainView.Instance.createNewFolder();
}, 'creates a new folder in myFiles when called');
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index af7f38937..a6dc5c62b 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -1,7 +1,10 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/require-default-props */
import { isDark } from 'browndash-components';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { SettingsManager } from '../util/SettingsManager';
+import { SnappingManager } from '../util/SnappingManager';
import './MainViewModal.scss';
export interface MainViewOverlayProps {
@@ -11,7 +14,6 @@ export interface MainViewOverlayProps {
dialogueBoxStyle?: React.CSSProperties;
overlayStyle?: React.CSSProperties;
dialogueBoxDisplayedOpacity?: number;
- overlayDisplayedOpacity?: number;
closeOnExternalClick?: () => void; // the close method of a MainViewModal, triggered if there is a click on the overlay (closing the modal)
}
@@ -20,7 +22,6 @@ export class MainViewModal extends React.Component<MainViewOverlayProps> {
render() {
const p = this.props;
const dialogueOpacity = p.dialogueBoxDisplayedOpacity || 1;
- const overlayOpacity = p.overlayDisplayedOpacity || 0.4;
return !p.isDisplayed ? null : (
<div
className="mainViewModal-cont"
@@ -43,7 +44,7 @@ export class MainViewModal extends React.Component<MainViewOverlayProps> {
className="overlay"
onClick={this.props?.closeOnExternalClick}
style={{
- backgroundColor: isDark(SettingsManager.userColor) ? '#DFDFDF30' : '#32323230',
+ backgroundColor: isDark(SnappingManager.userColor) ? '#DFDFDF30' : '#32323230',
...(p.overlayStyle || {}),
}}
/>
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 9fa20f642..a917a7d57 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -7,16 +7,14 @@ import { List } from '../../fields/List';
import { NumCast } from '../../fields/Types';
import { GetEffectiveAcl } from '../../fields/util';
import { unimplementedFunction, Utils } from '../../Utils';
-import { Docs, DocUtils } from '../documents/Documents';
+import { Docs } from '../documents/Documents';
+import { DocUtils, FollowLinkScript } from '../documents/DocUtils';
import { DragManager } from '../util/DragManager';
-import { FollowLinkScript } from '../util/LinkFollower';
import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
import './MarqueeAnnotator.scss';
import { DocumentView } from './nodes/DocumentView';
-import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import { AnchorMenu } from './pdf/AnchorMenu';
import { ObservableReactComponent } from './ObservableReactComponent';
-const _global = (window /* browser */ || global) /* node */ as any;
+import { AnchorMenu } from './pdf/AnchorMenu';
export interface MarqueeAnnotatorProps {
Document: Doc;
@@ -96,42 +94,41 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
presentation_zoomText: true,
title: '>' + this.props.Document.title,
});
+ const textRegionAnnoProto = textRegionAnno[DocData];
let minX = Number.MAX_VALUE;
let maxX = -Number.MAX_VALUE;
let minY = Number.MAX_VALUE;
let maxY = -Number.MIN_VALUE;
- const annoDocs: Doc[] = [];
- savedAnnoMap.forEach((value: HTMLDivElement[], key: number) =>
- value.map(anno => {
- const textRegion = new Doc();
- textRegion.x = parseInt(anno.style.left ?? '0');
- textRegion.y = parseInt(anno.style.top ?? '0');
- textRegion._height = parseInt(anno.style.height ?? '0');
- textRegion._width = parseInt(anno.style.width ?? '0');
- textRegion.annoTextRegion = textRegionAnno;
- textRegion.backgroundColor = color;
- annoDocs.push(textRegion);
+ const annoRects: string[] = [];
+ savedAnnoMap.forEach((value: HTMLDivElement[]) =>
+ value.forEach(anno => {
+ const x = parseInt(anno.style.left ?? '0');
+ const y = parseInt(anno.style.top ?? '0');
+ const height = parseInt(anno.style.height ?? '0');
+ const width = parseInt(anno.style.width ?? '0');
+ annoRects.push(`${x}:${y}:${width}:${height}`);
anno.remove();
- minY = Math.min(NumCast(textRegion.y), minY);
- minX = Math.min(NumCast(textRegion.x), minX);
- maxY = Math.max(NumCast(textRegion.y) + NumCast(textRegion._height), maxY);
- maxX = Math.max(NumCast(textRegion.x) + NumCast(textRegion._width), maxX);
+ minY = Math.min(NumCast(y), minY);
+ minX = Math.min(NumCast(x), minX);
+ maxY = Math.max(NumCast(y) + NumCast(height), maxY);
+ maxX = Math.max(NumCast(x) + NumCast(width), maxX);
})
);
- const textRegionAnnoProto = textRegionAnno[DocData];
textRegionAnnoProto.y = Math.max(minY, 0);
textRegionAnnoProto.x = Math.max(minX, 0);
textRegionAnnoProto.height = Math.max(maxY, 0) - Math.max(minY, 0);
textRegionAnnoProto.width = Math.max(maxX, 0) - Math.max(minX, 0);
+ textRegionAnnoProto.backgroundColor = color;
// mainAnnoDocProto.text = this._selectionText;
- textRegionAnnoProto.text_inlineAnnotations = new List<Doc>(annoDocs);
+ textRegionAnnoProto.text_inlineAnnotations = new List<string>(annoRects);
+ textRegionAnnoProto.opacity = 0;
textRegionAnnoProto.layout_unrendered = true;
savedAnnoMap.clear();
return textRegionAnno;
};
@action
- highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean, summarize?: boolean) => {
+ highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => {
// creates annotation documents for current highlights
const effectiveAcl = GetEffectiveAcl(this.props.Document[DocData]);
const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
@@ -157,7 +154,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
// 3) localize the unrotated vector by scaling into the marquee container's coordinates
// 4) reattach the vector to the center of the bounding box
getTransformedScreenPt = (down: number[]) => {
- const marqueeContainer = this.props.marqueeContainer;
+ const { marqueeContainer } = this.props;
const containerXf = this.props.isNativeScaled ? this.props.docView().screenToContentsTransform() : this.props.docView().screenToViewTransform();
const boundingRect = marqueeContainer.getBoundingClientRect();
const center = { x: boundingRect.x + boundingRect.width / 2, y: boundingRect.y + boundingRect.height / 2 };
@@ -177,15 +174,15 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
document.addEventListener('pointermove', this.onSelectMove);
document.addEventListener('pointerup', this.onSelectEnd);
- AnchorMenu.Instance.OnCrop = (e: PointerEvent) => {
+ AnchorMenu.Instance.OnCrop = () => {
if (this.props.anchorMenuCrop) {
UndoManager.RunInBatch(() => this.props.anchorMenuCrop?.(this.highlight('', true, undefined, false), true), 'cropping');
}
};
- AnchorMenu.Instance.OnClick = undoable((e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true)), 'make sidebar annotation');
+ AnchorMenu.Instance.OnClick = undoable(() => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true)), 'make sidebar annotation');
AnchorMenu.Instance.OnAudio = unimplementedFunction;
AnchorMenu.Instance.Highlight = (color: string) => this.highlight(color, false, undefined, true);
- AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true);
+ AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]> /* , addAsAnnotation?: boolean */) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true);
AnchorMenu.Instance.onMakeAnchor = () => AnchorMenu.Instance.GetAnchor(undefined, true);
/**
@@ -199,14 +196,14 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
const targetCreator = (annotationOn: Doc | undefined) => {
const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
- FormattedTextBox.SetSelectOnLoad(target);
+ Doc.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.Document;
- e.annoDragData.linkSourceDoc.followLinkZoom = false;
+ dragComplete: dragEv => {
+ if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) {
+ dragEv.annoDragData.linkSourceDoc.followLinkToggle = dragEv.annoDragData.dropDocument.annotationOn === this.props.Document;
+ dragEv.annoDragData.linkSourceDoc.followLinkZoom = false;
}
},
});
@@ -220,16 +217,16 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
: action((e: PointerEvent, ele: HTMLElement) => {
e.preventDefault();
e.stopPropagation();
- var cropRegion: Doc | undefined;
+ let cropRegion: Doc | undefined;
+ // eslint-disable-next-line no-return-assign
const sourceAnchorCreator = () => (cropRegion = this.highlight('', true, undefined, true)); // hyperlink color
- const targetCreator = (annotationOn: Doc | undefined) => this.props.anchorMenuCrop!(cropRegion, false)!;
+ const targetCreator = (/* annotationOn: Doc | undefined */) => this.props.anchorMenuCrop!(cropRegion, false)!;
DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
- dragComplete: e => {
- if (!e.aborted && e.linkDocument) {
- const linkDocData = e.linkDocument[DocData];
+ dragComplete: dragEx => {
+ if (!dragEx.aborted && dragEx.linkDocument) {
+ const linkDocData = dragEx.linkDocument[DocData];
linkDocData.link_relationship = 'cropped image';
linkDocData.title = 'crop: ' + this.props.Document.title;
- linkDocData.link_displayLine = false;
}
},
});
@@ -252,7 +249,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
const movLoc = this.getTransformedScreenPt([e.clientX, e.clientY]);
this._width = movLoc.x - this._start.x;
this._height = movLoc.y - this._start.y;
- //e.stopPropagation(); // overlay documents are all 'active', yet they can be dragged. if we stop propagation, then they can be marqueed but not dragged. if we don't stop, then they will be marqueed and dragged, but the marquee will be zero width since the doc will move along with the cursor.
+ // e.stopPropagation(); // overlay documents are all 'active', yet they can be dragged. if we stop propagation, then they can be marqueed but not dragged. if we don't stop, then they will be marqueed and dragged, but the marquee will be zero width since the doc will move along with the cursor.
};
onSelectEnd = (e: PointerEvent) => {
@@ -268,7 +265,9 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
// copy the temporary marquee to allow for multiple selections (not currently available though).
const copy = document.createElement('div');
const scale = (this.props.scaling?.() || 1) * NumCast(this.props.Document._freeform_scale, 1);
- ['border', 'opacity', 'top', 'left', 'width', 'height'].forEach(prop => (copy.style[prop as any] = marqueeStyle[prop as any]));
+ ['border', 'opacity', 'top', 'left', 'width', 'height'].forEach(prop => {
+ copy.style[prop as any] = marqueeStyle[prop as any];
+ });
copy.className = 'marqueeAnnotator-annotationBox';
copy.style.top = parseInt(marqueeStyle.top.toString().replace('px', '')) / scale + this.props.scrollTop + 'px';
copy.style.left = parseInt(marqueeStyle.left.toString().replace('px', '')) / scale + 'px';
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
deleted file mode 100644
index 89c3c41f8..000000000
--- a/src/client/views/MetadataEntryMenu.tsx
+++ /dev/null
@@ -1,197 +0,0 @@
-import { IReactionDisposer, action, observable, reaction, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import * as Autosuggest from 'react-autosuggest';
-import { emptyFunction, emptyPath } from '../../Utils';
-import { Doc, DocListCast, Field } from '../../fields/Doc';
-import { undoBatch } from '../util/UndoManager';
-import './MetadataEntryMenu.scss';
-import { KeyValueBox } from './nodes/KeyValueBox';
-
-export type DocLike = Doc | Doc[] | Promise<Doc> | Promise<Doc[]>;
-export interface MetadataEntryProps {
- docs: Doc[];
- onError?: () => boolean;
- suggestWithFunction?: boolean;
-}
-
-@observer
-export class MetadataEntryMenu extends React.Component<MetadataEntryProps> {
- @observable private _currentKey: string = '';
- @observable private _currentValue: string = '';
- private _addChildren: boolean = false;
- @observable _allSuggestions: string[] = [];
- _suggestionDispser: IReactionDisposer | undefined;
- private userModified = false;
-
- private autosuggestRef = React.createRef<Autosuggest>();
-
- @action
- onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
- this._currentKey = newValue;
- if (!this.userModified) {
- this.previewValue();
- }
- };
-
- previewValue = async () => {
- let field: Field | undefined | null = null;
- let onProto: boolean = false;
- let value: string | undefined = undefined;
- const docs = this.props.docs;
- for (const doc of docs) {
- const v = await doc[this._currentKey];
- onProto = onProto || !Object.keys(doc).includes(this._currentKey);
- if (field === null) {
- field = v;
- } else if (v !== field) {
- value = 'multiple values';
- }
- }
- if (value === undefined) {
- if (field !== null && field !== undefined) {
- value = (onProto ? '' : '= ') + Field.toScriptString(field);
- } else {
- value = '';
- }
- }
- const s = value;
- runInAction(() => (this._currentValue = s));
- };
-
- @action
- onValueChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._currentValue = e.target.value;
- this.userModified = e.target.value.trim() !== '';
- };
-
- @undoBatch
- @action
- onValueKeyDown = async (e: React.KeyboardEvent) => {
- if (e.key === 'Enter') {
- e.stopPropagation();
- const script = KeyValueBox.CompileKVPScript(this._currentValue);
- if (!script) return;
-
- let childSuccess = true;
- if (this._addChildren) {
- for (const document of this.props.docs) {
- const collectionChildren = DocListCast(document.data);
- if (collectionChildren) {
- childSuccess = collectionChildren.every(c => KeyValueBox.ApplyKVPScript(c, this._currentKey, script));
- }
- }
- }
- const success = this.props.docs.every(d => KeyValueBox.ApplyKVPScript(d, this._currentKey, script)) && childSuccess;
- if (!success) {
- if (this.props.onError) {
- if (this.props.onError()) {
- this.clearInputs();
- }
- } else {
- this.clearInputs();
- }
- } else {
- this.clearInputs();
- }
- }
- };
-
- @action
- clearInputs = () => {
- this._currentKey = '';
- this._currentValue = '';
- this.userModified = false;
- if (this.autosuggestRef.current) {
- const input: HTMLInputElement = (this.autosuggestRef.current as any).input;
- input && input.focus();
- }
- };
-
- getKeySuggestions = (value: string) => {
- value = value.toLowerCase();
- const docs = this.props.docs;
- const keys = new Set<string>();
- docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
- return Array.from(keys).filter(key => key.toLowerCase().startsWith(value));
- };
- getSuggestionValue = (suggestion: string) => suggestion;
-
- renderSuggestion = (suggestion: string) => {
- return null;
- };
- componentDidMount() {
- this._suggestionDispser = reaction(
- () => this._currentKey,
- () => (this._allSuggestions = this.getKeySuggestions(this._currentKey)),
- { fireImmediately: true }
- );
- }
- componentWillUnmount() {
- this._suggestionDispser && this._suggestionDispser();
- }
-
- onClick = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._addChildren = !this._addChildren;
- };
-
- private get considerChildOptions() {
- if (!this.props.docs.every(doc => doc._type_collection !== undefined)) {
- return null;
- }
- return (
- <div style={{ display: 'flex' }}>
- Children:
- <input type="checkbox" onChange={this.onClick}></input>
- </div>
- );
- }
-
- _ref = React.createRef<HTMLInputElement>();
- render() {
- return (
- <div className="metadataEntry-outerDiv" id="metadataEntry-outer" onPointerDown={e => e.stopPropagation()}>
- <div className="metadataEntry-inputArea">
- <div style={{ display: 'flex', flexDirection: 'row' }}>
- <span>Key:</span>
- <div className="metadataEntry-autoSuggester" onClick={e => this.autosuggestRef.current!.input?.focus()}>
- <Autosuggest
- // @ts-ignore
- inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
- getSuggestionValue={this.getSuggestionValue}
- suggestions={emptyPath}
- alwaysRenderSuggestions={false}
- renderSuggestion={this.renderSuggestion}
- onSuggestionsFetchRequested={emptyFunction}
- onSuggestionsClearRequested={emptyFunction}
- ref={this.autosuggestRef}
- />
- </div>
- </div>
- <div style={{ display: 'flex', flexDirection: 'row' }}>
- <span>Value:</span>
- <input className="metadataEntry-input" ref={this._ref} value={this._currentValue} onClick={e => this._ref.current!.focus()} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} />
- </div>
- {this.considerChildOptions}
- </div>
- <div className="metadataEntry-keys">
- <ul>
- {this._allSuggestions
- .slice()
- .sort()
- .map(s => (
- <li
- key={s}
- onClick={action(() => {
- this._currentKey = s;
- this.previewValue();
- })}>
- {s}
- </li>
- ))}
- </ul>
- </div>
- </div>
- );
- }
-}
diff --git a/src/client/views/ObservableReactComponent.tsx b/src/client/views/ObservableReactComponent.tsx
index 394d1eae2..266411770 100644
--- a/src/client/views/ObservableReactComponent.tsx
+++ b/src/client/views/ObservableReactComponent.tsx
@@ -14,7 +14,10 @@ export abstract class ObservableReactComponent<T> extends React.Component<T, {}>
makeObservable(this);
}
componentDidUpdate(prevProps: Readonly<T>): void {
- Object.keys(prevProps).filter(pkey => (prevProps as any)[pkey] !== (this.props as any)[pkey]).forEach(action(pkey =>
- ((this._props as any)[pkey] = (this.props as any)[pkey]))); // prettier-ignore
+ Object.keys(prevProps)
+ .filter(pkey => (prevProps as any)[pkey] !== (this.props as any)[pkey])
+ .forEach(action(pkey => {
+ (this._props as any)[pkey] = (this.props as any)[pkey];
+ })); // prettier-ignore
}
}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 15b1f0275..a7907a565 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -3,19 +3,21 @@ import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import * as React from 'react';
import ReactLoading from 'react-loading';
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue, setupMoveUpEvents } from '../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnTrue, setupMoveUpEvents } from '../../ClientUtils';
+import { Utils, emptyFunction } from '../../Utils';
import { Doc } from '../../fields/Doc';
import { Height, Width } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
-import { NumCast } from '../../fields/Types';
+import { NumCast, toList } from '../../fields/Types';
import { DocumentType } from '../documents/DocumentTypes';
-import { DragManager, dropActionType } from '../util/DragManager';
+import { DragManager } from '../util/DragManager';
+import { dropActionType } from '../util/DropActionTypes';
import { Transform } from '../util/Transform';
-import { LightboxView } from './LightboxView';
import { ObservableReactComponent } from './ObservableReactComponent';
import './OverlayView.scss';
import { DefaultStyleProvider } from './StyleProvider';
import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
+
const _global = (window /* browser */ || global) /* node */ as any;
export type OverlayDisposer = () => void;
@@ -50,14 +52,14 @@ export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps>
this.height = opts.height || 200;
}
- onPointerDown = (_: React.PointerEvent) => {
+ onPointerDown = () => {
document.removeEventListener('pointermove', this.onPointerMove);
document.removeEventListener('pointerup', this.onPointerUp);
document.addEventListener('pointermove', this.onPointerMove);
document.addEventListener('pointerup', this.onPointerUp);
};
- onResizerPointerDown = (_: React.PointerEvent) => {
+ onResizerPointerDown = () => {
document.removeEventListener('pointermove', this.onResizerPointerMove);
document.removeEventListener('pointerup', this.onResizerPointerUp);
document.addEventListener('pointermove', this.onResizerPointerMove);
@@ -80,27 +82,27 @@ export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps>
this.height = Math.max(this.height, 30);
};
- onPointerUp = (e: PointerEvent) => {
+ onPointerUp = () => {
document.removeEventListener('pointermove', this.onPointerMove);
document.removeEventListener('pointerup', this.onPointerUp);
};
- onResizerPointerUp = (e: PointerEvent) => {
+ onResizerPointerUp = () => {
document.removeEventListener('pointermove', this.onResizerPointerMove);
document.removeEventListener('pointerup', this.onResizerPointerUp);
};
render() {
- return LightboxView.LightboxDoc ? null : (
+ return (
<div className="overlayWindow-outerDiv" style={{ transform: `translate(${this.x}px, ${this.y}px)`, width: this.width, height: this.height }}>
<div className="overlayWindow-titleBar" onPointerDown={this.onPointerDown}>
{this._props.overlayOptions.title || 'Untitled'}
- <button onClick={this._props.onClick} className="overlayWindow-closeButton">
+ <button type="button" onClick={this._props.onClick} className="overlayWindow-closeButton">
X
</button>
</div>
<div className="overlayWindow-content">{this.props.children}</div>
- <div className="overlayWindow-resizeDragger" onPointerDown={this.onResizerPointerDown}></div>
+ <div className="overlayWindow-resizeDragger" onPointerDown={this.onResizerPointerDown} />
</div>
);
}
@@ -108,6 +110,7 @@ export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps>
@observer
export class OverlayView extends ObservableReactComponent<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: OverlayView;
@observable.shallow _elements: JSX.Element[] = [];
@@ -118,8 +121,9 @@ export class OverlayView extends ObservableReactComponent<{}> {
OverlayView.Instance = this;
new _global.ResizeObserver(
action((entries: any) => {
- for (const entry of entries) {
- Doc.MyOverlayDocs.forEach(doc => {
+ Array.from(entries).forEach((entry: any) => {
+ Doc.MyOverlayDocs.forEach(docIn => {
+ const doc = docIn;
if (NumCast(doc.overlayX) > entry.contentRect.width - 10) {
doc.overlayX = entry.contentRect.width - 10;
}
@@ -127,7 +131,7 @@ export class OverlayView extends ObservableReactComponent<{}> {
doc.overlayY = entry.contentRect.height - 10;
}
});
- }
+ });
})
).observe(window.document.body);
}
@@ -162,30 +166,22 @@ export class OverlayView extends ObservableReactComponent<{}> {
const index = this._elements.indexOf(contents);
if (index !== -1) this._elements.splice(index, 1);
});
- contents = (
+ const wincontents = (
<OverlayWindow onClick={remove} key={Utils.GenerateGuid()} overlayOptions={options}>
{contents}
</OverlayWindow>
);
- this._elements.push(contents);
+ this._elements.push(wincontents);
return remove;
}
- removeOverlayDoc = (doc: Doc | Doc[]) => {
- (doc instanceof Doc ? [doc] : doc).forEach(Doc.RemFromMyOverlay);
- return true;
- };
+ removeOverlayDoc = (docs: Doc | Doc[]) => toList(docs).every(Doc.RemFromMyOverlay);
- docScreenToLocalXf = computedFn(
- function docScreenToLocalXf(this: any, doc: Doc) {
- return () => new Transform(-NumCast(doc.overlayX), -NumCast(doc.overlayY), 1);
- }.bind(this)
- );
+ docScreenToLocalXf = computedFn((doc: Doc) => () => new Transform(-NumCast(doc.overlayX), -NumCast(doc.overlayY), 1));
@computed get overlayDocs() {
- return Doc.MyOverlayDocs.filter(d => !LightboxView.LightboxDoc || d.type === DocumentType.PRES).map(d => {
- let offsetx = 0,
- offsety = 0;
+ return Doc.MyOverlayDocs.map(d => {
+ let [offsetx, offsety] = [0, 0];
const dref = React.createRef<HTMLDivElement>();
const onPointerMove = action((e: PointerEvent, down: number[]) => {
if (e.cancelBubble) return false; // if the overlay doc processed the move event (e.g., to pan its contents), then the event should be marked as canceled since propagation can't be stopped
@@ -198,9 +194,7 @@ export class OverlayView extends ObservableReactComponent<{}> {
dragData.offset = [-offsetx, -offsety];
dragData.dropAction = dropActionType.move;
dragData.removeDocument = this.removeOverlayDoc;
- dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
- return dragData.removeDocument!(doc) ? addDocument(doc) : false;
- };
+ dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => (dragData.removeDocument?.(doc) ? addDocument(doc) : false);
DragManager.StartDocumentDrag([dref.current!], dragData, down[0], down[1]);
return true;
}
@@ -227,7 +221,7 @@ export class OverlayView extends ObservableReactComponent<{}> {
PanelHeight={d[Height]}
ScreenToLocalTransform={this.docScreenToLocalXf(d)}
renderDepth={1}
- hideDecorations={true}
+ hideDecorations
isDocumentActive={returnTrue}
isContentActive={returnTrue}
whenChildContentsActiveChanged={emptyFunction}
diff --git a/src/client/views/PinFuncs.ts b/src/client/views/PinFuncs.ts
new file mode 100644
index 000000000..3d998ecaf
--- /dev/null
+++ b/src/client/views/PinFuncs.ts
@@ -0,0 +1,139 @@
+import { Doc, DocListCast } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
+import { Copy, Id } from '../../fields/FieldSymbols';
+import { List } from '../../fields/List';
+import { ObjectField } from '../../fields/ObjectField';
+import { NumCast, StrCast } from '../../fields/Types';
+import { SerializationHelper } from '../util/SerializationHelper';
+
+export interface MarqueeViewBounds {
+ left: number;
+ top: number;
+ width: number;
+ height: number;
+}
+export interface pinDataTypes {
+ scrollable?: boolean;
+ dataviz?: number[];
+ pannable?: boolean;
+ type_collection?: boolean;
+ inkable?: boolean;
+ filters?: boolean;
+ pivot?: boolean;
+ temporal?: boolean;
+ clippable?: boolean;
+ datarange?: boolean;
+ dataview?: boolean;
+ poslayoutview?: boolean;
+ dataannos?: boolean;
+ map?: boolean;
+}
+export interface PinProps {
+ audioRange?: boolean;
+ activeFrame?: number;
+ currentFrame?: number;
+ hidePresBox?: boolean;
+ pinViewport?: MarqueeViewBounds; // pin a specific viewport on a freeform view (use MarqueeView.CurViewBounds to compute if no region has been selected)
+ pinDocLayout?: boolean; // pin layout info (width/height/x/y)
+ pinAudioPlay?: boolean; // pin audio annotation
+ pinData?: pinDataTypes;
+}
+
+/// copies values from the targetDoc (which is the prototype of the pinDoc) to
+/// reserved fields on the pinDoc so that those values can be restored to the
+/// target doc when navigating to it.
+export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) {
+ const pinDoc = pinDocIn;
+ pinDoc.presentation = true;
+ pinDoc.config = '';
+ if (pinProps.pinDocLayout) {
+ pinDoc.config_pinLayout = true;
+ pinDoc.config_x = NumCast(targetDoc.x);
+ pinDoc.config_y = NumCast(targetDoc.y);
+ pinDoc.config_rotation = NumCast(targetDoc.rotation);
+ pinDoc.config_width = NumCast(targetDoc.width);
+ pinDoc.config_height = NumCast(targetDoc.height);
+ }
+ if (pinProps.pinAudioPlay) pinDoc.presPlayAudio = true;
+ if (pinProps.pinData) {
+ pinDoc.config_pinData =
+ pinProps.pinData.scrollable ||
+ pinProps.pinData.temporal ||
+ pinProps.pinData.pannable ||
+ pinProps.pinData.type_collection ||
+ pinProps.pinData.clippable ||
+ pinProps.pinData.datarange ||
+ pinProps.pinData.dataview ||
+ pinProps.pinData.poslayoutview ||
+ pinProps?.activeFrame !== undefined;
+ const fkey = Doc.LayoutFieldKey(targetDoc);
+ if (pinProps.pinData.dataview) {
+ pinDoc.config_usePath = targetDoc[fkey + '_usePath'];
+ pinDoc.config_data = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data;
+ }
+ if (pinProps.pinData.dataannos) {
+ const fieldKey = Doc.LayoutFieldKey(targetDoc);
+ pinDoc.config_annotations = new List<Doc>(DocListCast(targetDoc[DocData][fieldKey + '_annotations']).filter(doc => !doc.layout_unrendered));
+ }
+ if (pinProps.pinData.inkable) {
+ pinDoc.config_fillColor = targetDoc.fillColor;
+ pinDoc.config_color = targetDoc.color;
+ pinDoc.config_width = targetDoc._width;
+ pinDoc.config_height = targetDoc._height;
+ }
+ if (pinProps.pinData.scrollable) pinDoc.config_scrollTop = targetDoc._layout_scrollTop;
+ if (pinProps.pinData.clippable) {
+ const fieldKey = Doc.LayoutFieldKey(targetDoc);
+ pinDoc.config_clipWidth = targetDoc[fieldKey + '_clipWidth'];
+ }
+ if (pinProps.pinData.datarange) {
+ pinDoc.config_xRange = undefined; // targetDoc?.xrange;
+ pinDoc.config_yRange = undefined; // targetDoc?.yrange;
+ }
+ if (pinProps.pinData.map) {
+ // pinDoc.config_latitude = targetDoc?.latitude;
+ // pinDoc.config_longitude = targetDoc?.longitude;
+ pinDoc.config_map_zoom = targetDoc?.map_zoom;
+ pinDoc.config_map_type = targetDoc?.map_type;
+ // ...
+ }
+ if (pinProps.pinData.poslayoutview)
+ pinDoc.config_pinLayoutData = new List<string>(
+ DocListCast(targetDoc[fkey] as ObjectField).map(d =>
+ JSON.stringify({
+ id: d[Id],
+ x: NumCast(d.x),
+ y: NumCast(d.y),
+ w: NumCast(d._width),
+ h: NumCast(d._height),
+ fill: StrCast(d._fillColor),
+ back: StrCast(d._backgroundColor),
+ data: SerializationHelper.Serialize(d.data instanceof ObjectField ? d.data[Copy]() : ''),
+ text: SerializationHelper.Serialize(d.text instanceof ObjectField ? d.text[Copy]() : ''),
+ })
+ )
+ );
+ if (pinProps.pinData.type_collection) pinDoc.config_viewType = targetDoc._type_collection;
+ if (pinProps.pinData.filters) pinDoc.config_docFilters = ObjectField.MakeCopy(targetDoc.childFilters as ObjectField);
+ if (pinProps.pinData.pivot) pinDoc.config_pivotField = targetDoc._pivotField;
+ if (pinProps.pinData.pannable) {
+ pinDoc.config_panX = NumCast(targetDoc._freeform_panX);
+ pinDoc.config_panY = NumCast(targetDoc._freeform_panY);
+ pinDoc.config_viewScale = NumCast(targetDoc._freeform_scale, 1);
+ }
+ if (pinProps.pinData.temporal) {
+ pinDoc.config_clipStart = targetDoc._layout_currentTimecode;
+ const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], NumCast(targetDoc.config_clipStart) + 0.1);
+ pinDoc.config_clipEnd = NumCast(pinDoc.config_clipStart) + NumCast(targetDoc.clipEnd, duration);
+ }
+ }
+ if (pinProps?.pinViewport) {
+ // If pinWithView option set then update scale and x / y props of slide
+ const bounds = pinProps.pinViewport;
+ pinDoc.config_pinView = true;
+ pinDoc.config_viewScale = NumCast(targetDoc._freeform_scale, 1);
+ pinDoc.config_panX = bounds.left + bounds.width / 2;
+ pinDoc.config_panY = bounds.top + bounds.height / 2;
+ pinDoc.config_viewBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
+ }
+}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 4b7771f27..034ade50b 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -1,9 +1,10 @@
import { action, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { lightOrDark, returnFalse } from '../../Utils';
+import { lightOrDark, returnFalse } from '../../ClientUtils';
import { Doc, Opt } from '../../fields/Doc';
-import { DocUtils, Docs, DocumentOptions } from '../documents/Documents';
+import { Docs, DocumentOptions } from '../documents/Documents';
+import { DocUtils } from '../documents/DocUtils';
import { ImageUtils } from '../util/Import & Export/ImageUtils';
import { Transform } from '../util/Transform';
import { UndoManager, undoBatch } from '../util/UndoManager';
@@ -13,6 +14,7 @@ import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
@observer
export class PreviewCursor extends ObservableReactComponent<{}> {
+ // eslint-disable-next-line no-use-before-define
static _instance: PreviewCursor;
public static get Instance() {
return PreviewCursor._instance;
@@ -39,7 +41,9 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
paste = async (e: ClipboardEvent) => {
if (this.Visible && e.clipboardData) {
const newPoint = this._getTransform?.().transformPoint(this._clickPoint[0], this._clickPoint[1]);
- runInAction(() => (this.Visible = false));
+ runInAction(() => {
+ this.Visible = false;
+ });
// tests for URL and makes web document
const re: any = /^https?:\/\//g;
@@ -88,10 +92,10 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
UndoManager.RunInBatch(() => this._addLiveTextDoc?.(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined)), 'paste');
}
}
- //pasting in images
+ // pasting in images
else if (e.clipboardData.getData('text/html') !== '' && e.clipboardData.getData('text/html').includes('<img src=')) {
- const re: any = /<img src="(.*?)"/g;
- const arr: any[] = re.exec(e.clipboardData.getData('text/html'));
+ const regEx: any = /<img src="(.*?)"/g;
+ const arr: any[] = regEx.exec(e.clipboardData.getData('text/html'));
if (newPoint) {
undoBatch(() => {
@@ -122,7 +126,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
@action
onKeyDown = (e: KeyboardEvent) => {
// Mixing events between React and Native is finicky.
- //if not these keys, make a textbox if preview cursor is active!
+ // if not these keys, make a textbox if preview cursor is active!
if (
e.key !== 'Escape' &&
e.key !== 'Backspace' &&
@@ -162,7 +166,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
}
};
- //when focus is lost, this will remove the preview cursor
+ // when focus is lost, this will remove the preview cursor
@action onBlur = (): void => {
this.Visible = false;
};
@@ -192,6 +196,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
}
render() {
return !this._clickPoint || !this.Visible ? null : (
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
<div className="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={e => e?.focus()} style={{ color: lightOrDark(this.Doc?.backgroundColor ?? 'white'), transform: `translate(${this._clickPoint[0]}px, ${this._clickPoint[1]}px)` }}>
I
</div>
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 517a80d63..edf6df2b9 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/no-unused-class-component-methods */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Dropdown, DropdownType, IListItemProps, Toggle, ToggleType, Type } from 'browndash-components';
import { action, computed, observable } from 'mobx';
@@ -12,98 +15,47 @@ import { MdClosedCaption, MdClosedCaptionDisabled, MdGridOff, MdGridOn, MdSubtit
import { RxWidth } from 'react-icons/rx';
import { TbEditCircle, TbEditCircleOff, TbHandOff, TbHandStop, TbHighlight, TbHighlightOff } from 'react-icons/tb';
import { TfiBarChart } from 'react-icons/tfi';
-import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { Doc, Opt } from '../../fields/Doc';
import { DocData } from '../../fields/DocSymbols';
import { ScriptField } from '../../fields/ScriptField';
import { BoolCast, ScriptCast } from '../../fields/Types';
import { ImageField } from '../../fields/URLField';
+import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
-import { DocUtils } from '../documents/Documents';
-import { IsFollowLinkScript } from '../util/LinkFollower';
-import { LinkManager } from '../util/LinkManager';
-import { SelectionManager } from '../util/SelectionManager';
import { SettingsManager } from '../util/SettingsManager';
import { undoBatch, undoable } from '../util/UndoManager';
import { InkingStroke } from './InkingStroke';
import './PropertiesButtons.scss';
import { Colors } from './global/globalEnums';
-import { DocumentView, OpenWhere } from './nodes/DocumentView';
+import { DocumentView } from './nodes/DocumentView';
+import { OpenWhere } from './nodes/OpenWhere';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
@observer
export class PropertiesButtons extends React.Component<{}, {}> {
+ // eslint-disable-next-line no-use-before-define
@observable public static Instance: PropertiesButtons;
@computed get selectedDoc() {
- return SelectionManager.SelectedSchemaDoc || SelectionManager.Views.lastElement()?.Document;
+ return DocumentView.SelectedSchemaDoc() || DocumentView.Selected().lastElement()?.Document;
}
@computed get selectedLayoutDoc() {
- return SelectionManager.SelectedSchemaDoc || SelectionManager.Views.lastElement()?.layoutDoc;
+ return DocumentView.SelectedSchemaDoc() || DocumentView.Selected().lastElement()?.layoutDoc;
}
@computed get selectedTabView() {
- return !SelectionManager.SelectedSchemaDoc && SelectionManager.Views.lastElement()?.topMost;
- }
-
- propertyToggleBtn = (label: (on?: any) => string, property: string, tooltip: (on?: any) => string, icon: (on?: any) => any, onClick?: (dv: Opt<DocumentView>, doc: Doc, property: string) => void, useUserDoc?: boolean) => {
- const targetDoc = useUserDoc ? Doc.UserDoc() : this.selectedLayoutDoc;
- const onPropToggle = (dv: Opt<DocumentView>, doc: Doc, prop: string) => ((dv?.layoutDoc || doc)[prop] = (dv?.layoutDoc || doc)[prop] ? false : true);
- return !targetDoc ? null : (
- <Toggle
- toggleStatus={BoolCast(targetDoc[property])}
- tooltip={tooltip(BoolCast(targetDoc[property]))}
- text={label(targetDoc?.[property])}
- color={SettingsManager.userColor}
- icon={icon(targetDoc?.[property] as any)}
- iconPlacement="left"
- align="flex-start"
- fillWidth={true}
- toggleType={ToggleType.BUTTON}
- onClick={undoable(() => {
- if (SelectionManager.Views.length > 1) {
- SelectionManager.Views.forEach(dv => (onClick ?? onPropToggle)(dv, dv.Document, property));
- } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property);
- }, property)}
- />
- );
- };
-
- // this implments a container pattern by marking the targetDoc (collection) as a lightbox
- // that always fits its contents to its container and that hides all other documents when
- // a link is followed that targets a 'lightbox' destination
- @computed get isLightboxButton() {
- return this.propertyToggleBtn(
- on => 'Lightbox',
- 'isLightbox',
- on => `${on ? 'Set' : 'Remove'} lightbox flag`,
- on => 'window-restore',
- onClick => {
- SelectionManager.Views.forEach(dv => {
- const containerDoc = dv.Document;
- //containerDoc.followAllLinks =
- // containerDoc.noShadow =
- // containerDoc.layout_disableBrushing =
- // containerDoc._forceActive =
- //containerDoc._freeform_fitContentsToBox =
- containerDoc._isLightbox = !containerDoc._isLightbox;
- //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
- const containerContents = DocListCast(dv.dataDoc[Doc.LayoutFieldKey(containerDoc)]);
- //dv.Docuemnt.onClick = ScriptField.MakeScript('{this.data = undefined; documentView.select(false)}', { documentView: 'any' });
- containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.link_displayLine = false)));
- });
- }
- );
+ return !DocumentView.SelectedSchemaDoc() && DocumentView.Selected().lastElement()?.topMost;
}
@computed get titleButton() {
return this.propertyToggleBtn(
- on => (!on ? 'SHOW TITLE' : this.selectedDoc?.['_layout_showTitle'] === 'title:hover' ? 'HIDE TITLE' : 'HOVER TITLE'),
+ on => (!on ? 'SHOW TITLE' : this.selectedDoc?._layout_showTitle === 'title:hover' ? 'HIDE TITLE' : 'HOVER TITLE'),
'_layout_showTitle',
- on => 'Switch between title styles',
+ () => 'Switch between title styles',
on => (on ? <MdSubtitlesOff /> : <MdSubtitles />), // {currentIcon}, //(on ? <MdSubtitles/> :) , //,'text-width', on ? <MdSubtitles/> : <MdSubtitlesOff/>,
(dv, doc) => {
const tdoc = dv?.Document || doc;
const newtitle = !tdoc._layout_showTitle ? 'title' : tdoc._layout_showTitle === 'title' ? 'title:hover' : '';
- tdoc._layout_showTitle = newtitle ? newtitle : undefined;
+ tdoc._layout_showTitle = newtitle || undefined;
}
);
}
@@ -119,7 +71,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
}
@computed get maskButton() {
- //highlight text while going down and reading through
+ // highlight text while going down and reading through
return this.propertyToggleBtn(
on => (on ? 'PLAIN INK' : 'HIGHLIGHTER MASK'),
'stroke_isInkMask',
@@ -132,10 +84,10 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@computed get hideImageButton() {
// put in developer -- can trace on top of object and drawing is still there
return this.propertyToggleBtn(
- on => (on ? 'SHOW BACKGROUND IMAGE' : 'HIDE BACKGROUND IMAGE'), //'Background',
+ on => (on ? 'SHOW BACKGROUND IMAGE' : 'HIDE BACKGROUND IMAGE'), // 'Background',
'_hideImage',
on => (on ? 'Show Image' : 'Show Background'),
- on => (on ? <BiShow /> : <BiHide />) //'portrait'
+ on => (on ? <BiShow /> : <BiHide />) // 'portrait'
);
}
@@ -144,35 +96,35 @@ export class PropertiesButtons extends React.Component<{}, {}> {
on => (on ? 'DISABLE CLUSTERS' : 'HIGHLIGHT CLUSTERS'),
'_freeform_useClusters',
on => `${on ? 'Hide' : 'Show'} clusters`,
- on => <FaBraille />
+ () => <FaBraille />
);
}
@computed get panButton() {
return this.propertyToggleBtn(
- on => (on ? 'ENABLE PANNING' : 'DISABLE PANNING'), //'Lock\xA0View',
+ on => (on ? 'ENABLE PANNING' : 'DISABLE PANNING'), // 'Lock\xA0View',
'_lockedTransform',
on => `${on ? 'Unlock' : 'Lock'} panning of view`,
- on => (on ? <TbHandStop /> : <TbHandOff />) //'lock'
+ on => (on ? <TbHandStop /> : <TbHandOff />) // 'lock'
);
}
@computed get forceActiveButton() {
- //select text
+ // select text
return this.propertyToggleBtn(
on => (on ? 'SELECT TO INTERACT' : 'ALWAYS INTERACTIVE'),
'_forceActive',
on => `${on ? 'Document must be selected to interact with its contents' : 'Contents always active (respond to click/drag events)'} `,
- on => <MdTouchApp /> // 'eye'
+ () => <MdTouchApp /> // 'eye'
);
}
@computed get verticalAlignButton() {
- //select text
+ // select text
return this.propertyToggleBtn(
on => (on ? 'ALIGN TOP' : 'ALIGN CENTER'),
'_layout_centered',
on => `${on ? 'Text is aligned with top of document' : 'Text is aligned with center of document'} `,
- on => <MdTouchApp /> // 'eye'
+ () => <MdTouchApp /> // 'eye'
);
}
@@ -181,9 +133,9 @@ export class PropertiesButtons extends React.Component<{}, {}> {
on => (on ? 'DISABLE FLASHCARD' : 'ENABLE FLASHCARD'),
'layout_textPainted',
on => `${on ? 'Flashcard enabled' : 'Flashcard disabled'} `,
- on => <MdTouchApp />,
+ () => <MdTouchApp />,
(dv, doc) => {
- const on = doc.onPaint ? true : false;
+ const on = !!doc.onPaint;
doc[DocData].onPaint = on ? undefined : ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, { documentView: 'any' });
doc[DocData].layout_textPainted = on ? undefined : `<ComparisonBox {...props} fieldKey={'${dv?.LayoutFieldKey ?? 'text'}'}/>`;
}
@@ -192,10 +144,10 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@computed get fitContentButton() {
return this.propertyToggleBtn(
- on => (on ? 'PREVIOUS VIEW' : 'VIEW ALL'), //'View All',
+ on => (on ? 'PREVIOUS VIEW' : 'VIEW ALL'), // 'View All',
'_freeform_fitContentsToBox',
on => `${on ? "Don't" : 'Do'} fit content to container visible area`,
- on => (on ? <CiGrid31 /> : <BsGrid3X3GapFill />) //'object-group'
+ on => (on ? <CiGrid31 /> : <BsGrid3X3GapFill />) // 'object-group'
);
}
@@ -209,7 +161,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
// on => `${on ? 'Set' : 'Remove'} lightbox flag`,
// on => 'window-restore',
// onClick => {
- // SelectionManager.Views.forEach(dv => {
+ // DocumentView.Selected().forEach(dv => {
// const containerDoc = dv.Document;
// //containerDoc.followAllLinks =
// // containerDoc.noShadow =
@@ -228,7 +180,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@computed get layout_fitWidthButton() {
return this.propertyToggleBtn(
- on => (on ? 'SCALED VIEW' : 'READING VIEW'), //'Fit\xA0Width',
+ on => (on ? 'SCALED VIEW' : 'READING VIEW'), // 'Fit\xA0Width',
'_layout_fitWidth',
on =>
on
@@ -240,12 +192,14 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@computed get captionButton() {
return this.propertyToggleBtn(
- //DEVELOPER
- on => (on ? 'HIDE CAPTION' : 'SHOW CAPTION'), //'Caption',
+ // DEVELOPER
+ on => (on ? 'HIDE CAPTION' : 'SHOW CAPTION'), // 'Caption',
'_layout_showCaption',
on => `${on ? 'Hide' : 'Show'} caption footer`,
- on => (on ? <MdClosedCaptionDisabled /> : <MdClosedCaption />), //'closed-captioning',
- (dv, doc) => ((dv?.Document || doc)._layout_showCaption = (dv?.Document || doc)._layout_showCaption === undefined ? 'caption' : undefined)
+ on => (on ? <MdClosedCaptionDisabled /> : <MdClosedCaption />), // 'closed-captioning',
+ (dv, doc) => {
+ (dv?.Document || doc)._layout_showCaption = (dv?.Document || doc)._layout_showCaption === undefined ? 'caption' : undefined;
+ }
);
}
@@ -256,7 +210,9 @@ export class PropertiesButtons extends React.Component<{}, {}> {
'_chromeHidden',
on => `${on ? 'Show' : 'Hide'} editing UI`,
on => (on ? <TbEditCircle /> : <TbEditCircleOff />), // 'edit',
- (dv, doc) => ((dv?.Document || doc)._chromeHidden = !(dv?.Document || doc)._chromeHidden)
+ (dv, doc) => {
+ (dv?.Document || doc)._chromeHidden = !(dv?.Document || doc)._chromeHidden;
+ }
);
}
@@ -265,8 +221,8 @@ export class PropertiesButtons extends React.Component<{}, {}> {
return this.propertyToggleBtn(
on => (on ? 'AUTO\xA0SIZE' : 'FIXED SIZE'),
'_layout_autoHeight',
- on => `Automatical vertical sizing to show all content`,
- on => <FontAwesomeIcon icon="arrows-alt-v" size="lg" />
+ () => `Automatical vertical sizing to show all content`,
+ () => <FontAwesomeIcon icon="arrows-alt-v" size="lg" />
);
}
@@ -274,8 +230,8 @@ export class PropertiesButtons extends React.Component<{}, {}> {
return this.propertyToggleBtn(
on => (on ? 'HIDE GRID' : 'DISPLAY GRID'),
'_freeform_backgroundGrid',
- on => `Display background grid in collection`,
- on => (on ? <MdGridOff /> : <MdGridOn />) //'border-all'
+ () => `Display background grid in collection`,
+ on => (on ? <MdGridOff /> : <MdGridOn />) // 'border-all'
);
}
@@ -317,8 +273,8 @@ export class PropertiesButtons extends React.Component<{}, {}> {
return this.propertyToggleBtn(
on => (on ? 'HIDE SNAP LINES' : 'SHOW SNAP LINES'),
'freeform_snapLines',
- on => `Display snapping lines when objects are dragged`,
- on => <TfiBarChart />, //'th',
+ () => `Display snapping lines when objects are dragged`,
+ () => <TfiBarChart />, // 'th',
undefined
);
}
@@ -361,18 +317,20 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@undoBatch
handlePerspectiveChange = (e: any) => {
this.selectedDoc && (this.selectedDoc._type_collection = e.target.value);
- SelectionManager.Views.forEach(docView => (docView.layoutDoc._type_collection = e.target.value));
+ DocumentView.Selected().forEach(docView => {
+ docView.layoutDoc._type_collection = e.target.value;
+ });
};
@computed get onClickVal() {
const linkButton = IsFollowLinkScript(this.selectedDoc.onClick);
const followLoc = this.selectedDoc._followLinkLocation;
- const linkedToLightboxView = () => LinkManager.Links(this.selectedDoc).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
+ const linkedToLightboxView = () => Doc.Links(this.selectedDoc).some(link => Doc.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
if (followLoc === OpenWhere.lightbox && !linkedToLightboxView()) return 'linkInPlace';
- else if (linkButton && followLoc === OpenWhere.addRight) return 'linkOnRight';
- else if (linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView()) return 'enterPortal';
- else if (ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail')) return 'toggleDetail';
- else return 'nothing';
+ if (linkButton && followLoc === OpenWhere.addRight) return 'linkOnRight';
+ if (linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView()) return 'enterPortal';
+ if (ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail')) return 'toggleDetail';
+ return 'nothing';
}
@computed
@@ -385,20 +343,18 @@ export class PropertiesButtons extends React.Component<{}, {}> {
['linkOnRight', 'Open Link on Right'],
];
- const items: IListItemProps[] = buttonList.map(value => {
- return {
- text: value[1],
- val: value[1],
- };
- });
+ const items: IListItemProps[] = buttonList.map(value => ({
+ text: value[1],
+ val: value[1],
+ }));
return !this.selectedDoc ? null : (
<Dropdown
- tooltip={'Choose onClick behavior'}
+ tooltip="Choose onClick behavior"
items={items}
- closeOnSelect={true}
+ closeOnSelect
selectedVal={this.onClickVal}
setSelectedVal={val => this.handleOptionChange(val as string)}
- title={'Choose onClick behaviour'}
+ title="Choose onClick behaviour"
color={SettingsManager.userColor}
dropdownType={DropdownType.SELECT}
type={Type.SEC}
@@ -422,7 +378,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
@undoBatch
@action
handleOptionChange = (onClick: string) => {
- SelectionManager.Views.forEach(docView => {
+ DocumentView.Selected().forEach(docView => {
const linkButton = IsFollowLinkScript(docView.Document.onClick);
docView.noOnClick();
switch (onClick) {
@@ -440,16 +396,11 @@ export class PropertiesButtons extends React.Component<{}, {}> {
docView.toggleFollowLink(false, false);
docView.Document.followLinkLocation = linkButton ? OpenWhere.addRight : undefined;
break;
+ default:
}
});
};
- @undoBatch
- editOnClickScript = () => {
- if (SelectionManager.Views.length) SelectionManager.Views.forEach(dv => DocUtils.makeCustomViewClicked(dv.Document, undefined, 'onClick'));
- else this.selectedDoc && DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, 'onClick');
- };
-
@computed
get onClickFlyout() {
const buttonList = [
@@ -463,7 +414,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
const click = () => this.handleOptionChange(value[0]);
const linkButton = IsFollowLinkScript(this.selectedDoc.onClick);
const followLoc = this.selectedDoc._followLinkLocation;
- const linkedToLightboxView = () => LinkManager.Links(this.selectedDoc).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
+ const linkedToLightboxView = () => Doc.Links(this.selectedDoc).some(link => Doc.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
let active = false;
// prettier-ignore
@@ -473,6 +424,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
case 'enterPortal': active = linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView(); break;
case 'toggleDetail':active = ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail'); break;
case 'nothing': active = !linkButton && this.selectedDoc.onClick === undefined;break;
+ default:
}
return (
<div className="list-item" key={`${value}`} style={{ backgroundColor: active ? Colors.LIGHT_BLUE : undefined }} onClick={click}>
@@ -494,10 +446,40 @@ export class PropertiesButtons extends React.Component<{}, {}> {
</div>
);
}
+ @undoBatch
+ editOnClickScript = () => {
+ if (DocumentView.Selected().length) DocumentView.Selected().forEach(dv => DocUtils.makeCustomViewClicked(dv.Document, undefined, 'onClick'));
+ else this.selectedDoc && DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, 'onClick');
+ };
+
+ propertyToggleBtn = (label: (on?: any) => string, property: string, tooltip: (on?: any) => string, icon: (on?: any) => any, onClick?: (dv: Opt<DocumentView>, doc: Doc, property: string) => void, useUserDoc?: boolean) => {
+ const targetDoc = useUserDoc ? Doc.UserDoc() : this.selectedLayoutDoc;
+ const onPropToggle = (dv: Opt<DocumentView>, doc: Doc, prop: string) => {
+ (dv?.layoutDoc || doc)[prop] = !(dv?.layoutDoc || doc)[prop];
+ };
+ return !targetDoc ? null : (
+ <Toggle
+ toggleStatus={BoolCast(targetDoc[property])}
+ tooltip={tooltip(BoolCast(targetDoc[property]))}
+ text={label(targetDoc?.[property])}
+ color={SettingsManager.userColor}
+ icon={icon(targetDoc?.[property] as any)}
+ iconPlacement="left"
+ align="flex-start"
+ fillWidth
+ toggleType={ToggleType.BUTTON}
+ onClick={undoable(() => {
+ if (DocumentView.Selected().length > 1) {
+ DocumentView.Selected().forEach(dv => (onClick ?? onPropToggle)(dv, dv.Document, property));
+ } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property);
+ }, property)}
+ />
+ );
+ };
render() {
const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)];
- const isText = SelectionManager.Views.lastElement()?.ComponentView instanceof FormattedTextBox;
+ const isText = DocumentView.Selected().lastElement()?.ComponentView instanceof FormattedTextBox;
const isInk = this.selectedDoc?.layout_isSvg;
const isImage = layoutField instanceof ImageField;
const isMap = this.selectedDoc?.type === DocumentType.MAP;
@@ -505,11 +487,9 @@ export class PropertiesButtons extends React.Component<{}, {}> {
const isStacking = [CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.NoteTaking].includes(this.selectedDoc?._type_collection as any);
const isFreeForm = this.selectedDoc?._type_collection === CollectionViewType.Freeform;
const isTree = this.selectedDoc?._type_collection === CollectionViewType.Tree;
- const isTabView = this.selectedTabView;
const toggle = (ele: JSX.Element | null, style?: React.CSSProperties) => (
<div className="propertiesButtons-button" style={style}>
- {' '}
- {ele}{' '}
+ {ele}
</div>
);
const isNovice = Doc.noviceMode;
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index cf5105efc..edb55f341 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable react/require-default-props */
+/* eslint-disable react/no-unused-prop-types */
import { action } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -5,12 +7,12 @@ import { Doc } from '../../fields/Doc';
import { Cast } from '../../fields/Types';
import { DocumentType } from '../documents/DocumentTypes';
import { LinkManager } from '../util/LinkManager';
-import { SelectionManager } from '../util/SelectionManager';
import { SettingsManager } from '../util/SettingsManager';
import './PropertiesDocBacklinksSelector.scss';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { LinkMenu } from './linking/LinkMenu';
-import { OpenWhere } from './nodes/DocumentView';
+import { OpenWhere } from './nodes/OpenWhere';
+import { DocumentView } from './nodes/DocumentView';
type PropertiesDocBacklinksSelectorProps = {
Document: Doc;
@@ -23,7 +25,7 @@ type PropertiesDocBacklinksSelectorProps = {
export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDocBacklinksSelectorProps> {
getOnClick = action((link: Doc) => {
const linkAnchor = this.props.Document;
- const other = LinkManager.getOppositeAnchor(link, linkAnchor);
+ const other = Doc.getOppositeAnchor(link, linkAnchor);
const otherdoc = !other ? undefined : other.annotationOn && other.type !== DocumentType.RTF ? Cast(other.annotationOn, Doc, null) : other;
LinkManager.Instance.currentLink = link;
if (otherdoc) {
@@ -34,10 +36,10 @@ export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDo
});
render() {
- return !SelectionManager.Views.length ? null : (
+ return !DocumentView.Selected().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={DocumentView.Selected().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 b8bbde9de..7465c727a 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -1,14 +1,17 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/anchor-is-valid */
import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast } from '../../fields/Doc';
+import { Doc } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { Cast, StrCast } from '../../fields/Types';
-import { DocFocusOrOpen } from '../util/DocumentManager';
import { ObservableReactComponent } from './ObservableReactComponent';
import './PropertiesDocContextSelector.scss';
import { CollectionDockingView } from './collections/CollectionDockingView';
-import { DocumentView, OpenWhere } from './nodes/DocumentView';
+import { DocFocusOrOpen, DocumentView } from './nodes/DocumentView';
+import { OpenWhere } from './nodes/OpenWhere';
type PropertiesDocContextSelectorProps = {
DocView?: DocumentView;
@@ -52,9 +55,9 @@ export class PropertiesDocContextSelector extends ObservableReactComponent<Prope
.map(doc => ({ col: doc, target }));
}
- getOnClick = (col: Doc, target: Doc) => {
+ getOnClick = (clickCol: Doc) => {
if (!this._props.DocView) return;
- col = Doc.IsDataProto(col) ? Doc.MakeDelegate(col) : col;
+ const col = Doc.IsDataProto(clickCol) ? Doc.MakeDelegate(clickCol) : clickCol;
DocFocusOrOpen(Doc.GetProto(this._props.DocView.Document), undefined, col);
};
@@ -65,7 +68,7 @@ export class PropertiesDocContextSelector extends ObservableReactComponent<Prope
{this._props.hideTitle ? null : <p key="contexts">Contexts:</p>}
{this._docs.map(doc => (
<p key={doc.col[Id] + doc.target[Id]}>
- <a onClick={() => this.getOnClick(doc.col, doc.target)}>{StrCast(doc.col.title)}</a>
+ <a onClick={() => this.getOnClick(doc.col)}>{StrCast(doc.col.title)}</a>
</p>
))}
</div>
diff --git a/src/client/views/PropertiesSection.tsx b/src/client/views/PropertiesSection.tsx
index 3c9fa1123..b9a587719 100644
--- a/src/client/views/PropertiesSection.tsx
+++ b/src/client/views/PropertiesSection.tsx
@@ -1,5 +1,8 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/require-default-props */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
+import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { SettingsManager } from '../util/SettingsManager';
@@ -10,8 +13,6 @@ export interface PropertiesSectionProps {
children?: JSX.Element | string | null;
isOpen: boolean;
setIsOpen: (bool: boolean) => any;
- inSection?: boolean;
- setInSection?: (bool: boolean) => any;
onDoubleClick?: () => void;
}
@@ -21,44 +22,35 @@ export class PropertiesSection extends React.Component<PropertiesSectionProps> {
return SettingsManager.userColor;
}
- @computed get backgroundColor() {
- return SettingsManager.userBackgroundColor;
- }
-
@computed get variantColor() {
return SettingsManager.userVariantColor;
}
- @observable isDouble: boolean = false;
-
render() {
if (this.props.children === undefined || this.props.children === null) return null;
- else
- return (
- <div className="propertiesView-section" onPointerEnter={action(() => this.props.setInSection && this.props.setInSection(true))} onPointerLeave={action(() => this.props.setInSection && this.props.setInSection(false))}>
- <div
- className="propertiesView-sectionTitle"
- onDoubleClick={action(e => {
- this.isDouble = true;
- this.props.onDoubleClick && this.props.onDoubleClick();
- this.props.setIsOpen(true);
- setTimeout(() => (this.isDouble = false), 300);
- })}
- onClick={action(e => {
- this.props.setIsOpen(!this.props.isOpen);
- })}
- style={{
- background: this.variantColor,
- // this.props.isOpen ? this.variantColor : this.backgroundColor,
- color: this.color,
- }}>
- {this.props.title}
- <div className="propertiesView-sectionTitle-icon">
- <FontAwesomeIcon icon={this.props.isOpen ? 'caret-down' : 'caret-right'} size="lg" />
- </div>
+ return (
+ <div className="propertiesView-section">
+ <div
+ className="propertiesView-sectionTitle"
+ onDoubleClick={action(() => {
+ this.props.onDoubleClick && this.props.onDoubleClick();
+ this.props.setIsOpen(true);
+ })}
+ onClick={action(() => {
+ this.props.setIsOpen(!this.props.isOpen);
+ })}
+ style={{
+ background: this.variantColor,
+ // this.props.isOpen ? this.variantColor : this.backgroundColor,
+ color: this.color,
+ }}>
+ {this.props.title}
+ <div className="propertiesView-sectionTitle-icon">
+ <FontAwesomeIcon icon={this.props.isOpen ? 'caret-down' : 'caret-right'} size="lg" />
</div>
- {!this.props.isOpen ? null : <div className="propertiesView-content">{this.props.children}</div>}
</div>
- );
+ {!this.props.isOpen ? null : <div className="propertiesView-content">{this.props.children}</div>}
+ </div>
+ );
}
}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index ae29382c1..b03c1a64e 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable prettier/prettier */
import { IconLookup } from '@fortawesome/fontawesome-svg-core';
import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -8,9 +11,10 @@ import { IReactionDisposer, action, computed, makeObservable, observable, reacti
import { observer } from 'mobx-react';
import * as React from 'react';
import { ColorResult, SketchPicker } from 'react-color';
-import * as Icons from 'react-icons/bs'; //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs"
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
-import { Doc, Field, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
+import * as Icons from 'react-icons/bs'; // {BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs"
+import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
+import { Doc, Field, FieldResult, FieldType, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
import { AclAdmin, DocAcl, DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { InkField } from '../../fields/InkField';
@@ -19,10 +23,8 @@ import { ComputedField } from '../../fields/ScriptField';
import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
import { GetEffectiveAcl, SharingPermissions, normalizeEmail } from '../../fields/util';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
-import { DocumentManager } from '../util/DocumentManager';
import { GroupManager } from '../util/GroupManager';
import { LinkManager } from '../util/LinkManager';
-import { SelectionManager } from '../util/SelectionManager';
import { SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
import { Transform } from '../util/Transform';
@@ -36,11 +38,12 @@ import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector
import { PropertiesDocContextSelector } from './PropertiesDocContextSelector';
import { PropertiesSection } from './PropertiesSection';
import './PropertiesView.scss';
-import { DefaultStyleProvider } from './StyleProvider';
-import { DocumentView, OpenWhere } from './nodes/DocumentView';
+import { DefaultStyleProvider, SetFilterOpener as SetPropertiesFilterOpener } from './StyleProvider';
+import { DocumentView } from './nodes/DocumentView';
import { StyleProviderFuncType } from './nodes/FieldView';
-import { KeyValueBox } from './nodes/KeyValueBox';
+import { OpenWhere } from './nodes/OpenWhere';
import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
+
const _global = (window /* browser */ || global) /* node */ as any;
interface PropertiesViewProps {
@@ -54,11 +57,18 @@ interface PropertiesViewProps {
export class PropertiesView extends ObservableReactComponent<PropertiesViewProps> {
private _widthUndo?: UndoManager.Batch;
+ // eslint-disable-next-line no-use-before-define
public static Instance: PropertiesView | undefined;
constructor(props: any) {
super(props);
makeObservable(this);
PropertiesView.Instance = this;
+ SetPropertiesFilterOpener(
+ action(() => {
+ this.CloseAll();
+ this.openFilters = true;
+ })
+ );
}
@computed get MAX_EMBED_HEIGHT() {
@@ -66,7 +76,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
@computed get selectedDoc() {
- return SelectionManager.SelectedSchemaDoc || this.selectedDocumentView?.Document || Doc.ActiveDashboard;
+ return DocumentView.SelectedSchemaDoc() || this.selectedDocumentView?.Document || Doc.ActiveDashboard;
}
@computed get selectedLink() {
@@ -74,12 +84,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
@computed get selectedLayoutDoc() {
- return SelectionManager.SelectedSchemaDoc || this.selectedDocumentView?.layoutDoc || Doc.ActiveDashboard;
+ return DocumentView.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.Document);
- return undefined;
+ return DocumentView.Selected().lastElement();
}
@computed get isPres(): boolean {
return this.selectedDoc?.type === DocumentType.PRES;
@@ -101,14 +109,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@observable openTransform: boolean = true;
@observable openFilters: boolean = false;
- //Pres Trails booleans:
+ // Pres Trails booleans:
@observable openPresTransitions: boolean = true;
@observable openPresProgressivize: boolean = false;
@observable openPresVisibilityAndDuration: boolean = false;
@observable openAddSlide: boolean = false;
@observable openSlideOptions: boolean = false;
- @observable inOptions: boolean = false;
@observable _controlButton: boolean = false;
private _disposers: { [name: string]: IReactionDisposer } = {};
@@ -176,42 +183,47 @@ export class PropertiesView extends ObservableReactComponent<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[] =
+ DocumentView.Selected().length < 2 //
+ ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc]
+ : DocumentView.Selected().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc));
docs.forEach(doc =>
Object.keys(doc)
.filter(filter)
.forEach(key => doc[key] !== ComputedField.undefined && key && ids.add(key))
);
- // prettier-ignore
- Array.from(ids).sort().map(key => {
- const multiple = Array.from(docs.reduce((set,doc) => set.add(doc[key]), new Set<FieldResult>()).keys()).length > 1;
- const editableContents = multiple ? '-multiple-' : Field.toKeyValueString(docs[0], key);
- const displayContents = multiple ? '-multiple-' : Field.toString(docs[0][key] as Field);
- const contentElement = (
- <EditableView
- key="editableView"
- contents={displayContents}
- height={13}
- fontSize={10}
- GetValue={() => editableContents}
- SetValue={(value: string) => {
- value !== '-multiple-' && docs.map(doc => KeyValueBox.SetField(doc, key, value, true));
- return true;
- }}
- />);
- rows.push(
- <div style={{ display: 'flex', overflowY: 'visible', marginBottom: '-1px' }} key={key}>
- <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key + ':'}</span>
- &nbsp;
- {contentElement}
- </div>
- );
- });
+ Array.from(ids)
+ .sort()
+ .forEach(key => {
+ const multiple = Array.from(docs.reduce((set, doc) => set.add(doc[key]), new Set<FieldResult>()).keys()).length > 1;
+ const editableContents = multiple ? '-multiple-' : Field.toKeyValueString(docs[0], key);
+ const displayContents = multiple ? '-multiple-' : Field.toString(docs[0][key] as FieldType);
+ const contentElement = (
+ <EditableView
+ key="editableView"
+ contents={displayContents}
+ height={13}
+ fontSize={10}
+ GetValue={() => editableContents}
+ SetValue={(value: string) => {
+ value !== '-multiple-' && docs.map(doc => Doc.SetField(doc, key, value, true));
+ return true;
+ }}
+ />
+ );
+ rows.push(
+ <div style={{ display: 'flex', overflowY: 'visible', marginBottom: '-1px' }} key={key}>
+ <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key + ':'}</span>
+ &nbsp;
+ {contentElement}
+ </div>
+ );
+ });
rows.push(
<div className="propertiesView-field" key="newKeyValue" style={{ marginTop: '3px', backgroundColor: SettingsManager.userBackgroundColor, textAlign: 'center' }}>
- <EditableView key="editableView" oneLine contents={'add key:value or #tags'} height={13} fontSize={10} GetValue={() => ''} SetValue={this.setKeyValue} />
+ <EditableView key="editableView" oneLine contents="add key:value or #tags" height={13} fontSize={10} GetValue={returnEmptyString} SetValue={this.setKeyValue} />
</div>
);
}
@@ -224,23 +236,29 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get noviceFields() {
const noviceReqFields = ['author', 'author_date', 'tags', '_layout_curPage'];
- return this.editableFields(key => key.indexOf('modificationDate') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('acl')), noviceReqFields);
+ return this.editableFields(key => key.indexOf('modificationDate') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('acl_')), noviceReqFields);
}
@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 =
+ DocumentView.Selected().length < 2 && this.selectedDoc
+ ? [this.layoutFields
+ ? Doc.Layout(this.selectedDoc) //
+ : this.dataDoc!]
+ : DocumentView.Selected().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc)); // prettier-ignore
docs.forEach(doc => {
if (value.indexOf(':') !== -1) {
const newVal = value[0].toUpperCase() + value.substring(1, value.length);
const splits = newVal.split(':');
- KeyValueBox.SetField(doc, splits[0], splits[1], true);
+ Doc.SetField(doc, splits[0], splits[1], true);
const tags = StrCast(doc.tags, ':');
if (tags.includes(`${splits[0]}:`) && splits[1] === 'undefined') {
- KeyValueBox.SetField(doc, 'tags', `"${tags.replace(splits[0] + ':', '')}"`, true);
+ Doc.SetField(doc, 'tags', `"${tags.replace(splits[0] + ':', '')}"`, true);
}
return true;
- } else if (value[0] === '#') {
+ }
+ if (value[0] === '#') {
const tags = StrListCast(doc.tags);
if (!tags.includes(value)) {
tags.push(value);
@@ -248,6 +266,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
return true;
}
+ return undefined;
});
return false;
};
@@ -255,44 +274,43 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@observable transform: Transform = Transform.Identity();
getTransform = () => this.transform;
propertiesDocViewRef = (ref: HTMLDivElement) => {
- const observer = new _global.ResizeObserver(
- action((entries: any) => {
+ const resizeObserver = new _global.ResizeObserver(
+ action(() => {
const cliRect = ref.getBoundingClientRect();
this.transform = new Transform(-cliRect.x, -cliRect.y, 1);
})
);
- ref && observer.observe(ref);
+ ref && resizeObserver.observe(ref);
};
@computed get contexts() {
- return !this.selectedDoc ? null : <PropertiesDocContextSelector DocView={this.selectedDocumentView} hideTitle={true} addDocTab={this._props.addDocTab} />;
+ return !this.selectedDoc ? null : <PropertiesDocContextSelector DocView={this.selectedDocumentView} hideTitle addDocTab={this._props.addDocTab} />;
}
@computed get contextCount() {
if (this.selectedDocumentView) {
const target = this.selectedDocumentView.Document;
return Doc.GetEmbeddings(target).length - 1;
- } else {
- return 0;
}
+ return 0;
}
@computed get links() {
const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor ?? this.selectedDoc;
- return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle={true} addDocTab={this._props.addDocTab} />;
+ return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle addDocTab={this._props.addDocTab} />;
}
@computed get linkCount() {
const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor ?? this.selectedDoc;
- var counter = 0;
+ let counter = 0;
- LinkManager.Links(selAnchor).forEach((l, i) => counter++);
+ Doc.Links(selAnchor).forEach(() => counter++);
return counter;
}
@computed get layoutPreview() {
- if (SelectionManager.Views.length > 1) {
+ if (DocumentView.Selected().length > 1) {
return '-- multiple selected --';
}
if (this.selectedDoc) {
@@ -308,7 +326,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
fitContentsToBox={returnTrue}
styleProvider={DefaultStyleProvider}
containerViewPath={returnEmptyDoclist}
- dontCenter={'y'}
+ dontCenter="y"
isDocumentActive={returnFalse}
isContentActive={emptyFunction}
NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined}
@@ -326,13 +344,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
whenChildContentsActiveChanged={emptyFunction}
addDocTab={returnFalse}
pinToPres={emptyFunction}
- dontRegisterView={true}
+ dontRegisterView
/>
</div>
);
- } else {
- return null;
}
+ return null;
}
/**
@@ -340,7 +357,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
*/
@undoBatch
changePermissions = (e: any, user: string) => {
- const docs = SelectionManager.Views.length < 2 ? [this.selectedDoc] : SelectionManager.Views.map(dv => (this.layoutDocAcls ? dv.layoutDoc : dv.dataDoc));
+ const docs = DocumentView.Selected().length < 2 ? [this.selectedDoc] : DocumentView.Selected().map(dv => (this.layoutDocAcls ? dv.layoutDoc : dv.dataDoc));
SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs, this.layoutDocAcls);
};
@@ -352,9 +369,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
if (permission === '-multiple-') dropdownValues.unshift(permission);
return (
<select className="propertiesView-permissions-select" value={permission} onChange={e => this.changePermissions(e, user)}>
- {dropdownValues.map(permission => (
- <option className="propertiesView-permisssions-select" key={permission} value={permission}>
- {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
+ {dropdownValues.map(permissionVal => (
+ <option className="propertiesView-permisssions-select" key={permissionVal} value={permissionVal}>
+ {concat(ReverseHierarchyMap.get(permissionVal)?.image, ' ', permissionVal)}
</option>
))}
</select>
@@ -381,7 +398,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
return (
<div className="expansion-button">
<IconButton
- icon={<FontAwesomeIcon icon={'ellipsis-h'} />}
+ icon={<FontAwesomeIcon icon="ellipsis-h" />}
size={Size.XSMALL}
color={SettingsManager.userColor}
onClick={action(() => {
@@ -397,10 +414,8 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
/**
* @returns a row of the permissions panel
*/
- sharingItem(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) {
- if (name == Doc.CurrentUserEmail) {
- name = 'Me';
- }
+ sharingItem(nameIn: string, admin: boolean, permission: string, showExpansionIcon?: boolean) {
+ const name = nameIn === ClientUtils.CurrentUserEmail() ? 'Me' : nameIn;
return (
<div
className="propertiesView-sharingTable-item"
@@ -415,7 +430,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
{/* {name !== "Me" ? this.notifyIcon : null} */}
<div className="propertiesView-sharingTable-item-permission">
{this.colorACLDropDown(name, admin, permission, false)}
- {(permission === 'Owner' && name == 'Me') || showExpansionIcon ? this.expansionIcon : null}
+ {(permission === 'Owner' && name === 'Me') || showExpansionIcon ? this.expansionIcon : null}
</div>
</div>
);
@@ -425,10 +440,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
* @returns a colored dropdown bar reflective of the permission
*/
colorACLDropDown(name: string, admin: boolean, permission: string, showGuestOptions: boolean) {
- var shareImage = ReverseHierarchyMap.get(permission)?.image;
+ const shareImage = ReverseHierarchyMap.get(permission)?.image;
return (
<div>
- <div className={'propertiesView-shareDropDown'}>
+ <div className="propertiesView-shareDropDown">
<div className={`propertiesView-shareDropDown${permission}`}>
<div>{admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission, showGuestOptions) : concat(shareImage, ' ', permission)}</div>
</div>
@@ -440,9 +455,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
/**
* Sorting algorithm to sort users.
*/
- sortUsers = (u1: String, u2: String) => {
- return u1 > u2 ? -1 : u1 === u2 ? 0 : 1;
- };
+ sortUsers = (u1: String, u2: String) => (u1 > u2 ? -1 : u1 === u2 ? 0 : 1);
/**
* Sorting algorithm to sort groups.
@@ -458,45 +471,45 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
*/
@computed get sharingTable() {
// all selected docs
- const docs = SelectionManager.Views.length < 2 && this.selectedDoc ? [this.selectedDoc] : SelectionManager.Views.map(docView => docView.Document);
+ const docs = DocumentView.Selected().length < 2 && this.selectedDoc ? [this.selectedDoc] : DocumentView.SelectedDocs();
const target = docs[0];
- const showAdmin = GetEffectiveAcl(target) == AclAdmin;
+ const showAdmin = GetEffectiveAcl(target) === AclAdmin;
const individualTableEntries = [];
const usersAdded: string[] = []; // all shared users being added - organized by denormalized email
const seldoc = this.layoutDocAcls ? this.selectedLayoutDoc : this.selectedDoc?.[DocData];
// adds each user to usersAdded
SharingManager.Instance.users.forEach(eachUser => {
- var userOnDoc = true;
+ let userOnDoc = true;
if (seldoc) {
- if (Doc.GetT(seldoc, 'acl-' + normalizeEmail(eachUser.user.email), 'string', true) === '' || Doc.GetT(seldoc, 'acl-' + normalizeEmail(eachUser.user.email), 'string', true) === undefined) {
+ if (Doc.GetT(seldoc, 'acl_' + normalizeEmail(eachUser.user.email), 'string', true) === '' || Doc.GetT(seldoc, 'acl_' + normalizeEmail(eachUser.user.email), 'string', true) === undefined) {
userOnDoc = false;
}
}
- if (userOnDoc && !usersAdded.includes(eachUser.user.email) && eachUser.user.email !== 'guest' && eachUser.user.email != target.author) {
+ if (userOnDoc && !usersAdded.includes(eachUser.user.email) && eachUser.user.email !== 'guest' && eachUser.user.email !== target.author) {
usersAdded.push(eachUser.user.email);
}
});
// sorts and then adds each user to the table
usersAdded.sort(this.sortUsers);
- usersAdded.map(userEmail => {
- const userKey = `acl-${normalizeEmail(userEmail)}`;
- var aclField = Doc.GetT(this.layoutDocAcls ? target : Doc.GetProto(target), userKey, 'string', true);
- var permission = StrCast(aclField);
+ usersAdded.forEach(userEmail => {
+ const userKey = `acl_${normalizeEmail(userEmail)}`;
+ const aclField = Doc.GetT(this.layoutDocAcls ? target : Doc.GetProto(target), userKey, 'string', true);
+ const permission = StrCast(aclField);
individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user
});
// adds current user
- var userEmail = Doc.CurrentUserEmail;
- if (userEmail == 'guest') userEmail = 'Guest';
- const userKey = `acl-${normalizeEmail(userEmail)}`;
- if (!usersAdded.includes(userEmail) && userEmail !== 'Guest' && userEmail != target.author) {
- var permission;
+ let userEmail = ClientUtils.CurrentUserEmail();
+ if (userEmail === 'guest') userEmail = 'Guest';
+ const userKey = `acl_${normalizeEmail(userEmail)}`;
+ if (!usersAdded.includes(userEmail) && userEmail !== 'Guest' && userEmail !== target.author) {
+ let permission;
if (this.layoutDocAcls) {
if (target[DocAcl][userKey]) permission = HierarchyMapping.get(target[DocAcl][userKey])?.name;
- else if (target['embedContainer']) permission = StrCast(Doc.GetProto(DocCast(target['embedContainer']))[userKey]);
+ else if (target.embedContainer) permission = StrCast(Doc.GetProto(DocCast(target.embedContainer))[userKey]);
else permission = StrCast(Doc.GetProto(target)?.[userKey]);
} else permission = StrCast(target[userKey]);
individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user
@@ -509,15 +522,15 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
const groupTableEntries: JSX.Element[] = [];
const groupList = GroupManager.Instance?.allGroups || [];
groupList.sort(this.sortGroups);
- groupList.map(group => {
- if (group.title != 'Guest' && this.selectedDoc) {
- const groupKey = 'acl-' + normalizeEmail(StrCast(group.title));
- if (this.selectedDoc[groupKey] != '' && this.selectedDoc[groupKey] != undefined) {
- var permission;
+ groupList.forEach(group => {
+ if (group.title !== 'Guest' && this.selectedDoc) {
+ const groupKey = 'acl_' + normalizeEmail(StrCast(group.title));
+ if (this.selectedDoc[groupKey] !== '' && this.selectedDoc[groupKey] !== undefined) {
+ let permission;
if (this.layoutDocAcls) {
if (target[DocAcl][groupKey]) {
permission = HierarchyMapping.get(target[DocAcl][groupKey])?.name;
- } else if (target['embedContainer']) permission = StrCast(Doc.GetProto(DocCast(target['embedContainer']))[groupKey]);
+ } else if (target.embedContainer) permission = StrCast(Doc.GetProto(DocCast(target.embedContainer))[groupKey]);
else permission = StrCast(Doc.GetProto(target)?.[groupKey]);
} else permission = StrCast(target[groupKey]);
groupTableEntries.unshift(this.sharingItem(StrCast(group.title), showAdmin, permission!, false));
@@ -526,27 +539,27 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
});
// guest permission
- const guestPermission = StrCast((this.layoutDocAcls ? target : Doc.GetProto(target))['acl-Guest']);
+ const guestPermission = StrCast((this.layoutDocAcls ? target : Doc.GetProto(target)).acl_Guest);
return (
<div>
<div>
- <br></br> Individuals with Access to this Document
+ <br /> Individuals with Access to this Document
</div>
<div className="propertiesView-sharingTable" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
- {<div> {individualTableEntries}</div>}
+ <div> {individualTableEntries}</div>
</div>
{groupTableEntries.length > 0 ? (
<div>
<div>
- <br></br> Groups with Access to this Document
+ <br /> Groups with Access to this Document
</div>
<div className="propertiesView-sharingTable" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
- {<div> {groupTableEntries}</div>}
+ <div> {groupTableEntries}</div>
</div>
</div>
) : null}
- <br></br> Guest
+ <br /> Guest
<div>{this.colorACLDropDown('Guest', showAdmin, guestPermission!, true)}</div>
</div>
);
@@ -558,7 +571,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
@action
- toggleCheckbox = () => (this.layoutFields = !this.layoutFields);
+ toggleCheckbox = () => {
+ this.layoutFields = !this.layoutFields;
+ };
@computed get color() {
return SettingsManager.userColor;
@@ -574,25 +589,21 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get editableTitle() {
const titles = new Set<string>();
- SelectionManager.Views.forEach(dv => titles.add(StrCast(dv.Document.title)));
+ DocumentView.Selected().forEach(dv => titles.add(StrCast(dv.Document.title)));
const title = Array.from(titles.keys()).length > 1 ? '--multiple selected--' : StrCast(this.selectedDoc?.title);
return (
<div>
- <EditableText val={title} setVal={this.setTitle} color={this.color} type={Type.SEC} formLabel={'Title'} fillWidth />
+ <EditableText val={title} setVal={this.setTitle} color={this.color} type={Type.SEC} formLabel="Title" fillWidth />
{LinkManager.Instance.currentLinkAnchor ? (
<p className="propertiesView-titleExtender">
- <>
- <b>Anchor:</b>
- {LinkManager.Instance.currentLinkAnchor.title}
- </>
+ <b>Anchor:</b>
+ {StrCast(LinkManager.Instance.currentLinkAnchor.title)}
</p>
) : null}
{this.selectedLink?.title ? (
<p className="propertiesView-titleExtender">
- <>
- <b>Link:</b>
- {this.selectedLink.title}
- </>
+ <b>Link:</b>
+ {StrCast(this.selectedLink.title)}
</p>
) : null}
</div>
@@ -600,9 +611,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
@computed get currentType() {
- const documentType = StrCast(this.selectedDoc?.type);
- var currentType: string = documentType;
- var capitalizedDocType = Utils.cleanDocumentType(currentType as DocumentType);
+ const docType = StrCast(this.selectedDoc?.type) as DocumentType;
+ const colType = StrCast(this.selectedDoc?.type_collection) as CollectionViewType;
+ const capitalizedDocType = ClientUtils.cleanDocumentType(docType, colType);
return (
<div>
@@ -618,7 +629,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
@computed get currentComponent() {
- var iconName = StrCast(this.selectedDoc?.systemIcon);
+ const iconName = StrCast(this.selectedDoc?.systemIcon);
if (iconName) {
const Icon = Icons[iconName as keyof typeof Icons];
@@ -629,11 +640,11 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@undoBatch
setTitle = (value: string | number) => {
- if (SelectionManager.Views.length > 1) {
- SelectionManager.Views.map(dv => Doc.SetInPlace(dv.Document, 'title', value, true));
+ if (DocumentView.Selected().length > 1) {
+ DocumentView.Selected().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);
+ else Doc.SetField(this.dataDoc, 'title', value as string, true);
}
};
@@ -650,13 +661,11 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
const ys = ink.map(p => p.Y);
const left = Math.min(...xs);
const top = Math.min(...ys);
- const right = Math.max(...xs);
- const bottom = Math.max(...ys);
_centerPoints.push({ X: left, Y: top });
}
}
- var index = 0;
+ let index = 0;
if (doc.type === DocumentType.INK && doc.x && doc.y && layout._width && layout._height && doc.data) {
layout.rotation = NumCast(layout.rotation) + angle;
const inks = Cast(doc.stroke, InkField)?.inkData;
@@ -687,11 +696,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
get controlPointsButton() {
return (
<div className="inking-button">
- <Tooltip title={<div className="dash-tooltip">{'Edit points'}</div>}>
+ <Tooltip title={<div className="dash-tooltip">Edit points</div>}>
<div
className="inking-button-points"
style={{ backgroundColor: InkStrokeProperties.Instance._controlButton ? 'black' : '' }}
- onPointerDown={action(() => (InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton))}>
+ onPointerDown={action(() => {
+ InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton;
+ })}>
<FontAwesomeIcon icon="bezier-curve" size="lg" />
</div>
</Tooltip>
@@ -699,152 +710,129 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
);
}
- inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => {
- return (
- <div
- className="inputBox"
- style={{
- marginRight: title === 'X:' ? '19px' : '',
- marginLeft: title === '∠:' ? '39px' : '',
- }}>
- <div className="inputBox-title"> {title} </div>
- <input
- className="inputBox-input"
- type="text"
- value={value}
- style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
- onChange={e => setter(e.target.value)}
- onKeyDown={e => e.stopPropagation()}
- />
- <div className="inputBox-button">
- <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}>
- <FontAwesomeIcon icon="caret-up" size="sm" />
- </div>
- <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}>
- <FontAwesomeIcon icon="caret-down" size="sm" />
- </div>
+ inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => (
+ <div
+ className="inputBox"
+ style={{
+ marginRight: title === 'X:' ? '19px' : '',
+ marginLeft: title === '∠:' ? '39px' : '',
+ }}>
+ <div className="inputBox-title"> {title} </div>
+ <input className="inputBox-input" type="text" value={value} style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} onChange={e => setter(e.target.value)} onKeyDown={e => e.stopPropagation()} />
+ <div className="inputBox-button">
+ <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}>
+ <FontAwesomeIcon icon="caret-up" size="sm" />
+ </div>
+ <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}>
+ <FontAwesomeIcon icon="caret-down" size="sm" />
</div>
</div>
- );
- };
+ </div>
+ );
- inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => {
- return (
- <div className="inputBox-duo">
- {this.inputBox(key, value, setter, title1)}
- {title2 === '' ? null : this.inputBox(key2, value2, setter2, title2)}
- </div>
- );
- };
+ inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => (
+ <div className="inputBox-duo">
+ {this.inputBox(key, value, setter, title1)}
+ {title2 === '' ? null : this.inputBox(key2, value2, setter2, title2)}
+ </div>
+ );
@action
upDownButtons = (dirs: string, field: string) => {
const selDoc = this.selectedDoc;
if (!selDoc) return;
- //prettier-ignore
+ // prettier-ignore
switch (field) {
case 'Xps': selDoc.x = NumCast(this.selectedDoc?.x) + (dirs === 'up' ? 10 : -10); break;
case 'Yps': selDoc.y = NumCast(this.selectedDoc?.y) + (dirs === 'up' ? 10 : -10); break;
case 'stk': selDoc.stroke_width = NumCast(this.selectedDoc?.[DocData].stroke_width) + (dirs === 'up' ? 0.1 : -0.1); break;
- case 'wid':
- const oldWidth = NumCast(selDoc._width);
- const oldHeight = NumCast(selDoc._height);
- const oldX = NumCast(selDoc.x);
- const oldY = NumCast(selDoc.y);
- selDoc._width = oldWidth + (dirs === 'up' ? 10 : -10);
- if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) {
- const ink = Cast(selDoc.data, InkField)?.inkData;
- if (ink) {
- const newPoints: { X: number; Y: number }[] = [];
- for (var j = 0; j < ink.length; j++) {
- // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
- const newX = NumCast(selDoc.x) - oldX + (ink[j].X * NumCast(selDoc._width)) / oldWidth;
- const newY = NumCast(selDoc.y) - oldY + (ink[j].Y * NumCast(selDoc._height)) / oldHeight;
- newPoints.push({ X: newX, Y: newY });
+ case 'wid': {
+ const oldWidth = NumCast(selDoc._width);
+ const oldHeight = NumCast(selDoc._height);
+ const oldX = NumCast(selDoc.x);
+ const oldY = NumCast(selDoc.y);
+ selDoc._width = oldWidth + (dirs === 'up' ? 10 : -10);
+ if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) {
+ const ink = Cast(selDoc.data, InkField)?.inkData;
+ if (ink) {
+ const newPoints: { X: number; Y: number }[] = [];
+ for (let j = 0; j < ink.length; j++) {
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = NumCast(selDoc.x) - oldX + (ink[j].X * NumCast(selDoc._width)) / oldWidth;
+ const newY = NumCast(selDoc.y) - oldY + (ink[j].Y * NumCast(selDoc._height)) / oldHeight;
+ newPoints.push({ X: newX, Y: newY });
+ }
+ selDoc.data = new InkField(newPoints);
}
- selDoc.data = new InkField(newPoints);
}
}
break;
- case 'hgt':
- const oWidth = NumCast(selDoc._width);
- const oHeight = NumCast(selDoc._height);
- const oX = NumCast(selDoc.x);
- const oY = NumCast(selDoc.y);
- selDoc._height = oHeight + (dirs === 'up' ? 10 : -10);
- if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) {
- const ink = Cast(selDoc.data, InkField)?.inkData;
- if (ink) {
- const newPoints: { X: number; Y: number }[] = [];
- for (var j = 0; j < ink.length; j++) {
- // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
- const newX = NumCast(selDoc.x) - oX + (ink[j].X * NumCast(selDoc._width)) / oWidth;
- const newY = NumCast(selDoc.y) - oY + (ink[j].Y * NumCast(selDoc._height)) / oHeight;
- newPoints.push({ X: newX, Y: newY });
+ case 'hgt': {
+ const oWidth = NumCast(selDoc._width);
+ const oHeight = NumCast(selDoc._height);
+ const oX = NumCast(selDoc.x);
+ const oY = NumCast(selDoc.y);
+ selDoc._height = oHeight + (dirs === 'up' ? 10 : -10);
+ if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) {
+ const ink = Cast(selDoc.data, InkField)?.inkData;
+ if (ink) {
+ const newPoints: { X: number; Y: number }[] = [];
+ for (let j = 0; j < ink.length; j++) {
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = NumCast(selDoc.x) - oX + (ink[j].X * NumCast(selDoc._width)) / oWidth;
+ const newY = NumCast(selDoc.y) - oY + (ink[j].Y * NumCast(selDoc._height)) / oHeight;
+ newPoints.push({ X: newX, Y: newY });
+ }
+ selDoc.data = new InkField(newPoints);
}
- selDoc.data = new InkField(newPoints);
}
}
break;
+ default: { /* empty */ }
}
};
getField(key: string) {
- return Field.toString(this.selectedDoc?.[DocData][key] as Field);
+ return Field.toString(this.selectedDoc?.[DocData][key] as FieldType);
}
- @computed get shapeXps() {
- return NumCast(this.selectedDoc?.x);
- }
- @computed get shapeYps() {
- return NumCast(this.selectedDoc?.y);
- }
- @computed get shapeHgt() {
- return NumCast(this.selectedDoc?._height);
- }
- @computed get shapeWid() {
- return NumCast(this.selectedDoc?._width);
- }
- @computed get strokeThk() {
- return NumCast(this.selectedDoc?.[DocData].stroke_width);
- }
- set shapeXps(value) {
- this.selectedDoc && (this.selectedDoc.x = Math.round(value * 100) / 100);
- }
- set shapeYps(value) {
- this.selectedDoc && (this.selectedDoc.y = Math.round(value * 100) / 100);
- }
- set shapeWid(value) {
- this.selectedDoc && (this.selectedDoc._width = Math.round(value * 100) / 100);
- }
- set shapeHgt(value) {
- this.selectedDoc && (this.selectedDoc._height = Math.round(value * 100) / 100);
- }
- set strokeThk(value) {
- this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Math.round(value * 100) / 100);
- }
+ @computed get shapeXps() { return NumCast(this.selectedDoc?.x); } // prettier-ignore
+ set shapeXps(value) { this.selectedDoc && (this.selectedDoc.x = Math.round(value * 100) / 100); } // prettier-ignore
+ @computed get shapeYps() { return NumCast(this.selectedDoc?.y); } // prettier-ignore
+ set shapeYps(value) { this.selectedDoc && (this.selectedDoc.y = Math.round(value * 100) / 100); } // prettier-ignore
+ @computed get shapeWid() { return NumCast(this.selectedDoc?._width); } // prettier-ignore
+ set shapeWid(value) { this.selectedDoc && (this.selectedDoc._width = Math.round(value * 100) / 100); } // prettier-ignore
+ @computed get shapeHgt() { return NumCast(this.selectedDoc?._height); } // prettier-ignore
+ set shapeHgt(value) { this.selectedDoc && (this.selectedDoc._height = Math.round(value * 100) / 100); } // prettier-ignore
+ @computed get strokeThk(){ return NumCast(this.selectedDoc?.[DocData].stroke_width); } // prettier-ignore
+ set strokeThk(value) { this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Math.round(value * 100) / 100); } // prettier-ignore
@computed get hgtInput() {
return this.inputBoxDuo(
'hgt',
this.shapeHgt,
- undoable((val: string) => !isNaN(Number(val)) && (this.shapeHgt = +val), 'set height'),
+ undoable((val: string) => {
+ !Number(val) && (this.shapeHgt = +val);
+ }, 'set height'),
'H:',
'wid',
this.shapeWid,
- undoable((val: string) => !isNaN(Number(val)) && (this.shapeWid = +val), 'set width'),
+ undoable((val: string) => {
+ !isNaN(Number(val)) && (this.shapeWid = +val);
+ }, 'set width'),
'W:'
);
}
@computed get XpsInput() {
+ // prettier-ignore
return this.inputBoxDuo(
'Xps',
this.shapeXps,
- undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeXps = +val), 'set x coord'),
+ undoable((val: string) => { val !== '0' && !isNaN(Number(val)) && (this.shapeXps = +val); }, 'set x coord'),
'X:',
'Yps',
this.shapeYps,
- undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeYps = +val), 'set y coord'),
+ undoable((val: string) => { val !== '0' && !isNaN(Number(val)) && (this.shapeYps = +val); }, 'set y coord'),
'Y:'
);
}
@@ -854,22 +842,14 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
private _lastDash: any = '2';
- @computed get colorFil() {
- return StrCast(this.selectedDoc?.[DocData].fillColor);
- }
- @computed get colorStk() {
- return StrCast(this.selectedDoc?.[DocData].color);
- }
- set colorFil(value) {
- this.selectedDoc && (this.selectedDoc[DocData].fillColor = value ? value : undefined);
- }
- set colorStk(value) {
- this.selectedDoc && (this.selectedDoc[DocData].color = value ? value : undefined);
- }
+ @computed get colorFil() { return StrCast(this.selectedDoc?.[DocData].fillColor); } // prettier-ignore
+ set colorFil(value) { this.selectedDoc && (this.selectedDoc[DocData].fillColor = value || undefined); } // prettier-ignore
+ @computed get colorStk() { return StrCast(this.selectedDoc?.[DocData].color); } // prettier-ignore
+ set colorStk(value) { this.selectedDoc && (this.selectedDoc[DocData].color = value || undefined); } // prettier-ignore
colorButton(value: string, type: string, setter: () => void) {
return (
- <div className="color-button" key="color" onPointerDown={action(e => setter())}>
+ <div className="color-button" key="color" onPointerDown={action(() => setter())}>
<div
className="color-button-preview"
style={{
@@ -888,7 +868,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
return (
<SketchPicker
onChange={undoable(
- action((color: ColorResult) => setter(color.hex)),
+ action((col: ColorResult) => setter(col.hex)),
'set stroke color property'
)}
presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
@@ -911,10 +891,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
@computed get fillPicker() {
- return this.colorPicker(this.colorFil, (color: string) => (this.colorFil = color));
+ return this.colorPicker(this.colorFil, (color: string) => { this.colorFil = color; }); // prettier-ignore
}
@computed get linePicker() {
- return this.colorPicker(this.colorStk, (color: string) => (this.colorStk = color));
+ return this.colorPicker(this.colorStk, (color: string) => { this.colorStk = color; }); // prettier-ignore
}
@computed get strokeAndFill() {
@@ -936,60 +916,41 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
);
}
- @computed get dashdStk() {
- return this.selectedDoc?.stroke_dash || '';
- }
- @computed get widthStk() {
- return this.getField('stroke_width') || '1';
- }
- @computed get markScal() {
- return Number(this.getField('stroke_markerScale') || '1');
- }
- @computed get markHead() {
- return this.getField('stroke_startMarker') || '';
- }
- @computed get markTail() {
- return this.getField('stroke_endMarker') || '';
- }
+ @computed get dashdStk() { return this.selectedDoc?.stroke_dash || ''; } // prettier-ignore
set dashdStk(value) {
value && (this._lastDash = value);
this.selectedDoc && (this.selectedDoc[DocData].stroke_dash = value ? this._lastDash : undefined);
}
- set markScal(value) {
- this.selectedDoc && (this.selectedDoc[DocData].stroke_markerScale = Number(value));
- }
+ @computed get widthStk() { return this.getField('stroke_width') || '1'; } // prettier-ignore
set widthStk(value) {
this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Number(value));
}
+ @computed get markScal() { return Number(this.getField('stroke_markerScale') || '1'); } // prettier-ignore
+ set markScal(value) {
+ this.selectedDoc && (this.selectedDoc[DocData].stroke_markerScale = Number(value));
+ }
+ @computed get markHead() { return this.getField('stroke_startMarker') || ''; } // prettier-ignore
set markHead(value) {
this.selectedDoc && (this.selectedDoc[DocData].stroke_startMarker = value);
}
+ @computed get markTail() { return this.getField('stroke_endMarker') || ''; } // prettier-ignore
set markTail(value) {
this.selectedDoc && (this.selectedDoc[DocData].stroke_endMarker = value);
}
- @computed get stkInput() {
- return this.regInput('stk', this.widthStk, (val: string) => (this.widthStk = val));
- }
- @computed get markScaleInput() {
- return this.regInput('scale', this.markScal.toString(), (val: string) => (this.markScal = Number(val)));
- }
-
- regInput = (key: string, value: any, setter: (val: string) => {}) => {
- return (
- <div className="inputBox">
- <input className="inputBox-input" type="text" value={value} style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} onChange={e => setter(e.target.value)} />
- <div className="inputBox-button">
- <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}>
- <FontAwesomeIcon icon="caret-up" size="sm" />
- </div>
- <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}>
- <FontAwesomeIcon icon="caret-down" size="sm" />
- </div>
+ regInput = (key: string, value: any, setter: (val: string) => {}) => (
+ <div className="inputBox">
+ <input className="inputBox-input" type="text" value={value} style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} onChange={e => setter(e.target.value)} />
+ <div className="inputBox-button">
+ <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}>
+ <FontAwesomeIcon icon="caret-up" size="sm" />
+ </div>
+ <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}>
+ <FontAwesomeIcon icon="caret-down" size="sm" />
</div>
</div>
- );
- };
+ </div>
+ );
@action
CloseAll = () => {
@@ -1005,18 +966,55 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get widthAndDash() {
return (
+ // prettier-ignore
<div className="widthAndDash">
- <div className="width">{this.getNumber('Thickness', '', 0, Math.max(50, this.strokeThk), this.strokeThk, (val: number) => !isNaN(val) && (this.strokeThk = val), 50, 1)}</div>
- <div className="width">{this.getNumber('Arrow Scale', '', 0, Math.max(10, this.markScal), this.markScal, (val: number) => !isNaN(val) && (this.markScal = val), 10, 1)}</div>
+ <div className="width">
+ {this.getNumber(
+ 'Thickness',
+ '',
+ 0,
+ Math.max(50, this.strokeThk),
+ this.strokeThk,
+ (val: number) => { !isNaN(val) && (this.strokeThk = val); },
+ 50,
+ 1
+ )}
+ </div>
+ <div className="width">
+ {this.getNumber(
+ 'Arrow Scale',
+ '',
+ 0,
+ Math.max(10, this.markScal),
+ this.markScal,
+ (val: number) => { !isNaN(val) && (this.markScal = val); },
+ 10,
+ 1
+ )}
+ </div>
<div className="arrows">
<div className="arrows-head">
<div className="arrows-head-title">Arrow Head: </div>
- <input key="markHead" className="arrows-head-input" type="checkbox" checked={this.markHead !== ''} onChange={undoBatch(action(() => (this.markHead = this.markHead ? '' : 'arrow')))} />
+ <input
+ key="markHead"
+ className="arrows-head-input"
+ type="checkbox"
+ checked={this.markHead !== ''}
+ onChange={undoBatch(action(() => { this.markHead = this.markHead ? '' : 'arrow'; }))}
+ />
</div>
<div className="arrows-tail">
<div className="arrows-tail-title">Arrow End: </div>
- <input key="markTail" className="arrows-tail-input" type="checkbox" checked={this.markTail !== ''} onChange={undoBatch(action(() => (this.markTail = this.markTail ? '' : 'arrow')))} />
+ <input
+ key="markTail"
+ className="arrows-tail-input"
+ type="checkbox"
+ checked={this.markTail !== ''}
+ onChange={undoBatch(
+ action(() => { this.markTail = this.markTail ? '' : 'arrow'; })
+ )}
+ />
</div>
</div>
<div className="dashed">
@@ -1045,50 +1043,53 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
setFinalNumber = () => {
this._sliderBatch?.end();
};
- getNumber = (label: string, unit: string, min: number, max: number, number: number, setNumber: any, autorange?: number, autorangeMinVal?: number) => {
- return (
- <div key={label + this.selectedDoc?.title}>
- <NumberInput formLabel={label} formLabelPlacement={'left'} type={Type.SEC} unit={unit} fillWidth color={this.color} number={number} setNumber={setNumber} min={min} max={max} />
- <Slider
- key={label}
- onPointerDown={e => (this._sliderBatch = UndoManager.StartBatch('slider ' + label))}
- multithumb={false}
- color={this.color}
- size={Size.XSMALL}
- min={min}
- max={max}
- autorangeMinVal={autorangeMinVal}
- autorange={autorange}
- number={number}
- unit={unit}
- decimals={1}
- setFinalNumber={this.setFinalNumber}
- setNumber={setNumber}
- fillWidth
- />
- </div>
- );
- };
+ getNumber = (label: string, unit: string, min: number, max: number, number: number, setNumber: any, autorange?: number, autorangeMinVal?: number) => (
+ <div key={label + (this.selectedDoc?.title ?? '')}>
+ <NumberInput formLabel={label} formLabelPlacement="left" type={Type.SEC} unit={unit} fillWidth color={this.color} number={number} setNumber={setNumber} min={min} max={max} />
+ <Slider
+ key={label}
+ onPointerDown={() => {
+ this._sliderBatch = UndoManager.StartBatch('slider ' + label);
+ }}
+ multithumb={false}
+ color={this.color}
+ size={Size.XSMALL}
+ min={min}
+ max={max}
+ autorangeMinVal={autorangeMinVal}
+ autorange={autorange}
+ number={number}
+ unit={unit}
+ decimals={1}
+ setFinalNumber={this.setFinalNumber}
+ setNumber={setNumber}
+ fillWidth
+ />
+ </div>
+ );
+ setVal = (func: (doc: Doc, val: number) => void) => (val: number) => this.selectedDoc && !isNaN(val) && func(this.selectedDoc, val);
@computed get transformEditor() {
return (
+ // prettier-ignore
<div className="transform-editor">
- {!this.isStack ? null : this.getNumber('Gap', ' px', 0, 200, NumCast(this.selectedDoc!.gridGap), (val: number) => !isNaN(val) && (this.selectedDoc!.gridGap = val))}
- {!this.isStack ? null : this.getNumber('xMargin', ' px', 0, 500, NumCast(this.selectedDoc!.xMargin), (val: number) => !isNaN(val) && (this.selectedDoc!.xMargin = val))}
- {!this.isStack ? null : this.getNumber('yMargin', ' px', 0, 500, NumCast(this.selectedDoc!.yMargin), (val: number) => !isNaN(val) && (this.selectedDoc!.yMargin = val))}
- {!this.isGroup ? null : this.getNumber('Padding', ' px', 0, 500, NumCast(this.selectedDoc!.xPadding), (val: number) => !isNaN(val) && (this.selectedDoc!.xPadding = this.selectedDoc!.yPadding = val))}
+ {!this.isStack ? null : this.getNumber('Gap', ' px', 0, 200, NumCast(this.selectedDoc!.gridGap), this.setVal((doc: Doc, val: number) => { doc.gridGap = val; })) }
+ {!this.isStack ? null : this.getNumber('xMargin', ' px', 0, 500, NumCast(this.selectedDoc!.xMargin), this.setVal((doc: Doc, val: number) => { doc.xMargin = val; })) }
+ {!this.isStack ? null : this.getNumber('yMargin', ' px', 0, 500, NumCast(this.selectedDoc!.yMargin), this.setVal((doc: Doc, val: number) => { doc.yMargin = val; })) }
+ {!this.isGroup ? null : this.getNumber('Padding', ' px', 0, 500, NumCast(this.selectedDoc!.xPadding), this.setVal((doc: Doc, val: number) => { doc.xPadding = doc.yPadding = val; })) }
{this.isInk ? this.controlPointsButton : null}
- {this.getNumber('Width', ' px', 0, Math.max(1000, this.shapeWid), this.shapeWid, (val: number) => !isNaN(val) && (this.shapeWid = val), 1000, 1)}
- {this.getNumber('Height', ' px', 0, Math.max(1000, this.shapeHgt), this.shapeHgt, (val: number) => !isNaN(val) && (this.shapeHgt = val), 1000, 1)}
- {this.getNumber('X', ' px', this.shapeXps - 500, this.shapeXps + 500, this.shapeXps, (val: number) => !isNaN(val) && (this.shapeXps = val), 1000)}
- {this.getNumber('Y', ' px', this.shapeYps - 500, this.shapeYps + 500, this.shapeYps, (val: number) => !isNaN(val) && (this.shapeYps = val), 1000)}
+ {this.getNumber('Width', ' px', 0, Math.max(1000, this.shapeWid), this.shapeWid, this.setVal((doc: Doc, val:number) => {this.shapeWid = val}), 1000, 1)}
+ {this.getNumber('Height', ' px', 0, Math.max(1000, this.shapeHgt), this.shapeHgt, this.setVal((doc: Doc, val:number) => {this.shapeHgt = val}), 1000, 1)}
+ {this.getNumber('X', ' px', this.shapeXps - 500, this.shapeXps + 500, this.shapeXps, this.setVal((doc: Doc, val:number) => {this.shapeXps = val}), 1000)}
+ {this.getNumber('Y', ' px', this.shapeYps - 500, this.shapeYps + 500, this.shapeYps, this.setVal((doc: Doc, val:number) => {this.shapeYps = val}), 1000)}
</div>
);
}
@computed get optionsSubMenu() {
return (
- <PropertiesSection title="Options" inSection={this.inOptions} isOpen={this.openOptions} setInSection={bool => (this.inOptions = bool)} setIsOpen={bool => (this.openOptions = bool)} onDoubleClick={this.CloseAll}>
+ // prettier-ignore
+ <PropertiesSection title="Options" isOpen={this.openOptions} setIsOpen={bool => { this.openOptions = bool; }} onDoubleClick={this.CloseAll}>
<PropertiesButtons />
</PropertiesSection>
);
@@ -1096,12 +1097,23 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get sharingSubMenu() {
return (
- <PropertiesSection title="Sharing and Permissions" isOpen={this.openSharing} setIsOpen={bool => (this.openSharing = bool)} onDoubleClick={() => this.CloseAll()}>
+ // prettier-ignore
+ <PropertiesSection
+ title="Sharing and Permissions"
+ isOpen={this.openSharing}
+ setIsOpen={bool => { this.openSharing = bool; }}
+ onDoubleClick={this.CloseAll}>
<>
{/* <div className="propertiesView-buttonContainer"> */}
<div className="propertiesView-acls-checkbox">
Layout Permissions
- <Checkbox color="primary" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} />
+ <Checkbox
+ color="primary"
+ onChange={action(() => {
+ this.layoutDocAcls = !this.layoutDocAcls;
+ })}
+ checked={this.layoutDocAcls}
+ />
</div>
{/* <Tooltip title={<><div className="dash-tooltip">{"Re-distribute sharing settings"}</div></>}>
<button onPointerDown={() => SharingManager.Instance.distributeOverCollection(this.selectedDoc!)}>
@@ -1141,7 +1153,8 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get filtersSubMenu() {
return (
- <PropertiesSection title="Filters" isOpen={this.openFilters} setIsOpen={bool => (this.openFilters = bool)} onDoubleClick={() => this.CloseAll()}>
+ // prettier-ignore
+ <PropertiesSection title="Filters" isOpen={this.openFilters} setIsOpen={bool => { this.openFilters = bool; }} onDoubleClick={this.CloseAll}>
<div className="propertiesView-content filters" style={{ position: 'relative', height: 'auto' }}>
<FilterPanel Document={this.selectedDoc ?? Doc.ActiveDashboard!} />
</div>
@@ -1151,11 +1164,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get inkSubMenu() {
return (
+ // prettier-ignore
<>
- <PropertiesSection title="Appearance" isOpen={this.openAppearance} setIsOpen={bool => (this.openAppearance = bool)} onDoubleClick={() => this.CloseAll()}>
+ <PropertiesSection title="Appearance" isOpen={this.openAppearance} setIsOpen={bool => { this.openAppearance = bool; }} onDoubleClick={this.CloseAll}>
{this.selectedLayoutDoc?.layout_isSvg ? this.appearanceEditor : null}
</PropertiesSection>
- <PropertiesSection title="Transform" isOpen={this.openTransform} setIsOpen={bool => (this.openTransform = bool)} onDoubleClick={() => this.CloseAll()}>
+ <PropertiesSection title="Transform" isOpen={this.openTransform} setIsOpen={bool => { this.openTransform = bool; }} onDoubleClick={this.CloseAll}>
{this.transformEditor}
</PropertiesSection>
</>
@@ -1164,7 +1178,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get fieldsSubMenu() {
return (
- <PropertiesSection title="Fields & Tags" isOpen={this.openFields} setIsOpen={bool => (this.openFields = bool)} onDoubleClick={() => this.CloseAll()}>
+ <PropertiesSection
+ title="Fields & Tags"
+ isOpen={this.openFields}
+ setIsOpen={bool => {
+ this.openFields = bool;
+ }}
+ onDoubleClick={this.CloseAll}>
<div className="propertiesView-content fields">{Doc.noviceMode ? this.noviceFields : this.expandedField}</div>
</PropertiesSection>
);
@@ -1172,7 +1192,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get contextsSubMenu() {
return (
- <PropertiesSection title="Other Contexts" isOpen={this.openContexts} setIsOpen={bool => (this.openContexts = bool)} onDoubleClick={() => this.CloseAll()}>
+ <PropertiesSection
+ title="Other Contexts"
+ isOpen={this.openContexts}
+ setIsOpen={bool => {
+ this.openContexts = bool;
+ }}
+ onDoubleClick={this.CloseAll}>
{this.contextCount > 0 ? this.contexts : 'There are no other contexts.'}
</PropertiesSection>
);
@@ -1180,7 +1206,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get linksSubMenu() {
return (
- <PropertiesSection title="Linked To" isOpen={this.openLinks} setIsOpen={bool => (this.openLinks = bool)} onDoubleClick={this.CloseAll}>
+ <PropertiesSection
+ title="Linked To"
+ isOpen={this.openLinks}
+ setIsOpen={bool => {
+ this.openLinks = bool;
+ }}
+ onDoubleClick={this.CloseAll}>
{this.linkCount > 0 ? this.links : 'There are no current links.'}
</PropertiesSection>
);
@@ -1188,14 +1220,20 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get layoutSubMenu() {
return (
- <PropertiesSection title="Layout" isOpen={this.openLayout} setIsOpen={bool => (this.openLayout = bool)} onDoubleClick={this.CloseAll}>
+ <PropertiesSection
+ title="Layout"
+ isOpen={this.openLayout}
+ setIsOpen={bool => {
+ this.openLayout = bool;
+ }}
+ onDoubleClick={this.CloseAll}>
{this.layoutPreview}
</PropertiesSection>
);
}
@computed get description() {
- return Field.toString(this.selectedLink?.link_description as any as Field);
+ return Field.toString(this.selectedLink?.link_description as any as FieldType);
}
@computed get relationship() {
return StrCast(this.selectedLink?.link_relationship);
@@ -1250,12 +1288,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
// if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes
} else if (linkRelationshipList && value !== prevRelationship) {
const index = linkRelationshipList.indexOf(value);
- //increment size of new relationship size
+ // increment size of new relationship size
if (index !== -1 && index < linkRelationshipSizes.length) {
const pvalue = linkRelationshipSizes[index];
linkRelationshipSizes[index] = pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1;
}
- //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation)
+ // decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation)
if (linkRelationshipList.includes(prevRelationship)) {
const pindex = linkRelationshipList.indexOf(prevRelationship);
if (pindex !== -1 && pindex < linkRelationshipSizes.length) {
@@ -1265,21 +1303,25 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
}
this.relationshipButtonColor = 'rgb(62, 133, 55)';
- setTimeout(
- action(() => (this.relationshipButtonColor = '')),
- 750
- );
+ setTimeout(action(() => { this.relationshipButtonColor = ''; }), 750); // prettier-ignore
return true;
}
+ return undefined;
});
- changeFollowBehavior = undoable((loc: Opt<string>) => this.sourceAnchor && (this.sourceAnchor.followLinkLocation = loc), 'change follow behavior');
+ changeFollowBehavior = undoable((loc: Opt<string>) => {
+ this.sourceAnchor && (this.sourceAnchor.followLinkLocation = loc);
+ }, 'change follow behavior');
@undoBatch
- changeAnimationBehavior = action((behavior: string) => this.sourceAnchor && (this.sourceAnchor.followLinkAnimEffect = behavior));
+ changeAnimationBehavior = action((behavior: string) => {
+ this.sourceAnchor && (this.sourceAnchor.followLinkAnimEffect = behavior);
+ });
@undoBatch
- changeEffectDirection = action((effect: PresEffectDirection) => this.sourceAnchor && (this.sourceAnchor.followLinkAnimDirection = effect));
+ changeEffectDirection = action((effect: PresEffectDirection) => {
+ this.sourceAnchor && (this.sourceAnchor.followLinkAnimDirection = effect);
+ });
animationDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => {
const lanch = this.sourceAnchor;
@@ -1300,7 +1342,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
document.getElementById('link_description_input')?.blur();
};
- onDescriptionKey = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
+ onDescriptionKey = () => {
// if (e.key === 'Enter') {
// this.setDescripValue(this.description);
// document.getElementById('link_description_input')?.blur();
@@ -1320,20 +1362,26 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
};
toggleLinkProp = (e: React.PointerEvent, prop: string) => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedLink && (this.selectedLink[prop] = !this.selectedLink[prop]))));
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ emptyFunction,
+ undoBatch(action(() => { this.selectedLink && (this.selectedLink[prop] = !this.selectedLink[prop]); })) // prettier-ignore
+ );
};
@computed get destinationAnchor() {
const ldoc = this.selectedLink;
const lanch = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor;
- if (ldoc && lanch) return LinkManager.getOppositeAnchor(ldoc, lanch) ?? lanch;
+ if (ldoc && lanch) return Doc.getOppositeAnchor(ldoc, lanch) ?? lanch;
return ldoc ? DocCast(ldoc.link_anchor_2) : ldoc;
}
@computed get sourceAnchor() {
const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor;
- return selAnchor ?? (this.selectedLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(this.selectedLink, this.destinationAnchor) : this.selectedLink);
+ return selAnchor ?? (this.selectedLink && this.destinationAnchor ? Doc.getOppositeAnchor(this.selectedLink, this.destinationAnchor) : this.selectedLink);
}
toggleAnchorProp = (e: React.PointerEvent, prop: string, anchor?: Doc, value: any = true, ovalue: any = false, cb: (val: any) => any = val => val) => {
@@ -1343,12 +1391,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
e,
returnFalse,
emptyFunction,
- undoBatch(
- action(() => {
- anchor[prop] = anchor[prop] === value ? ovalue : value;
- this.selectedDoc && cb(anchor[prop]);
- })
- )
+ undoBatch(action(() => {
+ anchor[prop] = anchor[prop] === value ? ovalue : value;
+ this.selectedDoc && cb(anchor[prop]);
+ })) // prettier-ignore
);
};
@@ -1357,7 +1403,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
return (
<input
style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
- autoComplete={'off'}
+ autoComplete="off"
id="link_relationship_input"
value={StrCast(this.selectedLink?.link_relationship)}
onKeyDown={this.onRelationshipKey}
@@ -1398,7 +1444,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
const zoom = Number((NumCast(this.sourceAnchor?.followLinkZoomScale, 1) * 100).toPrecision(3));
const targZoom = this.sourceAnchor?.followLinkZoom;
const indent = 30;
- const hasSelectedAnchor = this.selectedLink !== this.selectedDoc && LinkManager.Links(this.sourceAnchor).includes(this.selectedLink!);
+ const hasSelectedAnchor = this.selectedLink !== this.selectedDoc && Doc.Links(this.sourceAnchor).includes(this.selectedLink!);
return (
<>
@@ -1459,11 +1505,14 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
'10',
NumCast(this.sourceAnchor?.followLinkTransitionTime) / 1000,
true,
- (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.followLinkTransitionTime = timeInMS)),
+ (val: string) =>
+ PresBox.SetTransitionTime(val, (timeInMS: number) => {
+ this.sourceAnchor && (this.sourceAnchor.followLinkTransitionTime = timeInMS);
+ }),
indent
)}{' '}
<div
- className={'slider-headers'}
+ className="slider-headers"
style={{
display: 'grid',
justifyContent: 'space-between',
@@ -1478,111 +1527,147 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
</div>{' '}
<div className="propertiesView-input inline">
<p>Play Target Audio</p>
- <button
- style={{ background: !this.sourceAnchor?.followLinkAudio ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleAnchorProp(e, 'followLinkAudio', this.sourceAnchor)}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="button"
+ style={{ background: !this.sourceAnchor?.followLinkAudio ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkAudio', this.sourceAnchor)}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ }
</div>
<div className="propertiesView-input inline">
<p>Play Target Video</p>
- <button
- style={{ background: !this.sourceAnchor?.followLinkVideo ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleAnchorProp(e, 'followLinkVideo', this.sourceAnchor)}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="button"
+ style={{ background: !this.sourceAnchor?.followLinkVideo ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkVideo', this.sourceAnchor)}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ }
</div>
<div className="propertiesView-input inline">
<p>Zoom Text Selections</p>
- <button
- style={{ background: !this.sourceAnchor?.followLinkZoomText ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoomText', this.sourceAnchor)}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="button"
+ style={{ background: !this.sourceAnchor?.followLinkZoomText ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoomText', this.sourceAnchor)}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ }
</div>
<div className="propertiesView-input inline">
<p>Toggle Follow to Outer Context</p>
- <button
- style={{ background: !this.sourceAnchor?.followLinkToOuterContext ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToOuterContext', this.sourceAnchor)}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faWindowMaximize as IconLookup} size="lg" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="button"
+ style={{ background: !this.sourceAnchor?.followLinkToOuterContext ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToOuterContext', this.sourceAnchor)}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faWindowMaximize as IconLookup} size="lg" />
+ </button>
+ }
</div>
<div className="propertiesView-input inline">
<p>Toggle Target (Show/Hide)</p>
- <button
- style={{ background: !this.sourceAnchor?.followLinkToggle ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToggle', this.sourceAnchor)}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="button"
+ style={{ background: !this.sourceAnchor?.followLinkToggle ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToggle', this.sourceAnchor)}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ }
</div>
<div className="propertiesView-input inline">
<p>Ease Transitions</p>
- <button
- style={{ background: this.sourceAnchor?.followLinkEase === 'linear' ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleAnchorProp(e, 'followLinkEase', this.sourceAnchor, 'ease', 'linear')}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="button"
+ style={{ background: this.sourceAnchor?.followLinkEase === 'linear' ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkEase', this.sourceAnchor, 'ease', 'linear')}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ }
</div>
<div className="propertiesView-input inline">
<p>Capture Offset to Target</p>
- <button
- style={{ background: this.sourceAnchor?.followLinkXoffset === undefined ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => {
- this.toggleAnchorProp(e, 'followLinkXoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.x) - NumCast(this.sourceAnchor?.x), undefined);
- this.toggleAnchorProp(e, 'followLinkYoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.y) - NumCast(this.sourceAnchor?.y), undefined);
- }}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="button"
+ style={{ background: this.sourceAnchor?.followLinkXoffset === undefined ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => {
+ this.toggleAnchorProp(e, 'followLinkXoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.x) - NumCast(this.sourceAnchor?.x), undefined);
+ this.toggleAnchorProp(e, 'followLinkYoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.y) - NumCast(this.sourceAnchor?.y), undefined);
+ }}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ }
</div>
<div className="propertiesView-input inline">
<p>Center Target (no zoom)</p>
- <button
- style={{ background: this.sourceAnchor?.followLinkZoom ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="button"
+ style={{ background: this.sourceAnchor?.followLinkZoom ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ }
</div>
<div className="propertiesView-input inline" style={{ display: 'grid', gridTemplateColumns: '78px calc(100% - 108px) 50px' }}>
<p>Zoom %</p>
<div className="ribbon-property" style={{ display: !targZoom ? 'none' : 'inline-flex' }}>
- <input className="presBox-input" style={{ width: '100%', color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} readOnly={true} type="number" value={zoom} />
+ <input className="presBox-input" style={{ width: '100%', color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} readOnly type="number" value={zoom} />
<div className="ribbon-propertyUpDown" style={{ display: 'flex', flexDirection: 'column' }}>
<div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), 0.1))}>
- <FontAwesomeIcon icon={'caret-up'} />
+ <FontAwesomeIcon icon="caret-up" />
</div>
<div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), -0.1))}>
- <FontAwesomeIcon icon={'caret-down'} />
+ <FontAwesomeIcon icon="caret-down" />
</div>
</div>
</div>
- <button
- style={{ background: !targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? '' : '#4476f7', borderRadius: 3, gridColumn: 3 }}
- onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button
+ type="button"
+ style={{ background: !targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? '' : '#4476f7', borderRadius: 3, gridColumn: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
+ </button>
+ }
</div>
{!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)}
<div
- className={'slider-headers'}
+ className="slider-headers"
style={{
display: !targZoom ? 'none' : 'grid',
justifyContent: 'space-between',
@@ -1594,7 +1679,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}}>
<div className="slider-text">0%</div>
<div className="slider-text">100%</div>
- </div>{' '}
+ </div>
</div>
)}
</>
@@ -1622,128 +1707,136 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
</div>
</div>
);
- } else {
- if (this.selectedDoc && !this.isPres) {
- return (
- <div
- className="propertiesView"
- style={{
- background: SettingsManager.userBackgroundColor,
- color: SettingsManager.userColor,
- width: this._props.width,
- minWidth: this._props.width,
- }}>
- <div className="propertiesView-propAndInfoGrouping">
- <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
- Properties
- <div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties')}>
- <IconButton icon={<FontAwesomeIcon icon="info-circle" />} color={SettingsManager.userColor} />
- </div>
+ }
+ if (this.selectedDoc && !this.isPres) {
+ return (
+ <div
+ className="propertiesView"
+ style={{
+ background: SettingsManager.userBackgroundColor,
+ color: SettingsManager.userColor,
+ width: this._props.width,
+ minWidth: this._props.width,
+ }}>
+ <div className="propertiesView-propAndInfoGrouping">
+ <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
+ Properties
+ <div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties')}>
+ <IconButton icon={<FontAwesomeIcon icon="info-circle" />} color={SettingsManager.userColor} />
</div>
</div>
+ </div>
- <div className="propertiesView-name">{this.editableTitle}</div>
- <div className="propertiesView-type"> {this.currentType} </div>
- {this.fieldsSubMenu}
- {this.optionsSubMenu}
- {this.linksSubMenu}
- {!this.selectedLink || !this.openLinks ? null : this.linkProperties}
- {this.inkSubMenu}
- {this.contextsSubMenu}
- {isNovice ? null : this.sharingSubMenu}
- {this.filtersSubMenu}
- {isNovice ? null : this.layoutSubMenu}
+ <div className="propertiesView-name">{this.editableTitle}</div>
+ <div className="propertiesView-type"> {this.currentType} </div>
+ {this.fieldsSubMenu}
+ {this.optionsSubMenu}
+ {this.linksSubMenu}
+ {!this.selectedLink || !this.openLinks ? null : this.linkProperties}
+ {this.inkSubMenu}
+ {this.contextsSubMenu}
+ {isNovice ? null : this.sharingSubMenu}
+ {this.filtersSubMenu}
+ {isNovice ? null : this.layoutSubMenu}
+ </div>
+ );
+ }
+ if (this.isPres && PresBox.Instance) {
+ const selectedItem: boolean = PresBox.Instance.selectedArray.size > 0;
+ const type = [DocumentType.AUDIO, DocumentType.VID].find(dt => dt === DocCast(PresBox.Instance.activeItem?.annotationOn)?.type)
+ ? DocCast(PresBox.Instance.activeItem?.annotationOn)?.type
+ : PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type;
+ return (
+ <div className="propertiesView" style={{ width: this._props.width }}>
+ <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
+ Presentation
</div>
- );
- }
- if (this.isPres && PresBox.Instance) {
- const selectedItem: boolean = PresBox.Instance.selectedArray.size > 0;
- const type = [DocumentType.AUDIO, DocumentType.VID].includes(DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType)
- ? (DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType)
- : PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type;
- return (
- <div className="propertiesView" style={{ width: this._props.width }}>
- <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
- Presentation
+ <div className="propertiesView-name" style={{ borderBottom: 0 }}>
+ {this.editableTitle}
+ <div className="propertiesView-presSelected">
+ <div className="propertiesView-selectedCount">{PresBox.Instance.selectedArray.size} selected</div>
+ <div className="propertiesView-selectedList">{PresBox.Instance.listOfSelected}</div>
</div>
- <div className="propertiesView-name" style={{ borderBottom: 0 }}>
- {this.editableTitle}
- <div className="propertiesView-presSelected">
- <div className="propertiesView-selectedCount">{PresBox.Instance.selectedArray.size} selected</div>
- <div className="propertiesView-selectedList">{PresBox.Instance.listOfSelected}</div>
- </div>
- </div>
- {!selectedItem ? null : (
- <div className="propertiesView-presentationTrails">
- <div
- className="propertiesView-presentationTrails-title"
- onPointerDown={action(() => (this.openPresTransitions = !this.openPresTransitions))}
- style={{
- color: SettingsManager.userColor,
- backgroundColor: this.openPresTransitions ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
- }}>
- &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Transitions
- <div className="propertiesView-presentationTrails-title-icon">
- <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" />
- </div>
+ </div>
+ {!selectedItem ? null : (
+ <div className="propertiesView-presentationTrails">
+ <div
+ className="propertiesView-presentationTrails-title"
+ onPointerDown={action(() => {
+ this.openPresTransitions = !this.openPresTransitions;
+ })}
+ style={{
+ color: SettingsManager.userColor,
+ backgroundColor: this.openPresTransitions ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+ }}>
+ &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon="rocket" /> &nbsp; Transitions
+ <div className="propertiesView-presentationTrails-title-icon">
+ <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" />
</div>
- {this.openPresTransitions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.transitionDropdown}</div> : null}
</div>
- )}
- {!selectedItem ? null : (
- <div className="propertiesView-presentationTrails">
- <div
- className="propertiesView-presentationTrails-title"
- onPointerDown={action(() => (this.openPresVisibilityAndDuration = !this.openPresVisibilityAndDuration))}
- style={{
- color: SettingsManager.userColor,
- backgroundColor: this.openPresVisibilityAndDuration ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
- }}>
- &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Visibility
- <div className="propertiesView-presentationTrails-title-icon">
- <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" />
- </div>
+ {this.openPresTransitions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.transitionDropdown}</div> : null}
+ </div>
+ )}
+ {!selectedItem ? null : (
+ <div className="propertiesView-presentationTrails">
+ <div
+ className="propertiesView-presentationTrails-title"
+ onPointerDown={action(() => {
+ this.openPresVisibilityAndDuration = !this.openPresVisibilityAndDuration;
+ })}
+ style={{
+ color: SettingsManager.userColor,
+ backgroundColor: this.openPresVisibilityAndDuration ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+ }}>
+ &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon="rocket" /> &nbsp; Visibility
+ <div className="propertiesView-presentationTrails-title-icon">
+ <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" />
</div>
- {this.openPresVisibilityAndDuration ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.visibilityDurationDropdown}</div> : null}
</div>
- )}
- {!selectedItem ? null : (
- <div className="propertiesView-presentationTrails">
- <div
- className="propertiesView-presentationTrails-title"
- onPointerDown={action(() => (this.openPresProgressivize = !this.openPresProgressivize))}
- style={{
- color: SettingsManager.userColor,
- backgroundColor: this.openPresProgressivize ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
- }}>
- &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Progressivize
- <div className="propertiesView-presentationTrails-title-icon">
- <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" />
- </div>
+ {this.openPresVisibilityAndDuration ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.visibilityDurationDropdown}</div> : null}
+ </div>
+ )}
+ {!selectedItem ? null : (
+ <div className="propertiesView-presentationTrails">
+ <div
+ className="propertiesView-presentationTrails-title"
+ onPointerDown={action(() => {
+ this.openPresProgressivize = !this.openPresProgressivize;
+ })}
+ style={{
+ color: SettingsManager.userColor,
+ backgroundColor: this.openPresProgressivize ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+ }}>
+ &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon="rocket" /> &nbsp; Progressivize
+ <div className="propertiesView-presentationTrails-title-icon">
+ <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" />
</div>
- {this.openPresProgressivize ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.progressivizeDropdown}</div> : null}
</div>
- )}
- {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? null : (
- <div className="propertiesView-presentationTrails">
- <div
- className="propertiesView-presentationTrails-title"
- onPointerDown={action(() => (this.openSlideOptions = !this.openSlideOptions))}
- style={{
- color: SettingsManager.userColor,
- backgroundColor: this.openSlideOptions ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
- }}>
- &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={type === DocumentType.AUDIO ? 'file-audio' : 'file-video'} /> &nbsp; {type === DocumentType.AUDIO ? 'Audio Options' : 'Video Options'}
- <div className="propertiesView-presentationTrails-title-icon">
- <FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" />
- </div>
+ {this.openPresProgressivize ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.progressivizeDropdown}</div> : null}
+ </div>
+ )}
+ {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? null : (
+ <div className="propertiesView-presentationTrails">
+ <div
+ className="propertiesView-presentationTrails-title"
+ onPointerDown={action(() => {
+ this.openSlideOptions = !this.openSlideOptions;
+ })}
+ style={{
+ color: SettingsManager.userColor,
+ backgroundColor: this.openSlideOptions ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+ }}>
+ &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={type === DocumentType.AUDIO ? 'file-audio' : 'file-video'} /> &nbsp; {type === DocumentType.AUDIO ? 'Audio Options' : 'Video Options'}
+ <div className="propertiesView-presentationTrails-title-icon">
+ <FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" />
</div>
- {this.openSlideOptions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.mediaOptionsDropdown}</div> : null}
</div>
- )}
- </div>
- );
- }
+ {this.openSlideOptions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.mediaOptionsDropdown}</div> : null}
+ </div>
+ )}
+ </div>
+ );
}
+ return null;
}
}
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index 623201ed1..9c36e6d26 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -1,17 +1,18 @@
+/* eslint-disable react/require-default-props */
import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { emptyFunction } from '../../Utils';
import { Doc, Opt } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
import { ScriptField } from '../../fields/ScriptField';
import { ScriptCast } from '../../fields/Types';
-import { emptyFunction } from '../../Utils';
import { DragManager } from '../util/DragManager';
import { CompileScript } from '../util/Scripting';
import { EditableView } from './EditableView';
-import { DocumentIconContainer } from './nodes/DocumentIcon';
import { OverlayView } from './OverlayView';
import './ScriptBox.scss';
-import { DocData } from '../../fields/DocSymbols';
+import { DocumentIconContainer } from './nodes/DocumentIcon';
export interface ScriptBoxProps {
onSave: (text: string, onError: (error: string) => void) => void;
@@ -25,6 +26,7 @@ export interface ScriptBoxProps {
export class ScriptBox extends React.Component<ScriptBoxProps> {
@observable
private _scriptText: string;
+ overlayDisposer?: () => void;
constructor(props: ScriptBoxProps) {
super(props);
@@ -42,7 +44,6 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
console.log('ScriptBox: ' + error);
};
- overlayDisposer?: () => void;
onFocus = () => {
this.overlayDisposer?.();
this.overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
@@ -53,21 +54,35 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
};
render() {
- let onFocus: Opt<() => void> = undefined,
- onBlur: Opt<() => void> = undefined;
+ let onFocus: Opt<() => void>;
+ let onBlur: Opt<() => void>;
if (this.props.showDocumentIcons) {
onFocus = this.onFocus;
onBlur = this.onBlur;
}
- const params = <EditableView contents={''} display={'block'} maxHeight={72} height={35} fontSize={28} GetValue={() => ''} SetValue={(value: string) => (this.props.setParams?.(value.split(' ').filter(s => s !== ' ')) ? true : true)} />;
+ const params = (
+ <EditableView
+ contents=""
+ display="block"
+ maxHeight={72}
+ height={35}
+ fontSize={28}
+ GetValue={() => ''}
+ SetValue={(value: string) => {
+ this.props.setParams?.(value.split(' ').filter(s => s !== ' '));
+ return true;
+ }}
+ />
+ );
return (
<div className="scriptBox-outerDiv">
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
- <textarea className="scriptBox-textarea" onChange={this.onChange} value={this._scriptText} onFocus={onFocus} onBlur={onBlur}></textarea>
+ <textarea className="scriptBox-textarea" onChange={this.onChange} value={this._scriptText} onFocus={onFocus} onBlur={onBlur} />
<div style={{ background: 'beige' }}>{params}</div>
</div>
<div className="scriptBox-toolbar">
<button
+ type="button"
onClick={e => {
this.props.onSave(this._scriptText, this.onError);
e.stopPropagation();
@@ -75,6 +90,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
Save
</button>
<button
+ type="button"
onClick={e => {
this.props.onCancel && this.props.onCancel();
e.stopPropagation();
@@ -85,11 +101,12 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
</div>
);
}
- //let l = docList(this.source[0].data).length; if (l) { let ind = this.target[0].index !== undefined ? (this.target[0].index+1) % l : 0; this.target[0].index = ind; this.target[0].proto = getProto(docList(this.source[0].data)[ind]);}
+ // let l = docList(this.source[0].data).length; if (l) { let ind = this.target[0].index !== undefined ? (this.target[0].index+1) % l : 0; this.target[0].index = ind; this.target[0].proto = getProto(docList(this.source[0].data)[ind]);}
+ // eslint-disable-next-line react/sort-comp
public static EditButtonScript(title: string, doc: Doc, fieldKey: string, clientX: number, clientY: number, contextParams?: { [name: string]: string }, defaultScript?: ScriptField) {
let overlayDisposer: () => void = emptyFunction;
const script = ScriptCast(doc[fieldKey]) || defaultScript;
- let originalText: string | undefined = undefined;
+ let originalText: string | undefined;
if (script) {
originalText = script.script.originalScript;
}
@@ -105,14 +122,14 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
if (!text) {
doc[DocData][fieldKey] = undefined;
} else {
- const script = CompileScript(text, {
+ const compScript = CompileScript(text, {
params: { this: Doc.name, ...contextParams },
typecheck: false,
editable: true,
transformer: DocumentIconContainer.getTransformer(),
});
- if (!script.compiled) {
- onError(script.errors.map(error => error.messageText).join('\n'));
+ if (!compScript.compiled) {
+ onError(compScript.errors.map(error => error.messageText).join('\n'));
return;
}
@@ -124,9 +141,9 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
div.style.display = 'inline-block';
div.style.transform = `translate(${clientX}px, ${clientY}px)`;
div.innerHTML = 'button';
- params.length && DragManager.StartButtonDrag([div], text, doc.title + '-instance', {}, params, (button: Doc) => {}, clientX, clientY);
+ params.length && DragManager.StartButtonDrag([div], text, doc.title + '-instance', {}, params, () => {}, clientX, clientY);
- doc[DocData][fieldKey] = new ScriptField(script);
+ doc[DocData][fieldKey] = new ScriptField(compScript);
overlayDisposer();
}
}}
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index acf0ecff4..1a2eb460f 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -1,17 +1,50 @@
+/* eslint-disable react/no-array-index-key */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { DocumentManager } from '../util/DocumentManager';
import { CompileScript, Transformer, ts } from '../util/Scripting';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { SettingsManager } from '../util/SettingsManager';
+import { SnappingManager } from '../util/SnappingManager';
import { undoable } from '../util/UndoManager';
import { ObservableReactComponent } from './ObservableReactComponent';
import { OverlayView } from './OverlayView';
import './ScriptingRepl.scss';
import { DocumentIconContainer } from './nodes/DocumentIcon';
+import { DocumentView } from './nodes/DocumentView';
+interface replValueProps {
+ scrollToBottom: () => void;
+ value: any;
+ name?: string;
+}
+@observer
+export class ScriptingValueDisplay extends ObservableReactComponent<replValueProps> {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
+ render() {
+ const val = this._props.name ? this._props.value[this._props.name] : this._props.value;
+ const title = (name: string) => (
+ <>
+ {this._props.name ? <b>{this._props.name} : </b> : <> </>}
+ {name}
+ </>
+ );
+ if (typeof val === 'object') {
+ // eslint-disable-next-line no-use-before-define
+ return <ScriptingObjectDisplay scrollToBottom={this._props.scrollToBottom} value={val} name={this._props.name} />;
+ }
+ if (typeof val === 'function') {
+ return <div className="scriptingObject-leaf">{title('[Function]')}</div>;
+ }
+ return <div className="scriptingObject-leaf">{title(String(val))}</div>;
+ }
+}
interface ReplProps {
scrollToBottom: () => void;
value: { [key: string]: any };
@@ -37,7 +70,7 @@ export class ScriptingObjectDisplay extends ObservableReactComponent<ReplProps>
const name = (proto && proto.constructor && proto.constructor.name) || String(val);
const title = (
<>
- {this.props.name ? <b>{this._props.name} : </b> : <></>}
+ {this.props.name ? <b>{this._props.name} : </b> : null}
{name}
</>
);
@@ -50,53 +83,23 @@ export class ScriptingObjectDisplay extends ObservableReactComponent<ReplProps>
{title} (+{Object.keys(val).length})
</div>
);
- } else {
- return (
- <div className="scriptingObject-open">
- <div>
- <span onClick={this.toggle} className="scriptingObject-icon">
- <FontAwesomeIcon icon="caret-down" size="sm" />
- </span>
- {title}
- </div>
- <div className="scriptingObject-fields">
- {Object.keys(val).map(key => (
- <ScriptingValueDisplay {...this._props} name={key} />
- ))}
- </div>
- </div>
- );
}
- }
-}
-
-interface replValueProps {
- scrollToBottom: () => void;
- value: any;
- name?: string;
-}
-@observer
-export class ScriptingValueDisplay extends ObservableReactComponent<replValueProps> {
- constructor(props: any) {
- super(props);
- makeObservable(this);
- }
-
- render() {
- const val = this._props.name ? this._props.value[this._props.name] : this._props.value;
- const title = (name: string) => (
- <>
- {this._props.name ? <b>{this._props.name} : </b> : <> </>}
- {name}
- </>
+ return (
+ <div className="scriptingObject-open">
+ <div>
+ <span onClick={this.toggle} className="scriptingObject-icon">
+ <FontAwesomeIcon icon="caret-down" size="sm" />
+ </span>
+ {title}
+ </div>
+ <div className="scriptingObject-fields">
+ {Object.keys(val).map(key => (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ <ScriptingValueDisplay {...this._props} name={key} />
+ ))}
+ </div>
+ </div>
);
- if (typeof val === 'object') {
- return <ScriptingObjectDisplay scrollToBottom={this._props.scrollToBottom} value={val} name={this._props.name} />;
- } else if (typeof val === 'function') {
- const name = '[Function]';
- return <div className="scriptingObject-leaf">{title('[Function]')}</div>;
- }
- return <div className="scriptingObject-leaf">{title(String(val))}</div>;
}
}
@@ -119,47 +122,45 @@ export class ScriptingRepl extends ObservableReactComponent<{}> {
private args: any = {};
- getTransformer = (): Transformer => {
- return {
- transformer: context => {
- const knownVars: { [name: string]: number } = {};
- const usedDocuments: number[] = [];
- ScriptingGlobals.getGlobals().forEach((global: any) => (knownVars[global] = 1));
- return root => {
- function visit(node: ts.Node) {
- let skip = false;
- if (ts.isIdentifier(node)) {
- if (ts.isParameter(node.parent)) {
- skip = true;
- knownVars[node.text] = 1;
- }
+ getTransformer = (): Transformer => ({
+ transformer: context => {
+ const knownVars: { [name: string]: number } = {};
+ const usedDocuments: number[] = [];
+ ScriptingGlobals.getGlobals().forEach((global: any) => {
+ knownVars[global] = 1;
+ });
+ return root => {
+ function visit(nodeIn: ts.Node) {
+ if (ts.isIdentifier(nodeIn)) {
+ if (ts.isParameter(nodeIn.parent)) {
+ knownVars[nodeIn.text] = 1;
}
- node = ts.visitEachChild(node, visit, context);
+ }
+ const node = ts.visitEachChild(nodeIn, visit, context);
- if (ts.isIdentifier(node)) {
- const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
- const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
- if (ts.isParameter(node.parent)) {
- // delete knownVars[node.text];
- } else if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) {
- const match = node.text.match(/d([0-9]+)/);
- if (match) {
- const m = parseInt(match[1]);
- usedDocuments.push(m);
- } else {
- return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('args'), node);
- // ts.createPropertyAccess(ts.createIdentifier('args'), node);
- }
+ if (ts.isIdentifier(node)) {
+ const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
+ const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
+ if (ts.isParameter(node.parent)) {
+ // delete knownVars[node.text];
+ } else if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) {
+ const match = node.text.match(/d([0-9]+)/);
+ if (match) {
+ const m = parseInt(match[1]);
+ usedDocuments.push(m);
+ } else {
+ return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('args'), node);
+ // ts.createPropertyAccess(ts.createIdentifier('args'), node);
}
}
-
- return node;
}
- return ts.visitNode(root, visit);
- };
- },
- };
- };
+
+ return node;
+ }
+ return ts.visitNode(root, visit);
+ };
+ },
+ });
@action
onKeyDown = (e: React.KeyboardEvent) => {
@@ -168,14 +169,16 @@ export class ScriptingRepl extends ObservableReactComponent<{}> {
case 'Enter': {
e.stopPropagation();
const docGlobals: { [name: string]: any } = {};
- DocumentManager.Instance.DocumentViews.forEach((dv, i) => (docGlobals[`d${i}`] = dv.Document));
+ DocumentView.allViews().forEach((dv, i) => {
+ docGlobals[`d${i}`] = dv.Document;
+ });
const globals = ScriptingGlobals.makeMutableGlobalsCopy(docGlobals);
const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: 'any' }, transformer: this.getTransformer(), globals });
if (!script.compiled) {
this.commands.push({ command: this.commandString, result: script.errors });
return;
}
- const result = undoable(() => script.run({ args: this.args }, e => this.commands.push({ command: this.commandString, result: e.toString() })), 'run:' + this.commandString)();
+ const result = undoable(() => script.run({ args: this.args }, () => this.commands.push({ command: this.commandString, result: e.toString() })), 'run:' + this.commandString)();
if (result.success) {
this.commands.push({ command: this.commandString, result: result.result });
this.commandsHistory.push(this.commandString);
@@ -259,23 +262,21 @@ export class ScriptingRepl extends ObservableReactComponent<{}> {
render() {
return (
<div className="scriptingRepl-outerContainer">
- <div className="scriptingRepl-commandsContainer" style={{ background: SettingsManager.userBackgroundColor }} ref={this.commandsRef}>
- {this.commands.map(({ command, result }, i) => {
- return (
- <div className="scriptingRepl-resultContainer" style={{ background: SettingsManager.userBackgroundColor }} key={i}>
- <div className="scriptingRepl-commandString" style={{ background: SettingsManager.userBackgroundColor }}>
- {command || <br />}
- </div>
- <div className="scriptingRepl-commandResult" style={{ background: SettingsManager.userBackgroundColor }}>
- {<ScriptingValueDisplay scrollToBottom={this.maybeScrollToBottom} value={result} />}
- </div>
+ <div className="scriptingRepl-commandsContainer" style={{ background: SnappingManager.userBackgroundColor }} ref={this.commandsRef}>
+ {this.commands.map(({ command, result }, i) => (
+ <div className="scriptingRepl-resultContainer" style={{ background: SnappingManager.userBackgroundColor }} key={i}>
+ <div className="scriptingRepl-commandString" style={{ background: SnappingManager.userBackgroundColor }}>
+ {command || <br />}
+ </div>
+ <div className="scriptingRepl-commandResult" style={{ background: SnappingManager.userBackgroundColor }}>
+ <ScriptingValueDisplay scrollToBottom={this.maybeScrollToBottom} value={result} />
</div>
- );
- })}
+ </div>
+ ))}
</div>
<input
className="scriptingRepl-commandInput"
- style={{ background: SettingsManager.userBackgroundColor }} //
+ style={{ background: SnappingManager.userBackgroundColor }} //
onFocus={this.onFocus}
onBlur={this.onBlur}
value={this.commandString}
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 3ad3c92da..9b70f1ca7 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -1,24 +1,27 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnAll, returnFalse, returnOne, returnZero } from '../../Utils';
-import { Doc, DocListCast, Field, FieldResult, StrListCast } from '../../fields/Doc';
+import { ClientUtils, returnAll, returnFalse, returnOne, returnZero } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
+import { Doc, DocListCast, Field, FieldType, FieldResult, StrListCast } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { RichTextField } from '../../fields/RichTextField';
import { DocCast, NumCast, StrCast } from '../../fields/Types';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
-import { DocUtils, Docs } from '../documents/Documents';
-import { LinkManager } from '../util/LinkManager';
+import { Docs } from '../documents/Documents';
+import { DocUtils } from '../documents/DocUtils';
import { SearchUtil } from '../util/SearchUtil';
import { Transform } from '../util/Transform';
import { ObservableReactComponent } from './ObservableReactComponent';
import './SidebarAnnos.scss';
-import { StyleProp } from './StyleProvider';
+import { StyleProp } from './StyleProp';
import { CollectionStackingView } from './collections/CollectionStackingView';
import { FieldViewProps } from './nodes/FieldView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import { DocData } from '../../fields/DocSymbols';
interface ExtraProps {
fieldKey: string;
@@ -44,7 +47,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
_stackRef = React.createRef<CollectionStackingView>();
@computed get allMetadata() {
- const keys = new Map<string, FieldResult<Field>>();
+ const keys = new Map<string, FieldResult<FieldType>>();
DocListCast(this._props.Document[this.sidebarKey]).forEach(doc =>
SearchUtil.documentKeys(doc)
.filter(key => key[0] && key[0] !== '_' && key[0] === key[0].toUpperCase())
@@ -80,10 +83,9 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
_text_fontSize: StrCast(Doc.UserDoc().fontSize),
_text_fontFamily: StrCast(Doc.UserDoc().fontFamily),
});
- FormattedTextBox.SetSelectOnLoad(target);
+ Doc.SetSelectOnLoad(target);
FormattedTextBox.DontSelectInitialText = true;
const link = DocUtils.MakeLink(anchor, target, { link_relationship: 'inline comment:comment on' });
- link && (link.link_displayLine = false);
const taggedContent = this.childFilters()
.filter(data => data.split(':')[0])
@@ -95,7 +97,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
return {
type: 'dashField',
attrs: { fieldKey: key, docId: '', hideKey: false, hideValue: false, editable: true },
- marks: [{ type: 'pFontSize', attrs: { fontSize: '12px' } }, { type: 'strong' }, { type: 'user_mark', attrs: { userid: Doc.CurrentUserEmail, modified: 0 } }],
+ marks: [{ type: 'pFontSize', attrs: { fontSize: '12px' } }, { type: 'strong' }, { type: 'user_mark', attrs: { userid: ClientUtils.CurrentUserEmail(), modified: 0 } }],
};
});
@@ -183,8 +185,8 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
layout_showTitle = () => 'title';
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;
+ const ay = Doc.Links(a).length && DocCast(Doc.Links(a)[0].link_anchor_1).y;
+ const by = Doc.Links(b).length && DocCast(Doc.Links(b)[0].link_anchor_1).y;
return NumCast(ay) - NumCast(by);
};
render() {
@@ -196,7 +198,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
</div>
);
};
- const renderMeta = (tag: string, dflt: FieldResult<Field>) => {
+ const renderMeta = (tag: string) => {
const active = this.childFilters().includes(`${tag}${Doc.FilterSep}${Doc.FilterAny}${Doc.FilterSep}exists`);
return (
<div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, tag, Doc.FilterAny, 'exists', true, undefined, e.shiftKey)}>
@@ -227,13 +229,12 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
<div className="sidebarAnnos-tagList" style={{ height: this.filtersHeight() }} onWheel={e => e.stopPropagation()}>
{this.allUsers.length > 1 ? this.allUsers.map(renderUsers) : null}
{this.allHashtags.map(renderTag)}
- {Array.from(this.allMetadata.keys())
- .sort()
- .map(key => renderMeta(key, this.allMetadata.get(key)))}
+ {Array.from(this.allMetadata.keys()).sort().map(renderMeta)}
</div>
<div style={{ width: '100%', height: `calc(100% - 38px)`, position: 'relative' }}>
<CollectionStackingView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={emptyFunction}
NativeWidth={returnZero}
@@ -247,11 +248,11 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
isAnnotationOverlay={false}
select={emptyFunction}
NativeDimScaling={returnOne}
- //childlayout_showTitle={this.layout_showTitle}
+ // childlayout_showTitle={this.layout_showTitle}
isAnyChildContentActive={returnFalse}
childDocumentsActive={this._props.isContentActive}
whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
- childHideDecorationTitle={true}
+ childHideDecorationTitle
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
addDocument={this.addDocument}
diff --git a/src/client/views/StyleProp.ts b/src/client/views/StyleProp.ts
new file mode 100644
index 000000000..dd5b98cfe
--- /dev/null
+++ b/src/client/views/StyleProp.ts
@@ -0,0 +1,24 @@
+export enum StyleProp {
+ TreeViewIcon = 'treeView_Icon',
+ TreeViewSortings = 'treeView_Sortings', // options for how to sort tree view items
+ DocContents = 'docContents', // when specified, the JSX returned will replace the normal rendering of the document view
+ Opacity = 'opacity', // opacity of the document view
+ BoxShadow = 'boxShadow', // box shadow - used for making collections standout and for showing clusters in free form views
+ BorderRounding = 'borderRounding', // border radius of the document view
+ Color = 'color', // foreground color of Document view items
+ BackgroundColor = 'backgroundColor', // background color of a document view
+ FillColor = 'fillColor', // fill color of an ink stroke or shape
+ WidgetColor = 'widgetColor', // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note
+ PointerEvents = 'pointerEvents', // pointer events for DocumentView -- inherits pointer events if not specified
+ Decorations = 'decorations', // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background
+ HeaderMargin = 'headerMargin', // margin at top of documentview, typically for displaying a title -- doc contents will start below that
+ ShowCaption = 'layout_showCaption',
+ TitleHeight = 'titleHeight', // Height of Title area
+ ShowTitle = 'layout_showTitle', // whether to display a title on a Document (optional :hover suffix)
+ BorderPath = 'customBorder', // border path for document view
+ FontColor = 'fontColor', // color o tet
+ FontSize = 'fontSize', // size of text font
+ FontFamily = 'fontFamily', // font family of text
+ FontWeight = 'fontWeight', // font weight of text
+ Highlighting = 'highlighting', // border highlighting
+}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index dcec2fe3d..3ecfc8ba3 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
@@ -7,55 +10,24 @@ import { extname } from 'path';
import * as React from 'react';
import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs';
import { FaFilter } from 'react-icons/fa';
+import { ClientUtils, DashColor, lightOrDark } from '../../ClientUtils';
import { Doc, Opt, StrListCast } from '../../fields/Doc';
import { DocViews } from '../../fields/DocSymbols';
+import { Id } from '../../fields/FieldSymbols';
+import { ScriptField } from '../../fields/ScriptField';
import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
-import { DashColor, lightOrDark, Utils } from '../../Utils';
+import { AudioAnnoState } from '../../server/SharedMediaTypes';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
-import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager';
-import { IsFollowLinkScript } from '../util/LinkFollower';
-import { LinkManager } from '../util/LinkManager';
-import { SettingsManager } from '../util/SettingsManager';
+import { IsFollowLinkScript } from '../documents/DocUtils';
import { SnappingManager } from '../util/SnappingManager';
import { undoBatch, UndoManager } from '../util/UndoManager';
-import { CollectionSchemaView } from './collections/collectionSchema/CollectionSchemaView';
import { TreeSort } from './collections/TreeSort';
import { Colors } from './global/globalEnums';
-import { DocumentViewProps } from './nodes/DocumentView';
+import { DocFocusOrOpen, DocumentView, DocumentViewProps } from './nodes/DocumentView';
import { FieldViewProps } from './nodes/FieldView';
-import { KeyValueBox } from './nodes/KeyValueBox';
-import { PropertiesView } from './PropertiesView';
+import { StyleProp } from './StyleProp';
import './StyleProvider.scss';
-export enum StyleProp {
- TreeViewIcon = 'treeView_Icon',
- TreeViewSortings = 'treeView_Sortings', // options for how to sort tree view items
- DocContents = 'docContents', // when specified, the JSX returned will replace the normal rendering of the document view
- Opacity = 'opacity', // opacity of the document view
- BoxShadow = 'boxShadow', // box shadow - used for making collections standout and for showing clusters in free form views
- BorderRounding = 'borderRounding', // border radius of the document view
- Color = 'color', // foreground color of Document view items
- BackgroundColor = 'backgroundColor', // background color of a document view
- FillColor = 'fillColor', // fill color of an ink stroke or shape
- WidgetColor = 'widgetColor', // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note
- PointerEvents = 'pointerEvents', // pointer events for DocumentView -- inherits pointer events if not specified
- Decorations = 'decorations', // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background
- HeaderMargin = 'headerMargin', // margin at top of documentview, typically for displaying a title -- doc contents will start below that
- ShowCaption = 'layout_showCaption',
- TitleHeight = 'titleHeight', // Height of Title area
- ShowTitle = 'layout_showTitle', // whether to display a title on a Document (optional :hover suffix)
- BorderPath = 'customBorder', // border path for document view
- FontSize = 'fontSize', // size of text font
- FontFamily = 'fontFamily', // font family of text
- FontWeight = 'fontWeight', // font weight of text
- Highlighting = 'highlighting', // border highlighting
-}
-
-export enum AudioAnnoState {
- stopped = 'stopped',
- playing = 'playing',
-}
-
function toggleLockedPosition(doc: Doc) {
UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground');
}
@@ -70,6 +42,19 @@ function togglePaintView(e: React.MouseEvent, doc: Opt<Doc>, props: Opt<FieldVie
ScriptCast(doc?.onPaint)?.script.run(scriptProps);
}
+export function styleFromLayoutString(doc: Doc, props: FieldViewProps, scale: number) {
+ const style: { [key: string]: any } = {};
+ const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
+ const replacer = (match: any, expr: string) =>
+ // bcz: this executes a script to convert a property expression string: { script } into a value
+ ScriptField.MakeFunction(expr, { this: Doc.name, scale: 'number' })?.script.run({ this: doc, scale }).result?.toString() ?? '';
+ divKeys.forEach((prop: string) => {
+ const p = (props as any)[prop];
+ typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
+ });
+ return style;
+}
+
export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
return `M ${pw * 0.5} ${ph * inset} C ${pw * 0.6} ${ph * inset} ${pw * (1 - 2 * inset)} 0 ${pw * (1 - inset)} ${ph * inset} C ${pw} ${ph * (2 * inset)} ${pw * (1 - inset)} ${ph * 0.25} ${pw * (1 - inset)} ${ph * 0.3} C ${
pw * (1 - inset)
@@ -80,6 +65,11 @@ export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
} ${ph * inset}`;
}
+let _filterOpener: () => void;
+export function SetFilterOpener(func: () => void) {
+ _filterOpener = func;
+}
+
// 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<FieldViewProps & DocumentViewProps>, property: string): any {
@@ -90,20 +80,40 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
const isNonTransparentLevel = isNonTransparent ? Number(property.replace(/.*:nonTransparent([0-9]+).*/, '$1')) : 0; // property.includes(':nonTransparent');
const isAnnotated = property.includes(':annotated');
const layoutDoc = doc ? Doc.Layout(doc) : doc;
- const isInk = () => layoutDoc?._layout_isSvg && !props?.LayoutTemplateString;
const isOpen = property.includes(':open');
- const isEmpty = property.includes(':empty');
const boxBackground = property.includes(':box');
- const fieldKey = props?.fieldKey ? props.fieldKey + '_' : isCaption ? 'caption_' : '';
+ const {
+ fieldKey: fieldKeyProp,
+ styleProvider,
+ pointerEvents,
+ isGroupActive,
+ isDocumentActive,
+ containerViewPath,
+ childFilters,
+ hideCaptions,
+ // eslint-disable-next-line camelcase
+ layout_showTitle,
+ childFiltersByRanges,
+ renderDepth,
+ docViewPath,
+ DocumentView: docView,
+ LayoutTemplateString,
+ disableBrushing,
+ NativeDimScaling,
+ PanelWidth,
+ PanelHeight,
+ } = props || {}; // extract props that are not shared between fieldView and documentView props.
+ const fieldKey = fieldKeyProp ? fieldKeyProp + '_' : isCaption ? 'caption_' : '';
+ const isInk = () => layoutDoc?._layout_isSvg && !LayoutTemplateString;
const lockedPosition = () => doc && BoolCast(doc._lockedPosition);
- const titleHeight = () => props?.styleProvider?.(doc, props, StyleProp.TitleHeight);
- const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor + ':nonTransparent' + (isNonTransparentLevel + 1));
- const color = () => props?.styleProvider?.(doc, props, StyleProp.Color);
- const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity);
- const layout_showTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle);
+ const titleHeight = () => styleProvider?.(doc, props, StyleProp.TitleHeight);
+ const backgroundCol = () => styleProvider?.(doc, props, StyleProp.BackgroundColor + ':nonTransparent' + (isNonTransparentLevel + 1));
+ const color = () => styleProvider?.(doc, props, StyleProp.Color);
+ const opacity = () => styleProvider?.(doc, props, StyleProp.Opacity);
+ const layoutShowTitle = () => styleProvider?.(doc, props, StyleProp.ShowTitle);
// prettier-ignore
switch (property.split(':')[0]) {
- case StyleProp.TreeViewIcon:
+ case StyleProp.TreeViewIcon: {
const img = ImageCast(doc?.icon, ImageCast(doc?.data));
if (img) {
const ext = extname(img.url.href);
@@ -111,16 +121,18 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
return <img src={url} width={20} height={15} style={{ margin: 'auto', display: 'block', objectFit: 'contain' }} />;
}
return Doc.toIcon(doc, isOpen);
- case StyleProp.TreeViewSortings:
+ }
+ case StyleProp.TreeViewSortings: {
const allSorts: { [key: string]: { color: string; icon: JSX.Element | string } | undefined } = {};
allSorts[TreeSort.AlphaDown] = { color: Colors.MEDIUM_BLUE, icon: <BsArrowDown/> };
allSorts[TreeSort.AlphaUp] = { color: 'crimson', icon: <BsArrowUp/> };
if (doc?._type_collection === CollectionViewType.Freeform) allSorts[TreeSort.Zindex] = { color: 'green', icon: 'Z' };
allSorts[TreeSort.WhenAdded] = { color: 'darkgray', icon: <BsArrowDownUp/> };
return allSorts;
+ }
case StyleProp.Highlighting:
if (doc && (Doc.IsSystem(doc) || doc.type === DocumentType.FONTICON)) return undefined;
- if (doc && !doc.layout_disableBrushing && !props?.disableBrushing) {
+ if (doc && !doc.layout_disableBrushing && !disableBrushing) {
const selected = Array.from(doc?.[DocViews]??[]).filter(dv => dv.IsSelected).length;
const highlightIndex = Doc.GetBrushHighlightStatus(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0);
const highlightColor = ['transparent', 'rgb(68, 118, 247)', selected ? "black" : 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
@@ -135,71 +147,74 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
}
}
return undefined;
- case StyleProp.DocContents:return undefined;
- case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
- case StyleProp.Opacity: return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
- case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(Doc.UserDoc().fontSize));
- case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily));
- case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(Doc.UserDoc().fontWeight));
- case StyleProp.FillColor: return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent')));
- case StyleProp.ShowCaption:return props?.hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
- case StyleProp.TitleHeight:return Math.min(4,(props?.DocumentView?.().screenToViewTransform().Scale ?? 1)) * NumCast(Doc.UserDoc().headerHeight,30);
+ case StyleProp.DocContents: return undefined;
+ case StyleProp.WidgetColor: return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
+ case StyleProp.Opacity: return docView?.().ComponentView?.isUnstyledView?.() ? 1 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
+ case StyleProp.FontColor: return StrCast(doc?.[fieldKey + 'fontColor'], StrCast(Doc.UserDoc().fontColor, color()));
+ case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(Doc.UserDoc().fontSize));
+ case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily));
+ case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(Doc.UserDoc().fontWeight));
+ case StyleProp.FillColor: return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent')));
+ case StyleProp.ShowCaption: return hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
+ case StyleProp.TitleHeight: return Math.min(4,(docView?.().screenToViewTransform().Scale ?? 1)) * NumCast(Doc.UserDoc().headerHeight,30);
case StyleProp.ShowTitle:
return (
(doc &&
- !(props?.DocumentView?.().ComponentView instanceof CollectionSchemaView) &&
- !props?.LayoutTemplateString &&
+ !docView?.().ComponentView?.isUnstyledView?.() &&
+ !LayoutTemplateString &&
!doc.presentation_targetDoc &&
- !props?.LayoutTemplateString?.includes(KeyValueBox.name) &&
- props?.layout_showTitle?.() !== '' &&
+ layout_showTitle?.() !== '' &&
StrCast(
doc._layout_showTitle,
- props?.layout_showTitle?.() ||
+ layout_showTitle?.() ||
(!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.FUNCPLOT, DocumentType.LABEL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any)
- ? doc.author === Doc.CurrentUserEmail
+ ? doc.author === ClientUtils.CurrentUserEmail()
? StrCast(Doc.UserDoc().layout_showTitle)
: remoteDocHeader
: '')
)) ||
''
);
- case StyleProp.Color:
- if (SettingsManager.Instance.LastPressedBtn === doc) return SettingsManager.userBackgroundColor;
- if (Doc.IsSystem(doc!)) return SettingsManager.userColor;
- if (doc?.type === DocumentType.FONTICON) return SettingsManager.userColor;
+ case StyleProp.Color: {
+ if (SnappingManager.LastPressedBtn === doc?.[Id]) return SnappingManager.userBackgroundColor;
+ if (Doc.IsSystem(doc!)) return SnappingManager.userColor;
+ if (doc?.type === DocumentType.FONTICON) return SnappingManager.userColor;
const docColor: Opt<string> = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color));
if (docColor) return docColor;
const backColor = backgroundCol();
return backColor ? lightOrDark(backColor) : undefined;
- case StyleProp.BorderRounding:
+ }
+ case StyleProp.BorderRounding: {
const rounding = StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.layout_borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : ''));
return (doc?.[StrCast(doc?.layout_fieldKey)] instanceof Doc || doc?.isTemplateDoc) ? StrCast(doc._layout_borderRounding,rounding) : rounding;
- case StyleProp.BorderPath:
+ }
+ case StyleProp.BorderPath: {
const borderPath = Doc.IsComicStyle(doc) &&
- props?.renderDepth &&
- !doc?.layout_isSvg && { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, 0.08), width: 3 };
- return !borderPath
- ? null
- : {
- clipPath: `path('${borderPath.path}')`,
- jsx: (
- <div key="border2" className="documentView-customBorder" style={{ pointerEvents: 'none' }}>
- <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${props.PanelWidth()} ${props.PanelHeight()}`}>
- <path d={borderPath.path} style={{ stroke: 'black', fill: 'transparent', strokeWidth: borderPath.width }} />
- </svg>
- </div>
- ),
- };
+ renderDepth &&
+ !doc?.layout_isSvg && { path: wavyBorderPath(PanelWidth?.() || 0, PanelHeight?.() || 0), fill: wavyBorderPath(PanelWidth?.() || 0, PanelHeight?.() || 0, 0.08), width: 3 };
+ return !borderPath
+ ? null
+ : {
+ clipPath: `path('${borderPath.path}')`,
+ jsx: (
+ <div key="border2" className="documentView-customBorder" style={{ pointerEvents: 'none' }}>
+ <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${PanelWidth?.()} ${PanelHeight?.()}`}>
+ <path d={borderPath.path} style={{ stroke: 'black', fill: 'transparent', strokeWidth: borderPath.width }} />
+ </svg>
+ </div>
+ ),
+ };
+ }
case StyleProp.HeaderMargin:
return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._type_collection as any) ||
- (doc?.type === DocumentType.RTF && !layout_showTitle()?.includes('noMargin')) ||
+ (doc?.type === DocumentType.RTF && !layoutShowTitle()?.includes('noMargin')) ||
doc?.type === DocumentType.LABEL) &&
- layout_showTitle() &&
+ layoutShowTitle() &&
!StrCast(doc?.layout_showTitle).includes(':hover')
? titleHeight()
: 0;
case StyleProp.BackgroundColor: {
- if (SettingsManager.Instance.LastPressedBtn === doc) return SettingsManager.userColor; // hack to indicate active menu panel item
+ if (SnappingManager.LastPressedBtn === doc?.[Id]) return SnappingManager.userColor; // hack to indicate active menu panel item
const dataKey = doc ? Doc.LayoutFieldKey(doc) : "";
const usePath = StrCast(doc?.[dataKey + "_usePath"]);
const alternate = usePath.includes(":hover") ? ( doc?.isHovering ? '_' + usePath.replace(":hover","") : "") : usePath ? "_" +usePath:usePath;
@@ -224,47 +239,48 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
case DocumentType.VID: docColor = docColor || (Colors.LIGHT_GRAY); break;
case DocumentType.COL:
docColor = docColor || (doc && Doc.IsSystem(doc)
- ? SettingsManager.userBackgroundColor
+ ? SnappingManager.userBackgroundColor
: doc?.annotationOn
? '#00000010' // faint interior for collections on PDFs, images, etc
: doc?.isGroup
? undefined
: doc?._type_collection === CollectionViewType.Stacking ?
(Colors.DARK_GRAY)
- : Cast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (Colors.MEDIUM_GRAY));
+ : Cast((renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (Colors.MEDIUM_GRAY));
break;
- //if (doc._type_collection !== CollectionViewType.Freeform && doc._type_collection !== CollectionViewType.Time) return "rgb(62,62,62)";
+ // if (doc._type_collection !== CollectionViewType.Freeform && doc._type_collection !== CollectionViewType.Time) return "rgb(62,62,62)";
default: docColor = docColor || (Colors.WHITE);
}
- if (isNonTransparent && isNonTransparentLevel < 9 && (!docColor || docColor === 'transparent') && doc?.embedContainer && props?.styleProvider) {
- return props.styleProvider(DocCast(doc.embedContainer), props, StyleProp.BackgroundColor+":nonTransparent"+(isNonTransparentLevel+1));
+ if (isNonTransparent && isNonTransparentLevel < 9 && (!docColor || docColor === 'transparent') && doc?.embedContainer && styleProvider) {
+ return styleProvider(DocCast(doc.embedContainer), props, StyleProp.BackgroundColor+":nonTransparent"+(isNonTransparentLevel+1));
}
return (docColor && !doc) ? DashColor(docColor).fade(0.5).toString() : docColor;
}
case StyleProp.BoxShadow: {
if (!doc || opacity() === 0 || doc.noShadow) return undefined; // if it's not visible, then no shadow)
if (doc.layout_boxShadow === 'standard') return Shadows.STANDARD_SHADOW;
- if (IsFollowLinkScript(doc?.onClick) && LinkManager.Links(doc).length && !layoutDoc?.layout_isSvg) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
+ if (IsFollowLinkScript(doc?.onClick) && Doc.Links(doc).length && !layoutDoc?.layout_isSvg) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
switch (doc?.type) {
case DocumentType.COL:
return StrCast(
doc?.layout_boxShadow,
doc?._type_collection === CollectionViewType.Pile
? '4px 4px 10px 2px'
- : lockedPosition() || doc?.isGroup || props?.LayoutTemplateString
+ : lockedPosition() || doc?.isGroup || LayoutTemplateString
? undefined // groups have no drop shadow -- they're supposed to be "invisible". LayoutString's imply collection is being rendered as something else (e.g., title of a Slide)
: `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
);
case DocumentType.LABEL:
if (doc?.annotationOn !== undefined) return 'black 2px 2px 1px';
+ // eslint-disable-next-line no-fallthrough
default:
return doc.z
? `#9c9396 ${StrCast(doc?.layout_boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow
- : props?.containerViewPath?.().lastElement()?.Document._freeform_useClusters
- ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (props?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
+ : containerViewPath?.().lastElement()?.Document._freeform_useClusters
+ ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
: NumCast(doc.group, -1) !== -1
- ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (props?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
+ ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
: lockedPosition()
? undefined // if it's a background & has a cluster color, make the shadow spread really big
: fieldKey.includes('_inline') // if doc is an inline document in a text box
@@ -275,33 +291,33 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
}
}
case StyleProp.PointerEvents:
- if (StrCast(doc?.pointerEvents) && !props?.LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc
- if (props?.LayoutTemplateString?.includes(KeyValueBox.name)) return 'all';
+ if (docView?.().ComponentView?.dontRegisterView?.()) return 'all';
+ if (StrCast(doc?.pointerEvents)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc
if (SnappingManager.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all';
- if (props?.pointerEvents?.() === 'none') return 'none';
+ if (pointerEvents?.() === 'none') return 'none';
if (opacity() === 0) return 'none';
- if (props?.isGroupActive?.() ) return isInk() ? 'visiblePainted': (doc?.
+ if (isGroupActive?.() ) return isInk() ? 'visiblePainted': (doc?.
isGroup )? undefined: 'all'
- if (props?.isDocumentActive?.()) return isInk() ? 'visiblePainted' : 'all';
+ if (isDocumentActive?.()) return isInk() ? 'visiblePainted' : 'all';
return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
- case StyleProp.Decorations:
+ case StyleProp.Decorations: {
const lock = () => doc?.pointerEvents !== 'none' ? null : (
<div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
<FontAwesomeIcon icon='lock' size="lg" />
</div>
);
const paint = () => !doc?.onPaint ? null : (
- <div className={`styleProvider-paint${props?.DocumentView?.().IsSelected ? "-selected":""}`} onClick={e => togglePaintView(e, doc, props)}>
+ <div className={`styleProvider-paint${docView?.().IsSelected ? "-selected":""}`} onClick={e => togglePaintView(e, doc, props)}>
<FontAwesomeIcon icon='pen' size="lg" />
</div>
);
const filter = () => {
- const dashView = untracked(() => DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard));
+ const dashView = untracked(() => DocumentView.getDocumentView(Doc.ActiveDashboard));
const showFilterIcon =
StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length
? 'green' // #18c718bd' //'hasFilter'
- : props?.childFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragDocsFilter).length || props?.childFiltersByRanges().length
- ? 'orange' //'inheritsFilter'
+ : childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || childFiltersByRanges?.().length
+ ? 'orange' // 'inheritsFilter'
: undefined;
return !showFilterIcon ? null : (
<div className="styleProvider-filter">
@@ -309,19 +325,14 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
type={Type.TERT}
dropdownType={DropdownType.CLICK}
fillWidth
- iconProvider={(active:boolean) => <div className='styleProvider-filterShift'><FaFilter/></div>}
- closeOnSelect={true}
- setSelectedVal={
- action((dv) => {
- (dv as any).select(false);
- (SettingsManager.Instance.propertiesWidth = 250);
- setTimeout(action(() => {
- if (PropertiesView.Instance) {
- PropertiesView.Instance.CloseAll();
- PropertiesView.Instance.openFilters = true;
- }
- }));
- })
+ // eslint-disable-next-line react/no-unstable-nested-components
+ iconProvider={() => <div className='styleProvider-filterShift'><FaFilter/></div>}
+ closeOnSelect
+ setSelectedVal={((dv: DocumentView) => {
+ dv.select(false);
+ SnappingManager.SetPropertiesWidth(250);
+ _filterOpener?.();
+ }) as any // Dropdown assumes values are strings or numbers..
}
size={Size.XSMALL}
width={15}
@@ -329,27 +340,27 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
title={showFilterIcon === 'green' ?
"This view is filtered. Click to view/change filters":
"this view inherits filters from one of its parents"}
- color={SettingsManager.userColor}
+ color={SnappingManager.userColor}
background={showFilterIcon}
- items={[ ...(dashView ? [dashView]: []), ...(props?.docViewPath?.()??[])]
+ items={[ ...(dashView ? [dashView]: []), ...(docViewPath?.()??[])]
.filter(dv => StrListCast(dv?.Document.childFilters).length || StrListCast(dv?.Document.childRangeFilters).length)
.map(dv => ({
text: StrCast(dv?.Document.title),
val: dv as any,
- style: {color:SettingsManager.userColor, background:SettingsManager.userBackgroundColor},
+ style: {color:SnappingManager.userColor, background:SnappingManager.userBackgroundColor},
} as IListItemProps)) }
/>
</div>
);
};
const audio = () => {
- const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, AudioAnnoState.stopped);
- const audioAnnosCount = (doc: Doc) => StrListCast(doc[fieldKey + 'audioAnnotations']).length;
- if (!doc || props?.renderDepth === -1 || !audioAnnosCount(doc)) return null;
+ const audioAnnoState = (audioDoc: Doc) => StrCast(audioDoc.audioAnnoState, AudioAnnoState.stopped);
+ const audioAnnosCount = (audioDoc: Doc) => StrListCast(audioDoc[fieldKey + 'audioAnnotations']).length;
+ if (!doc || renderDepth === -1 || !audioAnnosCount(doc)) return null;
const audioIconColors: { [key: string]: string } = { playing: 'green', stopped: 'blue' };
return (
<Tooltip title={<div>{StrListCast(doc[fieldKey + 'audioAnnotations_text']).lastElement()}</div>}>
- <div className="styleProvider-audio" onPointerDown={() => DocumentManager.Instance.getFirstDocumentView(doc)?.playAnnotation()}>
+ <div className="styleProvider-audio" onPointerDown={() => DocumentView.getFirstDocumentView(doc)?.playAnnotation()}>
<FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors[audioAnnoState(doc)] }} icon='file-audio' size="sm" />
</div>
</Tooltip>
@@ -363,11 +374,13 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
{audio()}
</>
);
+ }
+ default:
}
}
export function DashboardToggleButton(doc: Doc, field: string, onIcon: IconProp, offIcon: IconProp, clickFunc?: () => void) {
- const color = SettingsManager.userColor;
+ const color = SnappingManager.userColor;
return (
<IconButton
size={Size.XSMALL}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 5df0bea1a..5a7c2ef5b 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -1,13 +1,15 @@
-import { computed, observable, ObservableSet, runInAction } from 'mobx';
+import { computed, ObservableSet, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../ClientUtils';
import { Doc, DocListCast } from '../../fields/Doc';
import { DocData } from '../../fields/DocSymbols';
import { ScriptField } from '../../fields/ScriptField';
import { Cast, DocCast, StrCast } from '../../fields/Types';
import { TraceMobx } from '../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils';
-import { Docs, DocUtils } from '../documents/Documents';
+import { emptyFunction } from '../../Utils';
+import { Docs } from '../documents/Documents';
+import { DocUtils } from '../documents/DocUtils';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
import { Transform } from '../util/Transform';
import { CollectionTreeView } from './collections/CollectionTreeView';
@@ -16,21 +18,6 @@ import { DefaultStyleProvider } from './StyleProvider';
import './TemplateMenu.scss';
@observer
-class TemplateToggle extends React.Component<{ template: string; checked: boolean; toggle: (event: React.ChangeEvent<HTMLInputElement>, template: string) => void }> {
- render() {
- if (this.props.template) {
- return (
- <li className="templateToggle">
- <input type="checkbox" checked={this.props.checked} onChange={event => this.props.toggle(event, this.props.template)} />
- {this.props.template}
- </li>
- );
- } else {
- return null;
- }
- }
-}
-@observer
class OtherToggle extends React.Component<{ checked: boolean; name: string; toggle: (event: React.ChangeEvent<HTMLInputElement>) => void }> {
render() {
return (
@@ -50,12 +37,25 @@ export interface TemplateMenuProps {
export class TemplateMenu extends React.Component<TemplateMenuProps> {
_addedKeys = new ObservableSet();
_customRef = React.createRef<HTMLInputElement>();
- @observable private _hidden: boolean = true;
+
+ componentDidMount() {
+ !this._addedKeys && (this._addedKeys = new ObservableSet());
+ [...Array.from(Object.keys(this.props.docViews[0].Document[DocData])), ...Array.from(Object.keys(this.props.docViews[0].Document))]
+ .filter(key => key.startsWith('layout_') && (
+ StrCast(this.props.docViews[0].Document[key]).startsWith("<") ||
+ DocCast(this.props.docViews[0].Document[key])?.isTemplateDoc
+ ))
+ .forEach(key => runInAction(() => this._addedKeys.add(key.replace('layout_', '')))); // prettier-ignore
+ }
+ @computed get scriptField() {
+ const script = ScriptField.MakeScript('docs.map(d => switchView(d, this))', { this: Doc.name }, { docs: this.props.docViews.map(dv => dv.Document) as any });
+ return script ? () => script : undefined;
+ }
toggleLayout = (e: React.ChangeEvent<HTMLInputElement>, layout: string): void => {
this.props.docViews.map(dv => dv.switchViews(e.target.checked, layout, undefined, true));
};
- toggleDefault = (e: React.ChangeEvent<HTMLInputElement>): void => {
+ toggleDefault = (): void => {
this.props.docViews.map(dv => dv.switchViews(false, 'layout'));
};
@@ -65,21 +65,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
runInAction(() => this._addedKeys.add(this._customRef.current!.value));
}
};
- componentDidMount() {
- !this._addedKeys && (this._addedKeys = new ObservableSet());
- [...Array.from(Object.keys(this.props.docViews[0].Document[DocData])), ...Array.from(Object.keys(this.props.docViews[0].Document))]
- .filter(key => key.startsWith('layout_') && (
- StrCast(this.props.docViews[0].Document[key]).startsWith("<") ||
- DocCast(this.props.docViews[0].Document[key])?.isTemplateDoc
- ))
- .map(key => runInAction(() => this._addedKeys.add(key.replace('layout_', '')))); // prettier-ignore
- }
return100 = () => 300;
- @computed get scriptField() {
- const script = ScriptField.MakeScript('docs.map(d => switchView(d, this))', { this: Doc.name }, { docs: this.props.docViews.map(dv => dv.Document) as any });
- return script ? () => script : undefined;
- }
templateIsUsed = (selDoc: Doc, templateDoc: Doc) => {
const template = StrCast(templateDoc.dragFactory ? Cast(templateDoc.dragFactory, Doc, null)?.title : templateDoc.title);
return StrCast(selDoc.layout_fieldKey) === 'layout_' + template ? 'check' : 'unchecked';
@@ -88,10 +75,11 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
TraceMobx();
const firstDoc = this.props.docViews[0].Document;
const templateName = StrCast(firstDoc.layout_fieldKey, 'layout').replace('layout_', '');
- const noteTypes = DocListCast(Cast(Doc.UserDoc()['template_notes'], Doc, null)?.data);
- const addedTypes = DocListCast(Cast(Doc.UserDoc()['template_clickFuncs'], Doc, null)?.data);
+ const noteTypes = DocListCast(Cast(Doc.UserDoc().template_notes, Doc, null)?.data);
+ const addedTypes = DocListCast(Cast(Doc.UserDoc().template_clickFuncs, Doc, null)?.data);
const templateMenu: Array<JSX.Element> = [];
templateMenu.push(<OtherToggle key="default" name={firstDoc.layout instanceof Doc ? StrCast(firstDoc.layout.title) : 'Default'} checked={templateName === 'layout'} toggle={this.toggleDefault} />);
+ // eslint-disable-next-line no-return-assign
addedTypes.concat(noteTypes).map(template => (template.treeView_Checked = this.templateIsUsed(firstDoc, template)));
this._addedKeys &&
Array.from(this._addedKeys)
@@ -122,10 +110,10 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
addDocTab={returnFalse}
PanelWidth={this.return100}
PanelHeight={this.return100}
- treeViewHideHeaderFields={true}
- treeViewHideTitle={true}
- dontRegisterView={true}
- fieldKey={'data'}
+ treeViewHideHeaderFields
+ treeViewHideTitle
+ dontRegisterView
+ fieldKey="data"
moveDocument={returnFalse}
removeDocument={returnFalse}
addDocument={returnFalse}
@@ -135,10 +123,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
}
}
-ScriptingGlobals.add(function switchView(doc: Doc, template: Doc | undefined) {
- if (template?.dragFactory) {
- template = Cast(template.dragFactory, Doc, null);
- }
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function switchView(doc: Doc, templateIn: Doc | undefined) {
+ const template = templateIn?.dragFactory ? Cast(templateIn.dragFactory, Doc, null) : templateIn;
const templateTitle = StrCast(template?.title);
return templateTitle && DocUtils.makeCustomViewClicked(doc, Docs.Create.FreeformDocument, templateTitle, template);
});
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
deleted file mode 100644
index 436cb688f..000000000
--- a/src/client/views/Touchable.tsx
+++ /dev/null
@@ -1,213 +0,0 @@
-import * as React from 'react';
-import { action } from 'mobx';
-import { InteractionUtils } from '../util/InteractionUtils';
-
-const HOLD_DURATION = 1000;
-
-export abstract class Touchable<T = {}> extends React.Component<React.PropsWithChildren<T>> {
- //private holdTimer: NodeJS.Timeout | undefined;
- private moveDisposer?: InteractionUtils.MultiTouchEventDisposer;
- private endDisposer?: InteractionUtils.MultiTouchEventDisposer;
- private holdMoveDisposer?: InteractionUtils.MultiTouchEventDisposer;
- private holdEndDisposer?: InteractionUtils.MultiTouchEventDisposer;
-
- protected abstract _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
- protected _touchDrag: boolean = false;
- protected prevPoints: Map<number, React.Touch> = new Map<number, React.Touch>();
-
- public FirstX: number = 0;
- public FirstY: number = 0;
- public SecondX: number = 0;
- public SecondY: number = 0;
-
- /**
- * When a touch even starts, we keep track of each touch that is associated with that event
- */
- @action
- protected onTouchStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => {
- const actualPts: React.Touch[] = [];
- const te = me.touchEvent;
- // loop through all touches on screen
- for (const pt of me.touches) {
- actualPts.push(pt);
- if (this.prevPoints.has(pt.identifier)) {
- this.prevPoints.set(pt.identifier, pt);
- }
- // only add the ones that are targeted on "this" element, but with the identifier that the screen touch gives
- for (const tPt of me.changedTouches) {
- if (pt.clientX === tPt.clientX && pt.clientY === tPt.clientY) {
- // pen is also a touch, but with a radius of 0.5 (at least with the surface pens)
- // and this seems to be the only way of differentiating pen and touch on touch events
- if ((pt as any).radiusX > 1 && (pt as any).radiusY > 1) {
- this.prevPoints.set(pt.identifier, pt);
- }
- }
- }
- }
-
- const ptsToDelete: number[] = [];
- this.prevPoints.forEach(pt => {
- if (!actualPts.includes(pt)) {
- ptsToDelete.push(pt.identifier);
- }
- });
-
- ptsToDelete.forEach(pt => this.prevPoints.delete(pt));
-
- if (this.prevPoints.size) {
- switch (this.prevPoints.size) {
- case 1:
- this.handle1PointerDown(te, me);
- te.persist();
- // -- code for radial menu --
- // if (this.holdTimer) {
- // clearTimeout(this.holdTimer)
- // this.holdTimer = undefined;
- // }
- break;
- case 2:
- this.handle2PointersDown(te, me);
- break;
- }
- }
- };
-
- /**
- * Handle touch move event
- */
- @action
- protected onTouch = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
- const te = me.touchEvent;
- const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
-
- // if we're not actually moving a lot, don't consider it as dragging yet
- if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return;
- this._touchDrag = true;
- switch (myTouches.length) {
- case 1:
- this.handle1PointerMove(te, me);
- break;
- case 2:
- this.handle2PointersMove(te, me);
- break;
- }
-
- for (const pt of me.touches) {
- if (pt && this.prevPoints.has(pt.identifier)) {
- this.prevPoints.set(pt.identifier, pt);
- }
- }
- };
-
- @action
- protected onTouchEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
- // remove all the touches associated with the event
- const te = me.touchEvent;
- for (const pt of me.changedTouches) {
- if (pt) {
- if (this.prevPoints.has(pt.identifier)) {
- this.prevPoints.delete(pt.identifier);
- }
- }
- }
- this._touchDrag = false;
- te.stopPropagation();
-
- // if (e.targetTouches.length === 0) {
- // this.prevPoints.clear();
- // }
-
- if (this.prevPoints.size === 0) {
- this.cleanUpInteractions();
- }
- e.stopPropagation();
- };
-
- cleanUpInteractions = (): void => {
- this.removeMoveListeners();
- this.removeEndListeners();
- };
-
- handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>): any => {
- e.stopPropagation();
- e.preventDefault();
- };
-
- handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>): any => {
- e.stopPropagation();
- e.preventDefault();
- };
-
- handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
- this.removeMoveListeners();
- this.addMoveListeners();
- this.removeEndListeners();
- this.addEndListeners();
- };
-
- handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
- this.removeMoveListeners();
- this.addMoveListeners();
- this.removeEndListeners();
- this.addEndListeners();
- };
-
- handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
- e.stopPropagation();
- me.touchEvent.stopPropagation();
- this.removeMoveListeners();
- this.removeEndListeners();
- this.removeHoldMoveListeners();
- this.removeHoldEndListeners();
- this.addHoldMoveListeners();
- this.addHoldEndListeners();
- };
-
- addMoveListeners = () => {
- const handler = (e: Event) => this.onTouch(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
- document.addEventListener('dashOnTouchMove', handler);
- this.moveDisposer = () => document.removeEventListener('dashOnTouchMove', handler);
- };
- addEndListeners = () => {
- const handler = (e: Event) => this.onTouchEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
- document.addEventListener('dashOnTouchEnd', handler);
- this.endDisposer = () => document.removeEventListener('dashOnTouchEnd', handler);
- };
-
- addHoldMoveListeners = () => {
- const handler = (e: Event) => this.handle1PointerHoldMove(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
- document.addEventListener('dashOnTouchHoldMove', handler);
- this.holdMoveDisposer = () => document.removeEventListener('dashOnTouchHoldMove', handler);
- };
-
- addHoldEndListeners = () => {
- const handler = (e: Event) => this.handle1PointerHoldEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
- document.addEventListener('dashOnTouchHoldEnd', handler);
- this.holdEndDisposer = () => document.removeEventListener('dashOnTouchHoldEnd', handler);
- };
-
- removeMoveListeners = () => this.moveDisposer?.();
- removeEndListeners = () => this.endDisposer?.();
- removeHoldMoveListeners = () => this.holdMoveDisposer?.();
- removeHoldEndListeners = () => this.holdEndDisposer?.();
-
- handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
- // e.stopPropagation();
- // me.touchEvent.stopPropagation();
- };
-
- handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
- e.stopPropagation();
- me.touchEvent.stopPropagation();
- this.removeHoldMoveListeners();
- this.removeHoldEndListeners();
-
- me.touchEvent.stopPropagation();
- me.touchEvent.preventDefault();
- };
-
- handleHandDown = (e: React.TouchEvent) => {
- // e.stopPropagation();
- // e.preventDefault();
- };
-}
diff --git a/src/client/views/UndoStack.tsx b/src/client/views/UndoStack.tsx
index 068143225..2d461c0ab 100644
--- a/src/client/views/UndoStack.tsx
+++ b/src/client/views/UndoStack.tsx
@@ -1,6 +1,7 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { Tooltip } from '@mui/material';
import { Popup, Type } from 'browndash-components';
-import { observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { StrCast } from '../../fields/Types';
@@ -15,19 +16,19 @@ export class UndoStack extends React.Component<UndoStackProps> {
const background = UndoManager.batchCounter.get() ? 'yellow' : SettingsManager.userVariantColor;
const color = UndoManager.batchCounter.get() ? 'black' : SettingsManager.userColor;
return (
- <Tooltip title={'undo stack (if it stays yellow, undo is broken - you should reload Dash)'}>
+ <Tooltip title="undo stack (if it stays yellow, undo is broken - you should reload Dash)">
<div>
<div className="undoStack-outerContainer">
<Popup
text="stack"
color={color}
background={background}
- placement={`top-start`}
+ placement="top-start"
type={Type.TERT}
popup={
<div
className="undoStack-commandsContainer"
- ref={r => r?.scroll({ behavior: 'auto', top: r?.scrollHeight + 20 })}
+ ref={r => r?.scroll({ behavior: 'auto', top: (r?.scrollHeight ?? 0) + 20 })}
style={{
background,
color,
@@ -35,12 +36,13 @@ export class UndoStack extends React.Component<UndoStackProps> {
{Array.from(UndoManager.undoStackNames).map((name, i) => (
<div
className="undoStack-resultContainer"
+ // eslint-disable-next-line react/no-array-index-key
key={i}
- onClick={e => {
+ onClick={() => {
const size = UndoManager.undoStackNames.length;
for (let n = 0; n < size - i; n++) UndoManager.Undo();
}}>
- <div className="undoStack-commandString">{StrCast(name).replace(/[^\.]*\./, '')}</div>
+ <div className="undoStack-commandString">{StrCast(name).replace(/[^.]*\./, '')}</div>
</div>
))}
{Array.from(UndoManager.redoStackNames)
@@ -48,12 +50,13 @@ export class UndoStack extends React.Component<UndoStackProps> {
.map((name, i) => (
<div
className="undoStack-resultContainer"
+ // eslint-disable-next-line react/no-array-index-key
key={i}
- onClick={e => {
+ onClick={() => {
for (let n = 0; n <= i; n++) UndoManager.Redo();
}}>
<div className="undoStack-commandString" style={{ fontWeight: 'bold', background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
- {StrCast(name).replace(/[^\.]*\./, '')}
+ {StrCast(name).replace(/[^.]*\./, '')}
</div>
</div>
))}
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index cc4da1694..c4d35330b 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -4,7 +4,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils';
+import { setupMoveUpEvents } from '../../../ClientUtils';
+import { Utils, emptyFunction } from '../../../Utils';
import { Doc, DocListCast } from '../../../fields/Doc';
import { BoolCast, NumCast, StrCast } from '../../../fields/Types';
import { DocumentType } from '../../documents/DocumentTypes';
@@ -45,13 +46,13 @@ import { Track } from './Track';
@observer
export class Timeline extends ObservableReactComponent<FieldViewProps> {
- //readonly constants
+ // readonly constants
private readonly DEFAULT_TICK_SPACING: number = 50;
private readonly MAX_TITLE_HEIGHT = 75;
private readonly MAX_CONTAINER_HEIGHT: number = 800;
private readonly DEFAULT_TICK_INCREMENT: number = 1000;
- //height variables
+ // height variables
private DEFAULT_CONTAINER_HEIGHT: number = 330;
private MIN_CONTAINER_HEIGHT: number = 205;
@@ -60,7 +61,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
makeObservable(this);
}
- //react refs
+ // react refs
@observable private _trackbox = React.createRef<HTMLDivElement>();
@observable private _titleContainer = React.createRef<HTMLDivElement>();
@observable private _timelineContainer = React.createRef<HTMLDivElement>();
@@ -68,7 +69,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
@observable private _roundToggleRef = React.createRef<HTMLDivElement>();
@observable private _roundToggleContainerRef = React.createRef<HTMLDivElement>();
- //boolean vars and instance vars
+ // boolean vars and instance vars
@observable private _currentBarX: number = 0;
@observable private _windSpeed: number = 1;
@observable private _totalLength: number = 0;
@@ -77,11 +78,11 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
@observable private _containerHeight: number = this.DEFAULT_CONTAINER_HEIGHT;
@observable private _tickSpacing = this.DEFAULT_TICK_SPACING;
@observable private _tickIncrement = this.DEFAULT_TICK_INCREMENT;
- @observable private _time = 100000; //DEFAULT
+ @observable private _time = 100000; // DEFAULT
@observable private _playButton = faPlayCircle;
@observable private _titleHeight = 0;
- @observable public IsPlaying: boolean = false; //scrubber playing
+ @observable public IsPlaying: boolean = false; // scrubber playing
/**
* collection get method. Basically defines what defines collection's children. These will be tracked in the timeline. Do not edit.
@@ -95,30 +96,30 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
return DocListCast(this._props.Document[this._props.fieldKey]);
}
- /////////lifecycle functions////////////
+ /// //////lifecycle functions////////////
@action
componentDidMount() {
- const relativeHeight = window.innerHeight / 20; //sets height to arbitrary size, relative to innerHeight
- this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; //check if relHeight is less than Maxheight. Else, just set relheight to max
- this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; //offset
- this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; //twice the titleheight + offset
+ const relativeHeight = window.innerHeight / 20; // sets height to arbitrary size, relative to innerHeight
+ this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; // check if relHeight is less than Maxheight. Else, just set relheight to max
+ this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; // offset
+ this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; // twice the titleheight + offset
if (!this._props.Document.AnimationLength) {
- //if animation length did not exist
- this._props.Document.AnimationLength = this._time; //set it to default time
+ // if animation length did not exist
+ this._props.Document.AnimationLength = this._time; // set it to default time
} else {
- this._time = NumCast(this._props.Document.AnimationLength); //else, set time to animationlength stored from before
+ this._time = NumCast(this._props.Document.AnimationLength); // else, set time to animationlength stored from before
}
- this._totalLength = this._tickSpacing * (this._time / this._tickIncrement); //the entire length of the timeline div (actual div part itself)
- this._visibleLength = this._infoContainer.current!.getBoundingClientRect().width; //the visible length of the timeline (the length that you current see)
- this._visibleStart = this._infoContainer.current!.scrollLeft; //where the div starts
- this._props.Document.isATOn = !this._props.Document.isATOn; //turns the boolean on, saying AT (animation timeline) is on
+ this._totalLength = this._tickSpacing * (this._time / this._tickIncrement); // the entire length of the timeline div (actual div part itself)
+ this._visibleLength = this._infoContainer.current!.getBoundingClientRect().width; // the visible length of the timeline (the length that you current see)
+ this._visibleStart = this._infoContainer.current!.scrollLeft; // where the div starts
+ this._props.Document.isATOn = !this._props.Document.isATOn; // turns the boolean on, saying AT (animation timeline) is on
this.toggleHandle();
}
componentWillUnmount() {
- this._props.Document.AnimationLength = this._time; //save animation length
+ this._props.Document.AnimationLength = this._time; // save animation length
}
- /////////////////////////////////////////////////
+ /// //////////////////////////////////////////////
/**
* React Functional Component
@@ -146,7 +147,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
pixel <= 0 ? (this._currentBarX = 0) : pixel >= this._totalLength ? (this._currentBarX = this._totalLength) : (this._currentBarX = pixel);
};
- //for playing
+ // for playing
onPlay = (e: React.MouseEvent) => {
e.stopPropagation();
this.play();
@@ -179,7 +180,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
e.preventDefault();
e.stopPropagation();
if (this._windSpeed < 64) {
- //max speed is 32
+ // max speed is 32
this._windSpeed = this._windSpeed * 2;
}
};
@@ -213,7 +214,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
const scrubberbox = this._infoContainer.current!;
const left = scrubberbox.getBoundingClientRect().left;
const offsetX = Math.round(e.clientX - left) * this._props.ScreenToLocalTransform().Scale;
- this.changeCurrentBarX(offsetX + this._visibleStart); //changes scrubber to clicked scrubber position
+ this.changeCurrentBarX(offsetX + this._visibleStart); // changes scrubber to clicked scrubber position
return false;
};
@@ -353,12 +354,12 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
* tool box includes the toggle buttons at the top of the timeline (both editing mode and play mode)
*/
private timelineToolBox = (scale: number, totalTime: number) => {
- const size = 40 * scale; //50 is default
+ const size = 40 * scale; // 50 is default
const iconSize = 25;
const width: number = this._props.PanelWidth();
const modeType = this._props.Document.isATOn ? 'Author' : 'Play';
- //decides if information should be omitted because the timeline is very small
+ // decides if information should be omitted because the timeline is very small
// if its less than 950 pixels then it's going to be overlapping
let modeString = modeType,
overviewString = '',
@@ -467,7 +468,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
this._props.Document.isATOn = !this._props.Document.isATOn;
if (!BoolCast(this._props.Document.isATOn)) {
- //turning on playmode...
+ // turning on playmode...
roundToggle.style.transform = 'translate(0px, 0px)';
roundToggle.style.animationName = 'turnoff';
roundToggleContainer.style.animationName = 'turnoff';
@@ -475,7 +476,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
timelineContainer.style.top = `${-this._containerHeight}px`;
this.toPlay();
} else {
- //turning on authoring mode...
+ // turning on authoring mode...
roundToggle.style.transform = 'translate(20px, 0px)';
roundToggle.style.animationName = 'turnon';
roundToggleContainer.style.animationName = 'turnon';
@@ -488,8 +489,8 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
@action.bound
changeLengths() {
if (this._infoContainer.current) {
- this._visibleLength = this._infoContainer.current.getBoundingClientRect().width; //the visible length of the timeline (the length that you current see)
- this._visibleStart = this._infoContainer.current.scrollLeft; //where the div starts
+ this._visibleLength = this._infoContainer.current.getBoundingClientRect().width; // the visible length of the timeline (the length that you current see)
+ this._visibleStart = this._infoContainer.current.scrollLeft; // where the div starts
}
}
@@ -513,7 +514,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
}
}
} else {
- //TODO: remove undefineds and duplicates
+ // TODO: remove undefineds and duplicates
}
});
return longestTime;
diff --git a/src/client/views/animationtimeline/TimelineMenu.tsx b/src/client/views/animationtimeline/TimelineMenu.tsx
index 97a571dc4..0d7873931 100644
--- a/src/client/views/animationtimeline/TimelineMenu.tsx
+++ b/src/client/views/animationtimeline/TimelineMenu.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { IconLookup } from '@fortawesome/fontawesome-svg-core';
import { faChartLine, faClipboard } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -9,6 +11,7 @@ import './TimelineMenu.scss';
@observer
export class TimelineMenu extends React.Component {
+ // eslint-disable-next-line no-use-before-define
public static Instance: TimelineMenu;
@observable private _opacity = 0;
diff --git a/src/client/views/animationtimeline/TimelineOverview.tsx b/src/client/views/animationtimeline/TimelineOverview.tsx
index 489c4dcde..7bf685c9e 100644
--- a/src/client/views/animationtimeline/TimelineOverview.tsx
+++ b/src/client/views/animationtimeline/TimelineOverview.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/no-unused-prop-types */
import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -32,7 +33,6 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
@observable private visibleTime: number = 0;
@observable private currentX: number = 0;
@observable private visibleStart: number = 0;
- private readonly DEFAULT_HEIGHT = 50;
private readonly DEFAULT_WIDTH = 300;
componentDidMount() {
@@ -50,9 +50,9 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
);
}
- componentWillUnmount = () => {
+ componentWillUnmount() {
this._authoringReaction && this._authoringReaction();
- };
+ }
@action
setOverviewWidth() {
@@ -109,7 +109,7 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
e.preventDefault();
e.stopPropagation();
const scrubberRef = this._scrubberRef.current!;
- const left = scrubberRef.getBoundingClientRect().left;
+ const { left } = scrubberRef.getBoundingClientRect();
const offsetX = Math.round(e.clientX - left);
this.props.changeCurrentBarX((offsetX / this.activeOverviewWidth) * this.props.totalLength + this.props.currentBarX);
};
@@ -152,17 +152,17 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
const timeline = this.props.isAuthoring
? [
<div key="timeline-overview-container" className="timeline-overview-container overviewBar" id="timelineOverview" ref={this.authoringContainer}>
- <div ref={this._visibleRef} key="1" className="timeline-overview-visible" style={{ left: `${barStart}px`, width: `${visibleBarWidth}px` }} onPointerDown={this.onPointerDown}></div>,
+ <div ref={this._visibleRef} key="1" className="timeline-overview-visible" style={{ left: `${barStart}px`, width: `${visibleBarWidth}px` }} onPointerDown={this.onPointerDown} />,
<div ref={this._scrubberRef} key="2" className="timeline-overview-scrubber-container" style={{ left: `${scrubberStart}px` }} onPointerDown={this.onScrubberDown}>
- <div key="timeline-overview-scrubber-head" className="timeline-overview-scrubber-head"></div>
+ <div key="timeline-overview-scrubber-head" className="timeline-overview-scrubber-head" />
</div>
</div>,
]
: [
<div key="1" className="timeline-play-bar overviewBar" id="timelinePlay" ref={this.playbackContainer}>
- <div ref={this._scrubberRef} className="timeline-play-head" style={{ left: `${scrubberStart}px` }} onPointerDown={this.onScrubberDown}></div>
+ <div ref={this._scrubberRef} className="timeline-play-head" style={{ left: `${scrubberStart}px` }} onPointerDown={this.onScrubberDown} />
</div>,
- <div key="2" className="timeline-play-tail" style={{ width: `${playWidth}px` }}></div>,
+ <div key="2" className="timeline-play-tail" style={{ width: `${playWidth}px` }} />,
];
return (
<div className="timeline-flex">
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index 490a14be5..1e4ed74be 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -73,7 +73,7 @@ export class Track extends ObservableReactComponent<IProps> {
this._timelineVisibleReaction?.();
this._autoKfReaction?.();
}
- ////////////////////////////////
+ // //////////////////////////////
getLastRegionTime = () => {
let lastTime: number = 0;
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx
index cbcc980a9..a08a7c7c1 100644
--- a/src/client/views/collections/CollectionCalendarView.tsx
+++ b/src/client/views/collections/CollectionCalendarView.tsx
@@ -1,7 +1,8 @@
import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { dateRangeStrToDates, emptyFunction, returnTrue } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
+import { dateRangeStrToDates, returnTrue } from '../../../ClientUtils';
import { Doc, DocListCast } from '../../../fields/Doc';
import { StrCast } from '../../../fields/Types';
import { CollectionStackingView } from './CollectionStackingView';
@@ -24,16 +25,13 @@ export class CollectionCalendarView extends CollectionSubView() {
removeCalendar = () => {};
- addCalendar = (doc: Doc | Doc[], annotationKey?: string | undefined): boolean => {
+ addCalendar = (/* doc: Doc | Doc[], annotationKey?: string | undefined */): boolean =>
// bring up calendar modal with option to create a calendar
- return true;
- };
+ true;
_stackRef = React.createRef<CollectionStackingView>();
- panelHeight = () => {
- return this._props.PanelHeight() - 40; // this should be the height of the stacking view. For now, it's the hieight of the calendar view minus 40 to allow for a title
- };
+ panelHeight = () => this._props.PanelHeight() - 40; // this should be the height of the stacking view. For now, it's the hieight of the calendar view minus 40 to allow for a title
// most recent calendar should come first
sortByMostRecentDate = (calendarA: Doc, calendarB: Doc) => {
@@ -45,18 +43,18 @@ export class CollectionCalendarView extends CollectionSubView() {
if (aFromDate > bFromDate) {
return -1; // a comes first
- } else if (aFromDate < bFromDate) {
+ }
+ if (aFromDate < bFromDate) {
+ return 1; // b comes first
+ }
+ // start dates are the same
+ if (aToDate > bToDate) {
+ return -1; // a comes first
+ }
+ if (aToDate < bToDate) {
return 1; // b comes first
- } else {
- // start dates are the same
- if (aToDate > bToDate) {
- return -1; // a comes first
- } else if (aToDate < bToDate) {
- return 1; // b comes first
- } else {
- return 0; // same start and end dates
- }
}
+ return 0; // same start and end dates
};
screenToLocalTransform = () =>
@@ -73,12 +71,12 @@ export class CollectionCalendarView extends CollectionSubView() {
return (
<div className="collectionCalendarView">
<CollectionStackingView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={emptyFunction}
ref={this._stackRef}
PanelHeight={this.panelHeight}
PanelWidth={this._props.PanelWidth}
- // childFilters={this.childFilters} DO I NEED THIS?
sortFunc={this.sortByMostRecentDate}
setHeight={undefined}
isAnnotationOverlay={false}
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 4e4bd43bf..7617f2a52 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -1,25 +1,28 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Utils, emptyFunction, returnFalse, returnZero } from '../../../Utils';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { returnZero } from '../../../ClientUtils';
+import { Utils } from '../../../Utils';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { DocumentType } from '../../documents/DocumentTypes';
import { DragManager } from '../../util/DragManager';
-import { SelectionManager } from '../../util/SelectionManager';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
import { DocumentView } from '../nodes/DocumentView';
-import { FocusViewOptions } from '../nodes/FieldView';
+import { FocusViewOptions } from '../nodes/FocusViewOptions';
import './CollectionCarousel3DView.scss';
import { CollectionSubView } from './CollectionSubView';
+
const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss');
@observer
export class CollectionCarousel3DView extends CollectionSubView() {
@computed get scrollSpeed() {
- return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; //default scroll speed
+ return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; // default scroll speed
}
constructor(props: any) {
super(props);
@@ -48,16 +51,16 @@ export class CollectionCarousel3DView extends CollectionSubView() {
panelHeight = () => this._props.PanelHeight() * Number(CAROUSEL3D_SIDE_SCALE);
onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
- isChildContentActive = () => (this.isContentActive() ? true : false);
+ isChildContentActive = () => !!this.isContentActive();
childScreenToLocal = () =>
this._props // document's left is the panel shifted by the doc's index * panelWidth/#docs. But it scales by centerScale around its center, so it's left moves left by the distance of the left from the center (panelwidth/2) * the scale delta (centerScale-1)
.ScreenToLocalTransform() // the top behaves the same way ecept it's shifted by the 'top' amount specified for the panel in css and then by the scale factor.
.translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, -((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2)
.scale(1 / this.centerScale);
- focus = (anchor: Doc, options: FocusViewOptions) => {
+ focus = (anchor: Doc, options: FocusViewOptions): Opt<number> => {
const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
- if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return;
+ if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return undefined;
options.didMove = true;
const target = DocCast(anchor.annotationOn) ?? anchor;
const index = docs.indexOf(target);
@@ -66,41 +69,38 @@ export class CollectionCarousel3DView extends CollectionSubView() {
};
@computed get content() {
const currentIndex = NumCast(this.layoutDoc._carousel_index);
- const displayDoc = (childPair: { layout: Doc; data: Doc }) => {
- return (
- <DocumentView
- {...this._props}
- Document={childPair.layout}
- TemplateDataDocument={childPair.data}
- //suppressSetHeight={true}
- NativeWidth={returnZero}
- NativeHeight={returnZero}
- layout_fitWidth={undefined}
- onDoubleClickScript={this.onChildDoubleClick}
- renderDepth={this._props.renderDepth + 1}
- LayoutTemplate={this._props.childLayoutTemplate}
- LayoutTemplateString={this._props.childLayoutString}
- focus={this.focus}
- ScreenToLocalTransform={this.childScreenToLocal}
- isContentActive={this.isChildContentActive}
- isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight}
- />
- );
- };
-
- return this.carouselItems.map((childPair, index) => {
- return (
- <div key={childPair.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}>
- {displayDoc(childPair)}
- </div>
- );
- });
+ const displayDoc = (childPair: { layout: Doc; data: Doc }) => (
+ <DocumentView
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ {...this._props}
+ Document={childPair.layout}
+ TemplateDataDocument={childPair.data}
+ // suppressSetHeight={true}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ layout_fitWidth={undefined}
+ onDoubleClickScript={this.onChildDoubleClick}
+ renderDepth={this._props.renderDepth + 1}
+ LayoutTemplate={this._props.childLayoutTemplate}
+ LayoutTemplateString={this._props.childLayoutString}
+ focus={this.focus}
+ ScreenToLocalTransform={this.childScreenToLocal}
+ isContentActive={this.isChildContentActive}
+ isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
+ />
+ );
+
+ return this.carouselItems.map((childPair, index) => (
+ <div key={childPair.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}>
+ {displayDoc(childPair)}
+ </div>
+ ));
}
changeSlide = (direction: number) => {
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + direction + this.carouselItems.length) % this.carouselItems.length;
};
@@ -124,21 +124,21 @@ export class CollectionCarousel3DView extends CollectionSubView() {
};
toggleAutoScroll = (direction: number) => {
- this.layoutDoc.autoScrollOn = this.layoutDoc.autoScrollOn ? false : true;
+ this.layoutDoc.autoScrollOn = !this.layoutDoc.autoScrollOn;
this.layoutDoc.autoScrollOn ? this.startAutoScroll(direction) : this.stopAutoScroll();
};
fadeScrollButton = () => {
window.setTimeout(() => {
- !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = 'none'); //fade away after 1.5s if it's not clicked.
+ !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = 'none'); // fade away after 1.5s if it's not clicked.
}, 1500);
};
@computed get buttons() {
return (
<div className="arrow-buttons">
- <div title="click to go back" key="back" className="carousel3DView-back" onClick={e => this.onArrowClick(-1)} />
- <div title="click to advance" key="fwd" className="carousel3DView-fwd" onClick={e => this.onArrowClick(1)} />
+ <div title="click to go back" key="back" className="carousel3DView-back" onClick={() => this.onArrowClick(-1)} />
+ <div title="click to advance" key="fwd" className="carousel3DView-fwd" onClick={() => this.onArrowClick(1)} />
{/* {this.autoScrollButton} */}
</div>
);
@@ -149,17 +149,25 @@ export class CollectionCarousel3DView extends CollectionSubView() {
return (
<>
<div className={`carousel3DView-back-scroll${whichButton === 'back' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(-1)}>
- {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-left'} size={'1x'} />}
+ {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon="pause" size="1x" /> : <FontAwesomeIcon icon="angle-double-left" size="1x" />}
</div>
<div className={`carousel3DView-fwd-scroll${whichButton === 'fwd' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(1)}>
- {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-right'} size={'1x'} />}
+ {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon="pause" size="1x" /> : <FontAwesomeIcon icon="angle-double-right" size="1x" />}
</div>
</>
);
}
@computed get dots() {
- return this.carouselItems.map((_child, index) => <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._carousel_index) ? '-active' : ''}`} onClick={() => (this.layoutDoc._carousel_index = index)} />);
+ return this.carouselItems.map((_child, index) => (
+ <div
+ key={Utils.GenerateGuid()}
+ className={`dot${index === NumCast(this.layoutDoc._carousel_index) ? '-active' : ''}`}
+ onClick={() => {
+ this.layoutDoc._carousel_index = index;
+ }}
+ />
+ ));
}
@computed get translateX() {
const index = NumCast(this.layoutDoc._carousel_index);
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 9c370bfbb..9d3657995 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -1,18 +1,22 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/jsx-props-no-spreading */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { StopEvent, emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
+import { StopEvent, returnFalse, returnOne, returnZero } from '../../../ClientUtils';
import { Doc, Opt } from '../../../fields/Doc';
import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { DocumentType } from '../../documents/DocumentTypes';
import { DragManager } from '../../util/DragManager';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
import { DocumentView } from '../nodes/DocumentView';
import { FieldViewProps } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import './CollectionCarouselView.scss';
import { CollectionSubView } from './CollectionSubView';
-import { DocumentType } from '../../documents/DocumentTypes';
@observer
export class CollectionCarouselView extends CollectionSubView() {
@@ -48,7 +52,7 @@ export class CollectionCarouselView extends CollectionSubView() {
};
captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string): any => {
// first look for properties on the document in the carousel, then fallback to properties on the container
- const childValue = doc?.['caption-' + property] ? this._props.styleProvider?.(doc, captionProps, property) : undefined;
+ const childValue = doc?.['caption_' + property] ? this._props.styleProvider?.(doc, captionProps, property) : undefined;
return childValue ?? this._props.styleProvider?.(this.layoutDoc, captionProps, property);
};
panelHeight = () => this._props.PanelHeight() - (StrCast(this.layoutDoc._layout_showCaption) ? 50 : 0);
@@ -106,10 +110,10 @@ export class CollectionCarouselView extends CollectionSubView() {
return (
<>
<div key="back" className="carouselView-back" onClick={this.goback}>
- <FontAwesomeIcon icon={'chevron-left'} size={'2x'} />
+ <FontAwesomeIcon icon="chevron-left" size="2x" />
</div>
<div key="fwd" className="carouselView-fwd" onClick={this.advance}>
- <FontAwesomeIcon icon={'chevron-right'} size={'2x'} />
+ <FontAwesomeIcon icon="chevron-right" size="2x" />
</div>
</>
);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index b2897a9b7..0ee3575f3 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -2,60 +2,55 @@ import { action, IReactionDisposer, makeObservable, observable, reaction } from
import { observer } from 'mobx-react';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
-import * as GoldenLayout from '../../../client/goldenLayout';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, incrementTitleCopy, returnTrue, UpdateIcon } from '../../../ClientUtils';
import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
-import { FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { GetEffectiveAcl, inheritParentAcls, SetPropSetterCb } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, emptyFunction, incrementTitleCopy } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
import { DocServer } from '../../DocServer';
import { Docs } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DocumentManager } from '../../util/DocumentManager';
+import * as GoldenLayout from '../../goldenLayout';
import { DragManager } from '../../util/DragManager';
import { InteractionUtils } from '../../util/InteractionUtils';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { SelectionManager } from '../../util/SelectionManager';
-import { SettingsManager } from '../../util/SettingsManager';
+import { SnappingManager } from '../../util/SnappingManager';
import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
import { DashboardView } from '../DashboardView';
import { LightboxView } from '../LightboxView';
-import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
+import { DocumentView } from '../nodes/DocumentView';
+import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere';
import { OverlayView } from '../OverlayView';
import { ScriptingRepl } from '../ScriptingRepl';
import { UndoStack } from '../UndoStack';
import './CollectionDockingView.scss';
-import { CollectionFreeFormView } from './collectionFreeForm';
import { CollectionSubView } from './CollectionSubView';
-import { TabDocView } from './TabDocView';
-import { ComputedField } from '../../../fields/ScriptField';
+
const _global = (window /* browser */ || global) /* node */ as any;
@observer
export class CollectionDockingView extends CollectionSubView() {
- @observable public static Instance: CollectionDockingView | undefined = undefined;
- public static makeDocumentConfig(document: Doc, panelName?: string, width?: number, keyValue?: boolean) {
- return {
- type: 'react-component',
- component: 'DocumentFrameRenderer',
- title: document.title,
- width: width,
- props: {
- documentId: document[Id],
- keyValue,
- panelName, // name of tab that can be used to close or replace its contents
- },
- };
+ static tabClass: JSX.Element | null = null;
+ /**
+ * Configure golden layout to render its documents using the specified React component
+ * @param ele - typically would be set to TabDocView
+ */
+ static setTabJSXComponent(ele: any) {
+ this.tabClass = ele;
}
+ // eslint-disable-next-line no-use-before-define
+ @observable public static Instance: CollectionDockingView | undefined = undefined;
private _reactionDisposer?: IReactionDisposer;
private _lightboxReactionDisposer?: IReactionDisposer;
private _containerRef = React.createRef<HTMLDivElement>();
- public _flush: UndoManager.Batch | undefined;
+ private _flush: UndoManager.Batch | undefined;
+ private _unmounting = false;
private _ignoreStateChange = '';
public tabMap: Set<any> = new Set();
public get HasFullScreen() {
@@ -68,7 +63,7 @@ export class CollectionDockingView extends CollectionSubView() {
super(props);
makeObservable(this);
if (this._props.renderDepth < 0) CollectionDockingView.Instance = this;
- //Why is this here?
+ // Why is this here?
(window as any).React = React;
(window as any).ReactDOM = ReactDOM;
DragManager.StartWindowDrag = this.StartOtherDrag;
@@ -84,7 +79,7 @@ export class CollectionDockingView extends CollectionSubView() {
*/
public StartOtherDrag = (e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => {
this._flush = this._flush ?? UndoManager.StartBatch('golden layout drag');
- const config = dragDocs.length === 1 ? CollectionDockingView.makeDocumentConfig(dragDocs[0]) : { type: 'row', content: dragDocs.map(doc => CollectionDockingView.makeDocumentConfig(doc)) };
+ const config = dragDocs.length === 1 ? DashboardView.makeDocumentConfig(dragDocs[0]) : { type: 'row', content: dragDocs.map(doc => DashboardView.makeDocumentConfig(doc)) };
const dragSource = CollectionDockingView.Instance?._goldenLayout.createDragSource(document.createElement('div'), config);
this.tabDragStart(dragSource, finishDrag);
dragSource._dragListener.onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 });
@@ -118,7 +113,7 @@ export class CollectionDockingView extends CollectionSubView() {
@undoBatch
public static CloseSplit(document: Opt<Doc>, panelName?: string): boolean {
if (CollectionDockingView.Instance) {
- const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => (panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document));
+ const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tabView => (panelName ? tabView.contentItem.config.props.panelName === panelName : tabView.DashDoc === document));
if (tab) {
const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
if (j !== -1) {
@@ -137,7 +132,7 @@ export class CollectionDockingView extends CollectionSubView() {
public static ReplaceTab(document: Doc, mods: OpenWhereMod, stack: any, panelName: string, addToSplit?: boolean, keyValue?: boolean): boolean {
const instance = CollectionDockingView.Instance;
if (!instance) return false;
- const newConfig = CollectionDockingView.makeDocumentConfig(document, panelName, undefined, keyValue);
+ const newConfig = DashboardView.makeDocumentConfig(document, panelName, undefined, keyValue);
if (!panelName && stack) {
const activeContentItemIndex = stack.contentItems.findIndex((item: any) => item.config === stack._activeContentItem.config);
const newContentItem = stack.layoutManager.createContentItem(newConfig, instance._goldenLayout);
@@ -145,7 +140,7 @@ export class CollectionDockingView extends CollectionSubView() {
stack.contentItems[activeContentItemIndex].remove();
return instance.layoutChanged();
}
- const tab = Array.from(instance.tabMap.keys()).find(tab => tab.contentItem.config.props.panelName === panelName);
+ const tab = Array.from(instance.tabMap.keys()).find(tabView => tabView.contentItem.config.props.panelName === panelName);
if (tab) {
const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
if (newConfig.props.documentId !== tab.header.parent.contentItems[j].config.props.documentId) {
@@ -170,7 +165,7 @@ export class CollectionDockingView extends CollectionSubView() {
public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) {
if (document?._type_collection === CollectionViewType.Docking && !keyValue) return DashboardView.openDashboard(document);
if (!CollectionDockingView.Instance) return false;
- const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document && !tab.contentItem.config.props.keyValue && !keyValue);
+ const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tabView => tabView.DashDoc === document && !tabView.contentItem.config.props.keyValue && !keyValue);
if (tab) {
tab.header.parent.setActiveContentItem(tab.contentItem);
return true;
@@ -178,7 +173,7 @@ export class CollectionDockingView extends CollectionSubView() {
const instance = CollectionDockingView.Instance;
const glayRoot = instance._goldenLayout.root;
if (!instance) return false;
- const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName, undefined, keyValue);
+ const docContentConfig = DashboardView.makeDocumentConfig(document, panelName, undefined, keyValue);
CollectionDockingView.Instance._flush = CollectionDockingView.Instance._flush ?? UndoManager.StartBatch('Add Split');
setTimeout(CollectionDockingView.Instance.endUndoBatch, 100);
@@ -201,6 +196,7 @@ export class CollectionDockingView extends CollectionSubView() {
} else if (instance._goldenLayout.root.contentItems[0].isRow) {
// if row
switch (pullSide) {
+ // eslint-disable-next-line default-case-last
default:
case OpenWhereMod.none:
case OpenWhereMod.right:
@@ -210,7 +206,7 @@ export class CollectionDockingView extends CollectionSubView() {
glayRoot.contentItems[0].addChild(newContentItem(), 0);
break;
case OpenWhereMod.top:
- case OpenWhereMod.bottom:
+ case OpenWhereMod.bottom: {
// if not going in a row layout, must add already existing content into column
const rowlayout = glayRoot.contentItems[0];
const newColumn = rowlayout.layoutManager.createContentItem({ type: 'column' }, instance._goldenLayout);
@@ -229,6 +225,7 @@ export class CollectionDockingView extends CollectionSubView() {
rowlayout.config.height = 50;
newItem.config.height = 50;
+ }
}
} else {
// if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column
@@ -241,7 +238,7 @@ export class CollectionDockingView extends CollectionSubView() {
break;
case 'left':
case 'right':
- default:
+ default: {
// if not going in a row layout, must add already existing content into column
const collayout = glayRoot.contentItems[0];
const newRow = collayout.layoutManager.createContentItem({ type: 'row' }, instance._goldenLayout);
@@ -260,6 +257,7 @@ export class CollectionDockingView extends CollectionSubView() {
collayout.config.width = 50;
newItem.config.width = 50;
+ }
}
}
instance._ignoreStateChange = JSON.stringify(instance._goldenLayout.toConfig());
@@ -278,10 +276,10 @@ export class CollectionDockingView extends CollectionSubView() {
}
setupGoldenLayout = async () => {
if (this._unmounting) return;
- //const config = StrCast(this.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.Document)));
+ // const config = StrCast(this.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.Document)));
const config = StrCast(this.Document.dockingConfig);
if (config) {
- const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+ const matches = config.match(/"documentId":"[a-z0-9-]+"/g);
const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? [];
await Promise.all(docids.map(id => DocServer.GetRefField(id)));
@@ -289,12 +287,13 @@ export class CollectionDockingView extends CollectionSubView() {
if (this._goldenLayout) {
if (config === JSON.stringify(this._goldenLayout.toConfig())) {
return;
- } else {
- try {
- this._goldenLayout.unbind('tabCreated', this.tabCreated);
- this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
- this._goldenLayout.unbind('stackCreated', this.stackCreated);
- } catch (e) {}
+ }
+ try {
+ this._goldenLayout.unbind('tabCreated', this.tabCreated);
+ this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
+ this._goldenLayout.unbind('stackCreated', this.stackCreated);
+ } catch (e) {
+ /* empty */
}
this.tabMap.clear();
this._goldenLayout.destroy();
@@ -303,7 +302,7 @@ export class CollectionDockingView extends CollectionSubView() {
glay.on('tabCreated', this.tabCreated);
glay.on('tabDestroyed', this.tabDestroyed);
glay.on('stackCreated', this.stackCreated);
- glay.registerComponent('DocumentFrameRenderer', TabDocView);
+ glay.registerComponent('DocumentFrameRenderer', CollectionDockingView.tabClass);
glay.container = this._containerRef.current;
glay.init();
glay.root.layoutManager.on('itemDropped', this.tabItemDropped);
@@ -323,7 +322,7 @@ export class CollectionDockingView extends CollectionSubView() {
*/
titleChanged = (target: any, value: any) => {
const title = Field.toString(value);
- if (title.startsWith('@') && !title.substring(1).match(/[\(\)\[\]@]/) && title.length > 1) {
+ if (title.startsWith('@') && !title.substring(1).match(/[()[\]@]/) && title.length > 1) {
const embedding = DocListCast(target.proto_embeddings).lastElement();
embedding && Doc.AddToMyPublished(embedding);
} else if (!title.startsWith('@')) {
@@ -332,12 +331,13 @@ export class CollectionDockingView extends CollectionSubView() {
};
componentDidMount: () => void = async () => {
+ this._props.setContentViewBox?.(this);
this._unmounting = false;
SetPropSetterCb('title', this.titleChanged); // this overrides any previously assigned callback for the property
if (this._containerRef.current) {
this._lightboxReactionDisposer = reaction(
() => LightboxView.LightboxDoc,
- doc => setTimeout(() => !doc && this.onResize(undefined))
+ doc => setTimeout(() => !doc && this.onResize())
);
new _global.ResizeObserver(this.onResize).observe(this._containerRef.current);
this._reactionDisposer = reaction(
@@ -356,25 +356,26 @@ export class CollectionDockingView extends CollectionSubView() {
{ fireImmediately: true }
);
reaction(
- () => [SettingsManager.userBackgroundColor, SettingsManager.userBackgroundColor],
+ () => [SnappingManager.userBackgroundColor, SnappingManager.userBackgroundColor],
() => {
clearStyleSheetRules(CollectionDockingView._highlightStyleSheet);
- addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { background: `${SettingsManager.userBackgroundColor} !important` });
- addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { color: `${SettingsManager.userColor} !important` });
- addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${SettingsManager.userBackgroundColor} !important` });
+ addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { background: `${SnappingManager.userBackgroundColor} !important` });
+ addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { color: `${SnappingManager.userColor} !important` });
+ addStyleSheetRule(SnappingManager.SettingsStyle, 'lm_header', { background: `${SnappingManager.userBackgroundColor} !important` });
},
{ fireImmediately: true }
);
}
};
- _unmounting = false;
componentWillUnmount: () => void = () => {
this._unmounting = true;
try {
this._goldenLayout.unbind('stackCreated', this.stackCreated);
this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
- } catch (e) {}
+ } catch (e) {
+ /* empty */
+ }
this._goldenLayout?.destroy();
window.removeEventListener('resize', this.onResize);
window.removeEventListener('mouseup', this.onPointerUp);
@@ -383,8 +384,11 @@ export class CollectionDockingView extends CollectionSubView() {
this._lightboxReactionDisposer?.();
};
+ // ViewBoxInterface overrides
+ override isUnstyledView = returnTrue;
+
@action
- onResize = (event: any) => {
+ onResize = () => {
const cur = this._containerRef.current;
// bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
!LightboxView.LightboxDoc && cur && this._goldenLayout?.updateSize(cur.getBoundingClientRect().width, cur.getBoundingClientRect().height);
@@ -392,7 +396,7 @@ export class CollectionDockingView extends CollectionSubView() {
endUndoBatch = () => {
const json = JSON.stringify(this._goldenLayout.toConfig());
- const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+ const matches = json.match(/"documentId":"[a-z0-9-]+"/g);
const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', ''));
const docs = !docids
? []
@@ -415,7 +419,7 @@ export class CollectionDockingView extends CollectionSubView() {
};
@action
- onPointerUp = (e: MouseEvent): void => {
+ onPointerUp = (): void => {
window.removeEventListener('pointerup', this.onPointerUp);
DragManager.CompleteWindowDrag = undefined;
setTimeout(this.endUndoBatch, 100);
@@ -437,8 +441,8 @@ export class CollectionDockingView extends CollectionSubView() {
} else {
const tabTarget = (e.target as HTMLElement)?.parentElement?.className.includes('lm_tab') ? (e.target as HTMLElement).parentElement : (e.target as HTMLElement);
const map = Array.from(this.tabMap).find(tab => tab.element[0] === tabTarget);
- if (map?.DashDoc && DocumentManager.Instance.getFirstDocumentView(map.DashDoc)) {
- SelectionManager.SelectView(DocumentManager.Instance.getFirstDocumentView(map.DashDoc), false);
+ if (map?.DashDoc && DocumentView.getDocumentView(map.DashDoc, this.DocumentView?.())) {
+ DocumentView.SelectView(DocumentView.getDocumentView(map.DashDoc, this.DocumentView?.()), false);
}
}
}
@@ -453,24 +457,27 @@ export class CollectionDockingView extends CollectionSubView() {
if (content) {
const _width = DivWidth(content);
const _height = DivHeight(content);
- return CollectionFreeFormView.UpdateIcon(this.layoutDoc[Id] + '-icon' + new Date().getTime(), content, _width, _height, _width, _height, 0, 1, true, this.layoutDoc[Id] + '-icon', (iconFile, _nativeWidth, _nativeHeight) => {
+ return UpdateIcon(this.layoutDoc[Id] + '-icon' + new Date().getTime(), content, _width, _height, _width, _height, 0, 1, true, this.layoutDoc[Id] + '-icon', iconFile => {
const proto = this.dataDoc; // Cast(img.proto, Doc, null)!;
- proto['thumb_nativeWidth'] = _width;
- proto['thumb_nativeHeight'] = _height;
+ proto.thumb_nativeWidth = _width;
+ proto.thumb_nativeHeight = _height;
proto.thumb = new ImageField(iconFile);
});
}
+ return undefined;
}
public static async TakeSnapshot(doc: Doc | undefined, clone = false) {
if (!doc) return undefined;
let json = StrCast(doc.dockingConfig);
if (clone) {
const cloned = await Doc.MakeClone(doc);
- Array.from(cloned.map.entries()).map(entry => (json = json.replace(entry[0], entry[1][Id])));
+ Array.from(cloned.map.entries()).forEach(entry => {
+ json = json.replace(entry[0], entry[1][Id]);
+ });
cloned.clone[DocData].dockingConfig = json;
return DashboardView.openDashboard(cloned.clone);
}
- const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+ const matches = json.match(/"documentId":"[a-z0-9-]+"/g);
const origtabids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) || [];
const origtabs = origtabids
.map(id => DocServer.GetCachedRefField(id))
@@ -508,19 +515,20 @@ export class CollectionDockingView extends CollectionSubView() {
tabDestroyed = (tab: any) => {
this._flush = this._flush ?? UndoManager.StartBatch('tab movement');
- if (tab.DashDoc && ![DocumentType.PRES].includes(tab.DashDoc?.type) && !tab.contentItem.config.props.keyValue) {
- Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc, undefined, undefined, true);
+ const dashDoc = tab.DashDoc;
+ if (dashDoc && ![DocumentType.PRES].includes(dashDoc.type) && !tab.contentItem.config.props.keyValue) {
+ Doc.AddDocToList(Doc.MyHeaderBar, 'data', dashDoc, undefined, undefined, true);
// if you close a tab that is not embedded somewhere else (an embedded Doc can be opened simultaneously in a tab), then add the tab to recently closed
- if (tab.DashDoc.embedContainer === this.Document) tab.DashDoc.embedContainer = undefined;
- if (!tab.DashDoc.embedContainer) {
- Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
- Doc.RemoveEmbedding(tab.DashDoc, tab.DashDoc);
+ if (dashDoc.embedContainer === this.Document) dashDoc.embedContainer = undefined;
+ if (!dashDoc.embedContainer) {
+ Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashDoc, undefined, true, true);
+ Doc.RemoveEmbedding(dashDoc, dashDoc);
}
}
if (CollectionDockingView.Instance) {
const dview = CollectionDockingView.Instance.Document;
- const fieldKey = CollectionDockingView.Instance.props.fieldKey;
- Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc);
+ const { fieldKey } = CollectionDockingView.Instance.props;
+ Doc.RemoveDocFromList(dview, fieldKey, dashDoc);
this.tabMap.delete(tab);
tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
this.stateChanged();
@@ -531,8 +539,8 @@ export class CollectionDockingView extends CollectionSubView() {
tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content)
};
- stackCreated = (stack: any) => {
- stack = stack.header ? stack : stack.origin;
+ stackCreated = (stackIn: any) => {
+ const stack = stackIn.header ? stackIn : stackIn.origin;
stack.header?.element.on('mousedown', (e: any) => {
const dashboard = Doc.ActiveDashboard;
if (dashboard && e.target === stack.header?.element[0] && e.button === 2) {
@@ -550,32 +558,29 @@ export class CollectionDockingView extends CollectionSubView() {
}
});
- let addNewDoc = undoable(
- action(() => {
- const dashboard = Doc.ActiveDashboard;
- if (dashboard) {
- dashboard.pane_count = NumCast(dashboard.pane_count) + 1;
- const docToAdd = Docs.Create.FreeformDocument([], {
- _width: this._props.PanelWidth(),
- _height: this._props.PanelHeight(),
- _layout_fitWidth: true,
- _freeform_backgroundGrid: true,
- title: `Untitled Tab ${NumCast(dashboard.pane_count)}`,
- });
- Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
- inheritParentAcls(this.dataDoc, docToAdd, false);
- CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
- }
- }),
- 'add new tab'
- );
+ const addNewDoc = undoable(() => {
+ const dashboard = Doc.ActiveDashboard;
+ if (dashboard) {
+ dashboard.pane_count = NumCast(dashboard.pane_count) + 1;
+ const docToAdd = Docs.Create.FreeformDocument([], {
+ _width: this._props.PanelWidth(),
+ _height: this._props.PanelHeight(),
+ _layout_fitWidth: true,
+ _freeform_backgroundGrid: true,
+ title: `Untitled Tab ${NumCast(dashboard.pane_count)}`,
+ });
+ Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
+ inheritParentAcls(this.dataDoc, docToAdd, false);
+ CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
+ }
+ }, 'add new tab');
stack.header?.controlsContainer
- .find('.lm_close') //get the close icon
- .off('click') //unbind the current click handler
+ .find('.lm_close') // get the close icon
+ .off('click') // unbind the current click handler
.click(
action(() => {
- //if (confirm('really close this?')) {
+ // if (confirm('really close this?')) {
if ((!stack.parent.isRoot && !stack.parent.parent.isRoot) || stack.parent.contentItems.length > 1) {
const batch = UndoManager.StartBatch('close stack');
stack.remove();
@@ -595,11 +600,11 @@ export class CollectionDockingView extends CollectionSubView() {
}
});
stack.header?.controlsContainer
- .find('.lm_maximise') //get the close icon
+ .find('.lm_maximise') // get the close icon
.click(() => setTimeout(this.stateChanged));
stack.header?.controlsContainer
- .find('.lm_popout') //get the popout icon
- .off('click') //unbind the current click handler
+ .find('.lm_popout') // get the popout icon
+ .off('click') // unbind the current click handler
.click(addNewDoc);
};
@@ -609,13 +614,14 @@ export class CollectionDockingView extends CollectionSubView() {
<div>
{href ? (
<img
+ alt="thumbnail of nested dashboard"
style={{ background: 'white', top: 0, position: 'absolute' }}
src={href} // + '?d=' + (new Date()).getTime()}
width={this._props.PanelWidth()}
height={this._props.PanelHeight()}
/>
) : (
- <p>nested dashboards has no thumbnail</p>
+ <p>nested dashboard has no thumbnail</p>
)}
</div>
) : (
@@ -625,6 +631,7 @@ export class CollectionDockingView extends CollectionSubView() {
}
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function openInLightbox(doc: any) {
LightboxView.Instance.AddDocTab(doc, OpenWhere.lightbox);
},
@@ -632,26 +639,37 @@ ScriptingGlobals.add(
'(doc: any)'
);
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function openDoc(doc: any, where: OpenWhere) {
switch (where) {
case OpenWhere.addRight:
return CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
case OpenWhere.overlay:
+ default:
// prettier-ignore
switch (doc) {
case '<ScriptingRepl />': return OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' });
case "<UndoStack />": return OverlayView.Instance.addWindow(<UndoStack />, { x: 300, y: 100, width: 200, height: 200, title: 'Undo stack' });
+ default:
}
Doc.AddToMyOverlay(doc);
+ return true;
}
},
'opens up document in location specified',
'(doc: any)'
);
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function openRepl() {
return 'openRepl';
},
'opens up document in screen overlay layer',
'(doc: any)'
);
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(async function snapshotDashboard() {
+ const batch = UndoManager.StartBatch('snapshot');
+ await CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard);
+ batch.end();
+}, 'creates a snapshot copy of a dashboard');
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 7dcfd32bd..9a6f1e2eb 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -1,9 +1,13 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, numberRange, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../Utils';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction, numberRange } from '../../../Utils';
+import { Doc } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
@@ -51,13 +55,19 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
@observable private collapsed: boolean = false;
@observable private _paletteOn = false;
private set _heading(value: string) {
- runInAction(() => this._props.headingObject && (this._props.headingObject.heading = this.heading = value));
+ runInAction(() => {
+ this._props.headingObject && (this._props.headingObject.heading = this.heading = value);
+ });
}
private set _color(value: string) {
- runInAction(() => this._props.headingObject && (this._props.headingObject.color = this.color = value));
+ runInAction(() => {
+ this._props.headingObject && (this._props.headingObject.color = this.color = value);
+ });
}
private set _collapsed(value: boolean) {
- runInAction(() => this._props.headingObject && (this._props.headingObject.collapsed = this.collapsed = value));
+ runInAction(() => {
+ this._props.headingObject && (this._props.headingObject.collapsed = this.collapsed = value);
+ });
}
private _dropDisposer?: DragManager.DragDropDisposer;
@@ -87,7 +97,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
if (this.collapsed) {
this._props.setDocHeight(this.heading, 20);
} else {
- const rawHeight = this._contRef.current!.getBoundingClientRect().height + 15; //+ 15 accounts for the group header
+ const rawHeight = this._contRef.current!.getBoundingClientRect().height + 15; // +15 accounts for the group header
const transformScale = this._props.screenToLocalTransform().Scale;
const trueHeight = rawHeight * transformScale;
this._props.setDocHeight(this.heading, trueHeight);
@@ -101,7 +111,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
const key = this._props.pivotField;
const castedValue = this.getValue(this.heading);
if (this._props.parent.onInternalDrop(e, de)) {
- key && de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !this.onLayoutDoc(key)));
+ key && de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, true));
}
return true;
}
@@ -117,7 +127,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
};
@action
- headingChanged = (value: string, shiftDown?: boolean) => {
+ headingChanged = (value: string /* , shiftDown?: boolean */) => {
this._createEmbeddingSelected = false;
const key = this._props.pivotField;
const castedValue = this.getValue(value);
@@ -140,7 +150,9 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
this._color = color;
};
- pointerEnteredRow = action(() => SnappingManager.IsDragging && (this._background = '#b4b4b4'));
+ pointerEnteredRow = action(() => {
+ SnappingManager.IsDragging && (this._background = '#b4b4b4');
+ });
@action
pointerLeaveRow = () => {
@@ -152,13 +164,13 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
addDocument = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
if (!value && !forceEmptyNote) return false;
this._createEmbeddingSelected = false;
- const key = this._props.pivotField;
+ const { pivotField } = this._props;
const newDoc = Docs.Create.TextDocument('', { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value });
- FormattedTextBox.SetSelectOnLoad(newDoc);
+ Doc.SetSelectOnLoad(newDoc);
FormattedTextBox.SelectOnLoadChar = value;
- key && ((this.onLayoutDoc(key) ? newDoc : newDoc[DocData])[key] = this.getValue(this._props.heading));
+ pivotField && (newDoc[DocData][pivotField] = this.getValue(this._props.heading));
const docs = this._props.parent.childDocList;
- return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list)
+ return docs ? !!docs.splice(0, 0, newDoc) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list)
};
deleteRow = undoBatch(
@@ -197,21 +209,11 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
@action
headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
if (e.button === 0 && !e.ctrlKey) {
- setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => !this._props.chromeHidden && this.collapseSection(e));
+ setupMoveUpEvents(this, e, this.headerMove, emptyFunction, clickEv => !this._props.chromeHidden && this.collapseSection(clickEv));
this._createEmbeddingSelected = false;
}
};
- /**
- * Returns true if a key is on the layout doc of the documents in the collection.
- */
- onLayoutDoc = (key: string): boolean => {
- DocListCast(this._props.parent.Document.data).forEach(doc => {
- if (Doc.Get(doc, key, true)) return true;
- });
- return false;
- };
-
renderColorPicker = () => {
const selected = this.color;
@@ -228,27 +230,29 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
return (
<div className="collectionStackingView-colorPicker">
<div className="colorOptions">
- <div className={'colorPicker' + (selected === pink ? ' active' : '')} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!)}></div>
- <div className={'colorPicker' + (selected === purple ? ' active' : '')} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!)}></div>
- <div className={'colorPicker' + (selected === blue ? ' active' : '')} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!)}></div>
- <div className={'colorPicker' + (selected === yellow ? ' active' : '')} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!)}></div>
- <div className={'colorPicker' + (selected === red ? ' active' : '')} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!)}></div>
- <div className={'colorPicker' + (selected === gray ? ' active' : '')} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray)}></div>
- <div className={'colorPicker' + (selected === green ? ' active' : '')} style={{ backgroundColor: green }} onClick={() => this.changeColumnColor(green!)}></div>
- <div className={'colorPicker' + (selected === cyan ? ' active' : '')} style={{ backgroundColor: cyan }} onClick={() => this.changeColumnColor(cyan!)}></div>
- <div className={'colorPicker' + (selected === orange ? ' active' : '')} style={{ backgroundColor: orange }} onClick={() => this.changeColumnColor(orange!)}></div>
+ <div className={'colorPicker' + (selected === pink ? ' active' : '')} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!)} />
+ <div className={'colorPicker' + (selected === purple ? ' active' : '')} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!)} />
+ <div className={'colorPicker' + (selected === blue ? ' active' : '')} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!)} />
+ <div className={'colorPicker' + (selected === yellow ? ' active' : '')} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!)} />
+ <div className={'colorPicker' + (selected === red ? ' active' : '')} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!)} />
+ <div className={'colorPicker' + (selected === gray ? ' active' : '')} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray)} />
+ <div className={'colorPicker' + (selected === green ? ' active' : '')} style={{ backgroundColor: green }} onClick={() => this.changeColumnColor(green!)} />
+ <div className={'colorPicker' + (selected === cyan ? ' active' : '')} style={{ backgroundColor: cyan }} onClick={() => this.changeColumnColor(cyan!)} />
+ <div className={'colorPicker' + (selected === orange ? ' active' : '')} style={{ backgroundColor: orange }} onClick={() => this.changeColumnColor(orange!)} />
</div>
</div>
);
};
- toggleEmbedding = action(() => (this._createEmbeddingSelected = true));
- toggleVisibility = () => (this._collapsed = !this.collapsed);
+ toggleEmbedding = action(() => {
+ this._createEmbeddingSelected = true;
+ });
+ toggleVisibility = () => {
+ this._collapsed = !this.collapsed;
+ };
@action
- textCallback = (char: string) => {
- return this.addDocument('', false);
- };
+ textCallback = (/* char: string */) => this.addDocument('', false);
@computed get contentLayout() {
const rows = Math.max(1, Math.min(this._props.docList.length, Math.floor((this._props.parent._props.PanelWidth() - 2 * this._props.parent.xMargin) / (this._props.parent.columnWidth + this._props.parent.gridGap))));
@@ -262,22 +266,22 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
className="collectionStackingView-addDocumentButton"
style={
{
- //width: style.columnWidth / style.numGroupColumns,
- //padding: `${NumCast(this._props.parent.layoutDoc._yPadding, this._props.parent.yMargin)}px 0px 0px 0px`,
+ // width: style.columnWidth / style.numGroupColumns,
+ // padding: `${NumCast(this._props.parent.layoutDoc._yPadding, this._props.parent.yMargin)}px 0px 0px 0px`,
}
}>
- <EditableView GetValue={returnEmptyString} SetValue={this.addDocument} textCallback={this.textCallback} contents={'+ NEW'} />
+ <EditableView GetValue={returnEmptyString} SetValue={this.addDocument} textCallback={this.textCallback} contents="+ NEW" />
</div>
) : null}
<div
- className={`collectionStackingView-masonryGrid`}
+ className="collectionStackingView-masonryGrid"
ref={this._contRef}
style={{
padding: stackPad,
minHeight: this._props.showHandle && this._props.parent._props.isContentActive() ? '10px' : undefined,
width: this._props.parent.NodeWidth,
gridGap: this._props.parent.gridGap,
- gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this._props.parent.columnWidth}px`, ''),
+ gridTemplateColumns: numberRange(rows).reduce(list => list + ` ${this._props.parent.columnWidth}px`, ''),
}}>
{this._props.parent.children(this._props.docList)}
</div>
@@ -289,7 +293,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
const noChrome = this._props.chromeHidden;
const key = this._props.pivotField;
const evContents = this.heading ? this.heading : this._props.type && this._props.type === 'number' ? '0' : `NO ${key.toUpperCase()} VALUE`;
- const editableHeaderView = <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} />;
+ const editableHeaderView = <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine />;
return this._props.Document.miniHeaders ? (
<div className="collectionStackingView-miniHeader">{editableHeaderView}</div>
) : !this._props.headingObject ? null : (
@@ -303,6 +307,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
{noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? null : (
<div className="collectionStackingView-sectionColor">
<button
+ type="button"
className="collectionStackingView-sectionColorButton"
onPointerDown={e =>
setupMoveUpEvents(
@@ -310,7 +315,9 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
e,
returnFalse,
emptyFunction,
- action(e => (this._paletteOn = !this._paletteOn))
+ action(() => {
+ this._paletteOn = !this._paletteOn;
+ })
)
}>
<FontAwesomeIcon icon="palette" size="lg" />
@@ -319,13 +326,13 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
</div>
)}
{noChrome ? null : (
- <button className="collectionStackingView-sectionDelete" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, noChrome ? emptyFunction : this.collapseSection)}>
+ <button type="button" className="collectionStackingView-sectionDelete" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, noChrome ? emptyFunction : this.collapseSection)}>
<FontAwesomeIcon icon={this.collapsed ? 'chevron-down' : 'chevron-up'} size="lg" />
</button>
)}
{noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? null : (
<div className="collectionStackingView-sectionOptions" onPointerDown={e => e.stopPropagation()}>
- <button className="collectionStackingView-sectionOptionButton" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.deleteRow)}>
+ <button type="button" className="collectionStackingView-sectionOptionButton" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.deleteRow)}>
<FontAwesomeIcon icon="trash" size="lg" />
</button>
</div>
diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss
index 5d46649b2..3ec875df4 100644
--- a/src/client/views/collections/CollectionMenu.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -10,7 +10,7 @@
border-bottom: $standard-border;
padding: 0 10px;
align-items: center;
- overflow-x: scroll;
+ overflow-x: auto;
&::-webkit-scrollbar {
display: none;
}
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 4f25f69ef..e53071584 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,10 +1,17 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/control-has-associated-label */
+/* eslint-disable react/no-unused-class-component-methods */
+/* eslint-disable react/sort-comp */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { Toggle, ToggleType, Type } from 'browndash-components';
import { Lambda, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
+import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { List } from '../../../fields/List';
@@ -12,14 +19,13 @@ import { ObjectField } from '../../../fields/ObjectField';
import { RichTextField } from '../../../fields/RichTextField';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DragManager, dropActionType } from '../../util/DragManager';
-import { SelectionManager } from '../../util/SelectionManager';
-import { SettingsManager } from '../../util/SettingsManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
+import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
import { AntimodeMenu } from '../AntimodeMenu';
import { EditableView } from '../EditableView';
-import { MainView } from '../MainView';
import { DefaultStyleProvider } from '../StyleProvider';
import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView';
import './CollectionMenu.scss';
@@ -30,10 +36,12 @@ interface CollectionMenuProps {
panelWidth: () => number;
toggleTopBar: () => void;
topBarHeight: () => number;
+ togglePropertiesFlyout: () => void;
}
@observer
export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
+ // eslint-disable-next-line no-use-before-define
@observable static Instance: CollectionMenu;
@observable SelectedCollection: DocumentView | undefined = undefined;
@@ -44,13 +52,13 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
makeObservable(this);
CollectionMenu.Instance = this;
this._canFade = false; // don't let the inking menu fade away
- 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.lastElement(),
+ () => DocumentView.Selected().lastElement(),
view => view && this.SetSelection(view)
);
}
@@ -61,25 +69,16 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
}
@action
- toggleMenuPin = (e: React.MouseEvent) => {
- Doc.UserDoc()['menuCollections-pinned'] = this.Pinned = !this.Pinned;
+ toggleMenuPin = () => {
+ Doc.UserDoc().menuCollections_pinned = this.Pinned = !this.Pinned;
if (!this.Pinned && this._left < 0) {
this.jumpTo(300, 300);
}
};
- @action
- toggleProperties = () => {
- if (MainView.Instance.propertiesWidth() > 0) {
- SettingsManager.Instance.propertiesWidth = 0;
- } else {
- SettingsManager.Instance.propertiesWidth = 300;
- }
- };
-
buttonBarXf = () => {
if (!this._docBtnRef.current) return Transform.Identity();
- const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current);
+ const { scale, translateX, translateY } = ClientUtils.GetScreenTransform(this._docBtnRef.current);
return new Transform(-translateX, -translateY, 1 / scale);
};
@@ -119,15 +118,15 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
render() {
const headerIcon = this.props.topBarHeight() > 0 ? 'angle-double-up' : 'angle-double-down';
const headerTitle = this.props.topBarHeight() > 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 propIcon = SnappingManager.PropertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
+ const propTitle = SnappingManager.PropertiesWidth > 0 ? 'Close Properties' : 'Open Properties';
const hardCodedButtons = (
- <div className={`hardCodedButtons`}>
+ <div className="hardCodedButtons">
<Toggle
toggleType={ToggleType.BUTTON}
type={Type.PRIM}
- color={SettingsManager.userColor}
+ color={SnappingManager.userColor}
onClick={this.props.toggleTopBar}
toggleStatus={this.props.topBarHeight() > 0}
icon={<FontAwesomeIcon icon={headerIcon} size="lg" />}
@@ -136,21 +135,21 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
<Toggle
toggleType={ToggleType.BUTTON}
type={Type.PRIM}
- color={SettingsManager.userColor}
- onClick={this.toggleProperties}
- toggleStatus={SettingsManager.Instance.propertiesWidth > 0}
+ color={SnappingManager.userColor}
+ onClick={this._props.togglePropertiesFlyout}
+ toggleStatus={SnappingManager.PropertiesWidth > 0}
icon={<FontAwesomeIcon icon={propIcon} size="lg" />}
tooltip={propTitle}
/>
</div>
);
- //dash col linear view buttons
+ // dash col linear view buttons
const contMenuButtons = (
<div
className="collectionMenu-container"
style={{
- background: SettingsManager.userBackgroundColor,
+ background: SnappingManager.userBackgroundColor,
// borderColor: SettingsManager.userColor
}}>
{this.contMenuButtons}
@@ -163,7 +162,9 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
}
interface CollectionViewMenuProps {
+ // eslint-disable-next-line react/no-unused-prop-types
type: CollectionViewType;
+ // eslint-disable-next-line react/no-unused-prop-types
fieldKey: string;
docView: DocumentView;
}
@@ -172,7 +173,7 @@ const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
@observer
export class CollectionViewBaseChrome extends React.Component<CollectionViewMenuProps> {
- //(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
+ // (!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
get document() {
return this.props.docView?.Document;
@@ -206,14 +207,18 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
params: ['target', 'source'],
title: 'child click view',
script: 'this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])',
- immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))),
+ immediate: undoBatch((source: Doc[]) => {
+ source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]));
+ }),
initialize: emptyFunction,
};
_contentCommand = {
params: ['target', 'source'],
title: 'set content',
script: 'getProto(this.target).data = copyField(this.source);',
- immediate: undoBatch((source: Doc[]) => (this.target[DocData].data = new List<Doc>(source))),
+ immediate: undoBatch((source: Doc[]) => {
+ this.target[DocData].data = new List<Doc>(source);
+ }),
initialize: emptyFunction,
};
_onClickCommand = {
@@ -224,14 +229,14 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
getProto(this.proxy[0]).target = this.target.target;
getProto(this.proxy[0]).source = copyField(this.target.source);
}}`,
- immediate: undoBatch((source: Doc[]) => {}),
+ immediate: undoBatch(() => {}),
initialize: emptyFunction,
};
_viewCommand = {
params: ['target'],
title: 'bookmark view',
- script: "this.target._freeform_panX = self['target-freeform_panX']; this.target._freeform_panY = this['target-freeform_panY']; this.target._freeform_scale = this['target_freeform_scale']; gotoFrame(this.target, this['target-currentFrame']);",
- immediate: undoBatch((source: Doc[]) => {
+ script: "this.target._freeform_panX = this.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(() => {
this.target._freeform_panX = 0;
this.target._freeform_panY = 0;
this.target._freeform_scale = 1;
@@ -248,30 +253,34 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
params: ['target'],
title: 'fit content',
script: 'this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox;',
- immediate: undoBatch((source: Doc[]) => (this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox)),
+ immediate: undoBatch(() => {
+ this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox;
+ }),
initialize: emptyFunction,
};
_fitContentCommand = {
params: ['target'],
title: 'toggle clusters',
script: 'this.target._freeform_useClusters = !this.target._freeform_useClusters;',
- immediate: undoBatch((source: Doc[]) => (this.target._freeform_useClusters = !this.target._freeform_useClusters)),
+ immediate: undoBatch(() => {
+ this.target._freeform_useClusters = !this.target._freeform_useClusters;
+ }),
initialize: emptyFunction,
};
_saveFilterCommand = {
params: ['target'],
title: 'save filter',
- script: `this.target._childFilters = compareLists(this['target-childFilters'],this.target._childFilters) ? undefined : copyField(this['target-childFilters']);
- this.target._searchFilterDocs = compareLists(this['target-searchFilterDocs'],this.target._searchFilterDocs) ? undefined: copyField(this['target-searchFilterDocs']);`,
- immediate: undoBatch((source: Doc[]) => {
+ 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(() => {
this.target._childFilters = undefined;
this.target._searchFilterDocs = undefined;
}),
initialize: (button: Doc) => {
const activeDash = Doc.ActiveDashboard;
if (activeDash) {
- button['target-childFilters'] = (Doc.MySearcher._childFilters || activeDash._childFilters) instanceof ObjectField ? ObjectField.MakeCopy((Doc.MySearcher._childFilters || activeDash._childFilters) as any as ObjectField) : undefined;
- button['target-searchFilterDocs'] = activeDash._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(activeDash._searchFilterDocs as any as ObjectField) : undefined;
+ button.target_childFilters = (Doc.MySearcher._childFilters || activeDash._childFilters) instanceof ObjectField ? ObjectField.MakeCopy((Doc.MySearcher._childFilters || activeDash._childFilters) as any as ObjectField) : undefined;
+ button.target_searchFilterDocs = activeDash._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(activeDash._searchFilterDocs as any as ObjectField) : undefined;
}
},
};
@@ -299,8 +308,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
}
private get _buttonizableCommands() {
switch (this.props.type) {
- default:
- return this._doc_commands;
case CollectionViewType.Freeform:
return this._freeform_commands;
case CollectionViewType.Tree:
@@ -319,6 +326,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
return this._freeform_commands;
case CollectionViewType.Carousel3D:
return this._freeform_commands;
+ default:
+ return this._doc_commands;
}
}
private _commandRef = React.createRef<HTMLInputElement>();
@@ -332,13 +341,13 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
@undoBatch
viewChanged = (e: React.ChangeEvent) => {
const target = this.document !== Doc.MyLeftSidebarPanel ? this.document : DocCast(this.document.proto);
- //@ts-ignore
- target._type_collection = e.target.selectedOptions[0].value;
+ target._type_collection = (e.target as any).selectedOptions[0].value;
};
commandChanged = (e: React.ChangeEvent) => {
- //@ts-ignore
- runInAction(() => (this._currentKey = e.target.selectedOptions[0].value));
+ runInAction(() => {
+ this._currentKey = (e.target as any).selectedOptions[0].value;
+ });
};
@action closeViewSpecs = () => {
@@ -356,7 +365,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
@undoBatch
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
- const docDragData = de.complete.docDragData;
+ const { docDragData } = de.complete;
if (docDragData?.draggedDocuments.length) {
this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate(docDragData.draggedDocuments || []));
e.stopPropagation();
@@ -369,16 +378,18 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
setupMoveUpEvents(
this,
e,
- (e, down, delta) => {
+ moveEv => {
const vtype = this.props.type;
const c = {
params: ['target'],
title: vtype,
script: `this.target._type_collection = '${StrCast(this.props.type)}'`,
- immediate: (source: Doc[]) => (this.document._type_collection = Doc.getDocTemplate(source?.[0])),
+ immediate: (source: Doc[]) => {
+ this.document._type_collection = Doc.getDocTemplate(source?.[0]);
+ },
initialize: emptyFunction,
};
- DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title), { target: this.document }, c.params, c.initialize, e.clientX, e.clientY);
+ DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title), { target: this.document }, c.params, c.initialize, moveEv.clientX, moveEv.clientY);
return true;
},
emptyFunction,
@@ -389,8 +400,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
setupMoveUpEvents(
this,
e,
- (e, down, delta) => {
- this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title, { target: this.document }, c.params, c.initialize, e.clientX, e.clientY));
+ moveEv => {
+ this._buttonizableCommands
+ ?.filter(c => c.title === this._currentKey)
+ .map(c => DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title, { target: this.document }, c.params, c.initialize, moveEv.clientX, moveEv.clientY));
return true;
},
emptyFunction,
@@ -405,11 +418,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
<div className="collectionViewBaseChrome-template" ref={this.createDropTarget}>
<Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom">
<div className="commandEntry-outerDiv" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
- <button className={'antimodeMenu-button'}>
+ <button type="button" className="antimodeMenu-button">
<FontAwesomeIcon icon="bullseye" size="lg" />
</button>
<select className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={'empty'} value={''} />
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key="empty" value="" />
{this._buttonizableCommands?.map(cmd => (
<option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>
{cmd.title}
@@ -456,23 +469,19 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
if (docs instanceof Doc) {
const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_'));
return keys.filter(key => key.toLowerCase().indexOf(val) > -1);
- } else {
- const keys = new Set<string>();
- docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
- const noviceKeys = Array.from(keys).filter(
- key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_')
- );
- return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
}
+ const keys = new Set<string>();
+ docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
+ const noviceKeys = Array.from(keys).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_'));
+ return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
}
if (docs instanceof Doc) {
return Object.keys(docs).filter(key => key.toLowerCase().indexOf(val) > -1);
- } else {
- const keys = new Set<string>();
- docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
- return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1);
}
+ const keys = new Set<string>();
+ docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
+ return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1);
};
@action
@@ -482,9 +491,7 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
getSuggestionValue = (suggestion: string) => suggestion;
- renderSuggestion = (suggestion: string) => {
- return <p>{suggestion}</p>;
- };
+ renderSuggestion = (suggestion: string) => <p>{suggestion}</p>;
onSuggestionFetch = async ({ value }: { value: string }) => {
const sugg = await this.getKeySuggestions(value);
@@ -572,12 +579,16 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
}
componentDidMount() {
- runInAction(() => (this.resize = this.props.docView.props.PanelWidth() < 700));
+ runInAction(() => {
+ this.resize = this.props.docView.props.PanelWidth() < 700;
+ });
// listener to reduce text on chrome resize (panel resize)
this.resizeListenerDisposer = reaction(
() => this.panelWidth,
- newValue => (this.resize = newValue < 700)
+ newValue => {
+ this.resize = newValue < 700;
+ }
);
}
@@ -593,7 +604,10 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
* Sets the value of `numCols` on the grid's Document to the value entered.
*/
onNumColsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- if (e.currentTarget.valueAsNumber > 0) undoBatch(() => (this.document.gridNumCols = e.currentTarget.valueAsNumber))();
+ if (e.currentTarget.valueAsNumber > 0)
+ undoBatch(() => {
+ this.document.gridNumCols = e.currentTarget.valueAsNumber;
+ })();
};
/**
@@ -622,7 +636,9 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
onIncrementButtonClick = () => {
this.clicked = true;
this.entered && (this.document.gridNumCols as number)--;
- undoBatch(() => (this.document.gridNumCols = this.numCols + 1))();
+ undoBatch(() => {
+ this.document.gridNumCols = this.numCols + 1;
+ })();
this.entered = false;
};
@@ -633,7 +649,9 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
this.clicked = true;
if (this.numCols > 1 && !this.decrementLimitReached) {
this.entered && (this.document.gridNumCols as number)++;
- undoBatch(() => (this.document.gridNumCols = this.numCols - 1))();
+ undoBatch(() => {
+ this.document.gridNumCols = this.numCols - 1;
+ })();
if (this.numCols === 1) this.decrementLimitReached = true;
}
this.entered = false;
@@ -726,7 +744,13 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
<label className="flexLabel">{this.resize ? 'Flex' : 'Flexible'}</label>
</span>
- <button onClick={() => (this.document.gridResetLayout = true)}>{!this.resize ? 'Reset' : <FontAwesomeIcon icon="redo-alt" size="1x" />}</button>
+ <button
+ type="button"
+ onClick={() => {
+ this.document.gridResetLayout = true;
+ }}>
+ {!this.resize ? 'Reset' : <FontAwesomeIcon icon="redo-alt" size="1x" />}
+ </button>
</div>
);
}
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index d8a0aebb1..3f9eed1d6 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -1,7 +1,8 @@
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, Field, Opt } from '../../../fields/Doc';
+import { ClientUtils, DivHeight, lightOrDark, returnZero, smoothScroll } from '../../../ClientUtils';
+import { Doc, Field, FieldType, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Copy, Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
@@ -9,25 +10,29 @@ import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { DivHeight, emptyFunction, lightOrDark, returnZero, smoothScroll, Utils } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { emptyFunction } from '../../../Utils';
+import { Docs } from '../../documents/Documents';
+import { DocUtils } from '../../documents/DocUtils';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoable, undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
+import { FieldsDropdown } from '../FieldsDropdown';
import { Colors } from '../global/globalEnums';
import { LightboxView } from '../LightboxView';
+import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
import { DocumentView } from '../nodes/DocumentView';
-import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView';
-import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
-import { StyleProp } from '../StyleProvider';
+import { FieldViewProps } from '../nodes/FieldView';
+import { FocusViewOptions } from '../nodes/FocusViewOptions';
+import { StyleProp } from '../StyleProp';
import './CollectionNoteTakingView.scss';
import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn';
import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivider';
import { CollectionSubView } from './CollectionSubView';
-import { FieldsDropdown } from '../FieldsDropdown';
+
const _global = (window /* browser */ || global) /* node */ as any;
/**
@@ -56,7 +61,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
}
@computed get chromeHidden() {
- return BoolCast(this.layoutDoc.chromeHidden) || this._props.onBrowseClickScript?.() ? true : false;
+ return BoolCast(this.layoutDoc.chromeHidden) || SnappingManager.ExploreMode;
}
// columnHeaders returns the list of SchemaHeaderFields currently being used by the layout doc to render the columns
@computed get colHeaderData() {
@@ -65,7 +70,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
if (needsUnsetCategory || colHeaderData === undefined || colHeaderData.length === 0) {
setTimeout(() => {
const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null) ?? []);
- const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders?.find(sh => sh.heading === 'unset'));
if (needsUnsetCategory || columnHeaders.length === 0) {
columnHeaders.push(new SchemaHeaderField('unset', undefined, undefined, 1));
this.resizeColumns(columnHeaders);
@@ -109,12 +113,12 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// to render the docs you see within an individual column.
children = (docs: Doc[]) => {
TraceMobx();
- return docs.map((d, i) => {
+ return docs.map(d => {
const height = () => this.getDocHeight(d);
const width = () => this.getDocWidth(d);
const style = { width: width(), marginTop: this.gridGap, height: height() };
return (
- <div className={`collectionNoteTakingView-columnDoc`} key={d[Id]} style={style}>
+ <div className="collectionNoteTakingView-columnDoc" key={d[Id]} style={style}>
{this.getDisplayDoc(d, width)}
</div>
);
@@ -133,7 +137,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
const sections = new Map<SchemaHeaderField, Doc[]>(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []]));
const rowCol = this.docsDraggedRowCol;
// this will sort the docs into the correct columns (minus the ones you're currently dragging)
- docs.map(d => {
+ docs.forEach(d => {
const sectionValue = (d[this.notetakingCategoryField] as object) ?? `unset`;
// look for if header exists already
const existingHeader = columnHeaders.find(sh => sh.heading === sectionValue.toString());
@@ -151,7 +155,9 @@ export class CollectionNoteTakingView extends CollectionSubView() {
removeDocDragHighlight = () => {
setTimeout(
- action(() => (this.docsDraggedRowCol.length = 0)),
+ action(() => {
+ this.docsDraggedRowCol.length = 0;
+ }),
100
);
};
@@ -189,13 +195,11 @@ export class CollectionNoteTakingView extends CollectionSubView() {
Object.keys(this._disposers).forEach(key => this._disposers[key]());
}
- moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => {
- return this._props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
- };
+ moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean) => !!(this._props.removeDocument?.(doc) && addDocument?.(doc));
createRef = (ele: HTMLDivElement | null) => {
this._masonryGridRef = ele;
- this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
+ this.createDashEventsTarget(ele!);
};
@computed get onChildClickHandler() {
@@ -215,14 +219,15 @@ export class CollectionNoteTakingView extends CollectionSubView() {
Doc.BrushDoc(doc);
const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
if (found) {
- const top = found.getBoundingClientRect().top;
+ const { top } = found.getBoundingClientRect();
const localTop = this.ScreenToLocalBoxXf().transformPoint(0, top);
if (Math.floor(localTop[1]) !== 0 && Math.ceil(this._props.PanelHeight()) < (this._mainCont?.scrollHeight || 0)) {
- let focusSpeed = options.zoomTime ?? 500;
+ const focusSpeed = options.zoomTime ?? 500;
smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
return focusSpeed;
}
}
+ return undefined;
};
styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
@@ -237,6 +242,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
return this._props.childOpacity();
}
break;
+ default:
}
return this._props.styleProvider?.(doc, props, property);
};
@@ -252,7 +258,9 @@ export class CollectionNoteTakingView extends CollectionSubView() {
const noteTakingDocTransform = () => this.getDocTransform(doc, dref);
return (
<DocumentView
- ref={r => (dref = r || undefined)}
+ ref={r => {
+ dref = r || undefined;
+ }}
Document={doc}
TemplateDataDocument={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)}
pointerEvents={this.blockPointerEventsWhenDragging}
@@ -264,8 +272,8 @@ export class CollectionNoteTakingView extends CollectionSubView() {
layout_fitWidth={this._props.childLayoutFitWidth}
isContentActive={emptyFunction}
onKey={this.onKeyDown}
- //TODO: change this from a prop to a parameter passed into a function
- dontHideOnDrag={true}
+ // TODO: change this from a prop to a parameter passed into a function
+ dontHideOnDrag
isDocumentActive={this.isContentActive}
LayoutTemplate={this._props.childLayoutTemplate}
LayoutTemplateString={this._props.childLayoutString}
@@ -277,7 +285,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
layout_showTitle={this._props.childlayout_showTitle}
dragAction={StrCast(this.layoutDoc.childDragAction) as dropActionType}
onClickScript={this.onChildClickHandler}
- onBrowseClickScript={this._props.onBrowseClickScript}
onDoubleClickScript={this.onChildDoubleClickHandler}
ScreenToLocalTransform={noteTakingDocTransform}
focus={this.focusDocument}
@@ -299,8 +306,8 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// getDocTransform is used to get the coordinates of a document when we go from a view like freeform to columns
getDocTransform(doc: Doc, dref?: DocumentView) {
- const y = this._scroll; // required for document decorations to update when the text box container is scrolled
- const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined);
+ this._scroll; // required for document decorations to update when the text box container is scrolled
+ const { translateX, translateY } = ClientUtils.GetScreenTransform(dref?.ContentDiv || undefined);
// the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off
return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.ScreenToLocalBoxXf().Scale);
}
@@ -308,7 +315,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// how to get the width of a document. Currently returns the width of the column (minus margins)
// if a note doc. Otherwise, returns the normal width (for graphs, images, etc...)
getDocWidth(d: Doc) {
- const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as Field);
+ const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as FieldType);
const existingHeader = this.colHeaderData.find(sh => sh.heading === heading);
const existingWidth = this.layoutDoc._notetaking_columns_autoSize ? 1 / (this.colHeaderData.length ?? 1) : existingHeader?.width ? existingHeader.width : 0;
const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth : this.maxColWidth;
@@ -344,7 +351,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// Adding example: column widths are [0.6, 0.4] --> user adds column at end --> column widths are [0.4, 0.267, 0.33]
@action
resizeColumns = (headers: SchemaHeaderField[]) => {
- const n = headers.length;
const curWidths = headers.reduce((sum, hdr) => sum + Math.abs(hdr.width), 0);
const scaleFactor = 1 / curWidths;
this.dataDoc[this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>(
@@ -382,7 +388,11 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// we alter the pivot fields of the docs in case they are moved to a new column.
const colIndex = this.getColumnFromXCoord(xCoord);
const colHeader = colIndex === undefined ? 'unset' : StrCast(this.colHeaderData[colIndex].heading);
- DragManager.docsBeingDragged.map(doc => doc[DocData]).forEach(d => (d[this.notetakingCategoryField] = colHeader));
+ DragManager.docsBeingDragged
+ .map(doc => doc[DocData])
+ .forEach(d => {
+ d[this.notetakingCategoryField] = colHeader;
+ });
// used to notify sections to re-render
this.docsDraggedRowCol.length = 0;
const columnFromCoord = this.getColumnFromXCoord(xCoord);
@@ -393,7 +403,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// getColumnFromXCoord returns the column index for a given x-coordinate (currently always the client's mouse coordinate).
// This function is used to know which document a column SHOULD be in while it is being dragged.
getColumnFromXCoord = (xCoord: number): number | undefined => {
- let colIndex: number | undefined = undefined;
+ let colIndex: number | undefined;
const numColumns = this.colHeaderData.length;
const coords = [];
let colStartXCoord = 0;
@@ -416,10 +426,10 @@ export class CollectionNoteTakingView extends CollectionSubView() {
const docsMatchingHeader: Doc[] = [];
const colIndex = this.getColumnFromXCoord(xCoord);
const colHeader = colIndex === undefined ? 'unset' : StrCast(this.colHeaderData[colIndex].heading);
- this.childDocs?.map(d => {
+ this.childDocs?.forEach(d => {
if (d instanceof Promise) return;
const sectionValue = (d[this.notetakingCategoryField] as object) ?? 'unset';
- if (sectionValue.toString() == colHeader) {
+ if (sectionValue.toString() === colHeader) {
docsMatchingHeader.push(d);
}
});
@@ -432,9 +442,10 @@ export class CollectionNoteTakingView extends CollectionSubView() {
e.stopPropagation?.();
const newDoc = Doc.MakeCopy(fieldProps.Document, true);
newDoc[DocData].text = undefined;
- FormattedTextBox.SetSelectOnLoad(newDoc);
+ Doc.SetSelectOnLoad(newDoc);
return this.addDocument?.(newDoc);
}
+ return undefined;
};
// onInternalDrop is used when dragging and dropping a document within the view, such as dragging
@@ -463,7 +474,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
}
return true;
}
- } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
+ } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.Document && CollectionFreeFormDocumentView.from(de.complete.linkDragData?.linkDragView)) {
const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' });
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
@@ -572,6 +583,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
alert('You cannot use an existing column name. Please try a new column name');
return value;
}
+ return undefined;
});
const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
const newColWidth = 1 / (this.numGroupColumns + 1);
@@ -590,17 +602,33 @@ export class CollectionNoteTakingView extends CollectionSubView() {
const subItems: ContextMenuProps[] = [];
subItems.push({
description: `${this.layoutDoc._notetaking_columns_autoCreate ? 'Manually' : 'Automatically'} Create columns`,
- event: () => (this.layoutDoc._notetaking_columns_autoCreate = !this.layoutDoc._notetaking_columns_autoCreate),
+ event: () => {
+ this.layoutDoc._notetaking_columns_autoCreate = !this.layoutDoc._notetaking_columns_autoCreate;
+ },
icon: 'computer',
});
subItems.push({ description: 'Remove Empty Columns', event: this.removeEmptyColumns, icon: 'computer' });
subItems.push({
description: `${this.layoutDoc._notetaking_columns_autoSize ? 'Variable Size' : 'Autosize'} Columns`,
- event: () => (this.layoutDoc._notetaking_columns_autoSize = !this.layoutDoc._notetaking_columns_autoSize),
+ event: () => {
+ this.layoutDoc._notetaking_columns_autoSize = !this.layoutDoc._notetaking_columns_autoSize;
+ },
icon: 'plus',
});
- subItems.push({ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight), icon: 'plus' });
- subItems.push({ description: 'Clear All', event: () => (this.dataDoc.data = new List([])), icon: 'times' });
+ subItems.push({
+ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`,
+ event: () => {
+ this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight;
+ },
+ icon: 'plus',
+ });
+ subItems.push({
+ description: 'Clear All',
+ event: () => {
+ this.dataDoc.data = new List([]);
+ },
+ icon: 'times',
+ });
ContextMenu.Instance.addItem({ description: 'Options...', subitems: subItems, icon: 'eye' });
}
};
@@ -625,6 +653,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
const sections = Array.from(this.Sections.entries());
return sections.reduce((list, sec, i) => {
list.push(this.sectionNoteTaking(sec[0], sec[1]));
+ // eslint-disable-next-line react/no-array-index-key
i !== sections.length - 1 && list.push(<CollectionNoteTakingViewDivider key={`divider${i}`} isContentActive={this.isContentActive} index={i} setColumnStartXCoords={this.setColumnStartXCoords} xMargin={this.xMargin} />);
return list;
}, [] as JSX.Element[]);
@@ -658,14 +687,18 @@ export class CollectionNoteTakingView extends CollectionSubView() {
background: this.backgroundColor(),
pointerEvents: this.backgroundEvents,
}}
- onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
- onPointerLeave={action(e => (this.docsDraggedRowCol.length = 0))}
+ onScroll={action(e => {
+ this._scroll = e.currentTarget.scrollTop;
+ })}
+ onPointerLeave={action(() => {
+ this.docsDraggedRowCol.length = 0;
+ })}
onPointerMove={e => e.buttons && this.onPointerMove(false, e.clientX, e.clientY)}
onDragOver={e => this.onPointerMove(true, e.clientX, e.clientY)}
onDrop={this.onExternalDrop.bind(this)}
onContextMenu={this.onContextMenu}
onWheel={e => this._props.isContentActive() && e.stopPropagation()}>
- <>{this.renderedSections}</>
+ {this.renderedSections}
<div className="collectionNotetaking-pivotField" style={{ right: 0, top: 0, position: 'absolute' }}>
<FieldsDropdown
Document={this.Document}
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 448b11b05..c098c033b 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -1,8 +1,9 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { lightOrDark, returnEmptyString } from '../../../Utils';
+import { lightOrDark, returnEmptyString } from '../../../ClientUtils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { RichTextField } from '../../../fields/RichTextField';
import { listSpec } from '../../../fields/Schema';
@@ -10,17 +11,19 @@ import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { Cast, NumCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { DocUtils, Docs } from '../../documents/Documents';
+import { DocumentFromField } from '../../documents/DocFromField';
+import { DocUtils } from '../../documents/DocUtils';
+import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
-import { undoBatch } from '../../util/UndoManager';
+import { undoBatch, undoable } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { EditableView } from '../EditableView';
import { ObservableReactComponent } from '../ObservableReactComponent';
-import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import './CollectionNoteTakingView.scss';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
interface CSVFieldColumnProps {
Document: Doc;
@@ -113,13 +116,15 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
};
@action
- headingChanged = (value: string, shiftDown?: boolean) => {
+ headingChanged = (value: string /* , shiftDown?: boolean */) => {
const castedValue = this.getValue(value);
if (castedValue) {
if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
return false;
}
- this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue));
+ this._props.docList.forEach(d => {
+ d[this._props.pivotField] = castedValue;
+ });
if (this._props.headingObject) {
this._props.headingObject.setHeading(castedValue.toString());
this._heading = this._props.headingObject.heading;
@@ -129,10 +134,14 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
return false;
};
- @action pointerEntered = () => (this._hover = true);
- @action pointerLeave = () => (this._hover = false);
- @undoBatch
- addTextNote = (char: string) => this.addNewTextDoc('-typed text-', false, true);
+ @action pointerEntered = () => {
+ this._hover = true;
+ };
+ @action pointerLeave = () => {
+ this._hover = false;
+ };
+
+ addTextNote = undoable(() => this.addNewTextDoc('-typed text-', false, true), 'add text note');
// addNewTextDoc is called when a user starts typing in a column to create a new node
@action
@@ -142,7 +151,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
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.SetSelectOnLoad(newDoc);
+ Doc.SetSelectOnLoad(newDoc);
FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
return this._props.addDocument?.(newDoc) || false;
};
@@ -171,7 +180,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
doc => {
const key = this._props.pivotField;
doc[key] = this.getValue(this._props.heading);
- FormattedTextBox.SetSelectOnLoad(doc);
+ Doc.SetSelectOnLoad(doc);
return this._props.addDocument?.(doc);
},
this._props.addDocument,
@@ -188,13 +197,14 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
docItems.push({
description: ':' + fieldKey,
event: () => {
- const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document));
+ const created = DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document));
if (created) {
if (this._props.Document.isTemplateDoc) {
Doc.MakeMetadataFieldTemplate(created, this._props.Document);
}
return this._props.addDocument?.(created);
}
+ return undefined;
},
icon: 'compress-arrows-alt',
})
@@ -214,6 +224,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
}
return this._props.addDocument?.(created) || false;
}
+ return undefined;
},
icon: 'compress-arrows-alt',
})
@@ -238,7 +249,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
const key = this._props.pivotField;
const heading = this._heading;
const columnYMargin = this._props.headingObject ? 0 : this._props.yMargin;
- const evContents = heading ? heading : '25';
+ const evContents = heading || '25';
const headingView = this._props.headingObject ? (
<div
key={heading}
@@ -252,17 +263,16 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
className="collectionNoteTakingView-sectionHeader-subCont"
title={evContents === `No Value` ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''}
style={{ background: evContents !== `No Value` ? this._color : 'inherit' }}>
- <EditableView GetValue={() => evContents} isEditingCallback={isEditing => isEditing && this._props.select(false)} SetValue={this.headingChanged} contents={evContents} oneLine={true} />
+ <EditableView GetValue={() => evContents} isEditingCallback={isEditing => isEditing && this._props.select(false)} SetValue={this.headingChanged} contents={evContents} oneLine />
</div>
{(this._props.colHeaderData?.length ?? 0) > 1 && (
- <button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}>
+ <button type="button" className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}>
<FontAwesomeIcon icon="trash" size="lg" />
</button>
)}
</div>
) : null;
const templatecols = this.columnWidth;
- const type = this._props.Document.type;
return (
<>
{headingView}
@@ -270,7 +280,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
<div className="collectionNoteTakingView-columnStack">
<div
key={`${heading}-stack`}
- className={`collectionNoteTakingView-Nodes`}
+ className="collectionNoteTakingView-Nodes"
style={{
padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`,
gridGap: this._props.gridGap,
@@ -282,10 +292,13 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
{!this._props.chromeHidden ? (
<div className="collectionNoteTakingView-DocumentButtons" style={{ display: this._props.isContentActive() ? 'flex' : 'none', marginBottom: 10 }}>
<div className="collectionNoteTakingView-addDocumentButton" style={{ color: lightOrDark(this._props.backgroundColor?.()) }}>
- <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.addTextNote} placeholder={"Type ':' for commands"} contents={'+ Node'} menuCallback={this.menuCallback} />
+ <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.addTextNote} placeholder={"Type ':' for commands"} contents="+ Node" menuCallback={this.menuCallback} />
</div>
<div className="collectionNoteTakingView-addDocumentButton" style={{ color: lightOrDark(this._props.backgroundColor?.()) }}>
- <EditableView {...this._props.editableViewProps()} />
+ {
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ <EditableView {...this._props.editableViewProps()} />
+ }
</div>
</div>
) : null}
diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
index 50a97b978..ddd4b8137 100644
--- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
@@ -1,7 +1,8 @@
import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
+import { setupMoveUpEvents } from '../../../ClientUtils';
import { UndoManager } from '../../util/UndoManager';
import { ObservableReactComponent } from '../ObservableReactComponent';
@@ -27,7 +28,7 @@ export class CollectionNoteTakingViewDivider extends ObservableReactComponent<Di
setupMoveUpEvents(
this,
e,
- (e, down, delta) => {
+ (moveEv, down, delta) => {
if (!batch) batch = UndoManager.StartBatch('resizing');
this._props.setColumnStartXCoords(delta[0], this._props.index);
return false;
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 9d68c621b..5b3f625db 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,19 +1,22 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { action, computed, IReactionDisposer, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
import { Doc, DocListCast } from '../../../fields/Doc';
import { ScriptField } from '../../../fields/ScriptField';
-import { NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
-import { DocUtils } from '../../documents/Documents';
-import { dropActionType } from '../../util/DragManager';
-import { SelectionManager } from '../../util/SelectionManager';
+import { NumCast, StrCast, toList } from '../../../fields/Types';
+import { emptyFunction } from '../../../Utils';
+import { DocUtils } from '../../documents/DocUtils';
+import { dropActionType } from '../../util/DropActionTypes';
import { undoBatch, UndoManager } from '../../util/UndoManager';
-import { OpenWhere } from '../nodes/DocumentView';
+import { OpenWhere } from '../nodes/OpenWhere';
import { computePassLayout, computeStarburstLayout } from './collectionFreeForm';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import './CollectionPileView.scss';
import { CollectionSubView } from './CollectionSubView';
+import { DocumentView } from '../nodes/DocumentView';
@observer
export class CollectionPileView extends CollectionSubView() {
@@ -40,15 +43,15 @@ export class CollectionPileView extends CollectionSubView() {
layoutEngine = () => StrCast(this.Document._freeform_pileEngine);
@undoBatch
- addPileDoc = (doc: Doc | Doc[]) => {
- (doc instanceof Doc ? [doc] : doc).map(d => DocUtils.iconify(d));
- return this._props.addDocument?.(doc) || false;
+ addPileDoc = (docs: Doc | Doc[]) => {
+ toList(docs).map(doc => DocUtils.iconify(doc));
+ return this._props.addDocument?.(docs) || false;
};
@undoBatch
- 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;
+ removePileDoc = (docs: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
+ toList(docs).forEach(doc => Doc.deiconifyView(doc));
+ const ret = this._props.moveDocument?.(docs, targetCollection, addDoc) || false;
if (ret && !DocListCast(this.dataDoc[this.fieldKey ?? 'data']).length) this.DocumentView?.()._props.removeDocument?.(this.Document);
return ret;
};
@@ -66,6 +69,7 @@ export class CollectionPileView extends CollectionSubView() {
return (
<div className="collectionPileView-innards" style={{ pointerEvents: this.contentEvents }}>
<CollectionFreeFormView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props} //
layoutEngine={this.layoutEngine}
addDocument={this.addPileDoc}
@@ -116,16 +120,16 @@ export class CollectionPileView extends CollectionSubView() {
setupMoveUpEvents(
this,
e,
- (e: PointerEvent, down: number[], delta: number[]) => {
- if (this.layoutEngine() === 'pass' && this.childDocs.length && e.shiftKey) {
+ (moveEv: PointerEvent, down: number[], delta: number[]) => {
+ if (this.layoutEngine() === 'pass' && this.childDocs.length && moveEv.shiftKey) {
dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]);
if (dist > 100) {
if (!this._undoBatch) {
this._undoBatch = UndoManager.StartBatch('layout pile');
}
const doc = this.childDocs[0];
- doc.x = e.clientX;
- doc.y = e.clientY;
+ doc.x = moveEv.clientX;
+ doc.y = moveEv.clientY;
this._props.addDocTab(doc, OpenWhere.inParentFromScreen) && (this._props.removeDocument?.(doc) || false);
dist = 0;
}
@@ -146,7 +150,7 @@ export class CollectionPileView extends CollectionSubView() {
@undoBatch
onClick = (e: React.MouseEvent) => {
if (e.button === 0) {
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
this.toggleStarburst();
e.stopPropagation();
}
@@ -154,7 +158,7 @@ export class CollectionPileView extends CollectionSubView() {
render() {
return (
- <div className={`collectionPileView`} onClick={this.onClick} onPointerDown={this.pointerDown} style={{ width: this._props.PanelWidth(), height: '100%' }}>
+ <div className="collectionPileView" onClick={this.onClick} onPointerDown={this.pointerDown} style={{ width: this._props.PanelWidth(), height: '100%' }}>
{this.contents}
</div>
);
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 656f850b3..f9b123bb6 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -1,7 +1,13 @@
+/* eslint-disable react/jsx-props-no-spreading */
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable no-use-before-define */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import * as React from 'react';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../ClientUtils';
import { Doc, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -10,26 +16,26 @@ import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { Cast, NumCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
-import { emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
+import { emptyFunction, formatTime } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
-import { DocumentManager } from '../../util/DocumentManager';
+import { FollowLinkScript, IsFollowLinkScript } from '../../documents/DocUtils';
import { DragManager } from '../../util/DragManager';
-import { FollowLinkScript, IsFollowLinkScript, LinkFollower } from '../../util/LinkFollower';
-import { LinkManager } from '../../util/LinkManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from '../../util/UndoManager';
-import { CollectionSubView } from '../collections/CollectionSubView';
+import { VideoThumbnails } from '../global/globalEnums';
import { LightboxView } from '../LightboxView';
import { AudioWaveform } from '../nodes/audio/AudioWaveform';
-import { DocumentView, OpenWhere } from '../nodes/DocumentView';
-import { FocusFuncType, FocusViewOptions, StyleProviderFuncType } from '../nodes/FieldView';
+import { DocumentView } from '../nodes/DocumentView';
+import { FocusFuncType, StyleProviderFuncType } from '../nodes/FieldView';
+import { FocusViewOptions } from '../nodes/FocusViewOptions';
import { LabelBox } from '../nodes/LabelBox';
-import { VideoBox } from '../nodes/VideoBox';
+import { OpenWhere } from '../nodes/OpenWhere';
import { ObservableReactComponent } from '../ObservableReactComponent';
import './CollectionStackedTimeline.scss';
+import { CollectionSubView } from './CollectionSubView';
export type CollectionStackedTimelineProps = {
Play: () => void;
@@ -57,9 +63,14 @@ export enum TrimScope {
@observer
export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() {
+ // eslint-disable-next-line no-use-before-define
public static SelectingRegions: Set<CollectionStackedTimeline> = new Set();
public static StopSelecting() {
- this.SelectingRegions.forEach(action(region => (region._selectingRegion = false)));
+ this.SelectingRegions.forEach(
+ action(region => {
+ region._selectingRegion = false;
+ })
+ );
this.SelectingRegions.clear();
}
constructor(props: any) {
@@ -73,7 +84,6 @@ 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 public static CurrentlyPlaying: DocumentView[] = [];
@observable _selectingRegion = false;
@observable _markerEnd: number | undefined = undefined;
@observable _trimming: number = TrimScope.None;
@@ -130,7 +140,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
componentWillUnmount() {
document.removeEventListener('keydown', this.keyEvents, true);
if (this._selectingRegion) {
- runInAction(() => (this._selectingRegion = false));
+ runInAction(() => {
+ this._selectingRegion = false;
+ });
CollectionStackedTimeline.SelectingRegions.delete(this);
}
}
@@ -166,7 +178,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
getView = async (doc: Doc, options: FocusViewOptions): Promise<Opt<DocumentView>> =>
new Promise<Opt<DocumentView>>(res => {
if (doc.hidden) options.didMove = !(doc.hidden = false);
- const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
+ const findDoc = (finish: (dv: DocumentView) => void) => DocumentView.addViewRenderedCb(doc, dv => finish(dv));
findDoc(dv => res(dv));
});
@@ -174,9 +186,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this._props.endTag], val) ?? null);
// converts screen pixel offset to time
- toTimeline = (screen_delta: number, width: number) => {
- return Math.max(this.clipStart, Math.min(this.clipEnd, (screen_delta / width) * this.clipDuration + this.clipStart));
- };
+ // prettier-ignore
+ toTimeline = (screenDelta: number, width: number) => //
+ Math.max(this.clipStart, Math.min(this.clipEnd, (screenDelta / width) * this.clipDuration + this.clipStart));
@computed get rangeClick() {
// prettier-ignore
@@ -234,6 +246,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd));
e.stopPropagation();
break;
+ default:
}
}
};
@@ -253,17 +266,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
@action
onPointerDownTimeline = (e: React.PointerEvent): void => {
const rect = this._timeline?.getBoundingClientRect();
- const clientX = e.clientX;
- const diff = rect ? clientX - rect?.x : null;
- const shiftKey = e.shiftKey;
+ const { clientX, shiftKey } = e;
if (rect && this._props.isContentActive()) {
const wasPlaying = this._props.playing();
if (wasPlaying) this._props.Pause();
- var wasSelecting = this._markerEnd !== undefined;
+ let wasSelecting = this._markerEnd !== undefined;
setupMoveUpEvents(
this,
e,
- action(e => {
+ action(() => {
if (!wasSelecting) {
this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width);
wasSelecting = true;
@@ -272,8 +283,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
return false;
}),
- action((e, movement, isClick) => {
- this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
+ action((upEvent, movement, isClick) => {
+ this._markerEnd = this.toTimeline(upEvent.clientX - rect.x, rect.width);
if (this._markerEnd < this._markerStart) {
const tmp = this._markerStart;
this._markerStart = this._markerEnd;
@@ -281,13 +292,13 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
}
if (!isClick && Math.abs(movement[0]) > 15 && !this.IsTrimming) {
const anchor = CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
- setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false));
+ setTimeout(() => DocumentView.getDocumentView(anchor)?.select(false));
}
(!isClick || !wasSelecting) && (this._markerEnd = undefined);
this._timelineWrapper && (this._timelineWrapper.style.cursor = '');
}),
- (e, doubleTap) => {
- if (e.button !== 2) {
+ (clickEv, doubleTap) => {
+ if (clickEv.button !== 2) {
this._props.select(false);
!wasPlaying && doubleTap && this._props.Play();
}
@@ -309,11 +320,11 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
onHover = (e: React.MouseEvent): void => {
e.stopPropagation();
const rect = this._timeline?.getBoundingClientRect();
- const clientX = e.clientX;
+ const { clientX } = e;
if (rect) {
this._hoverTime = this.toTimeline(clientX - rect.x, rect.width);
if (this.thumbnails) {
- const nearest = Math.floor((this._hoverTime / this._props.rawDuration) * VideoBox.numThumbnails);
+ const nearest = Math.floor((this._hoverTime / this._props.rawDuration) * VideoThumbnails.DENSE);
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;
}
@@ -327,14 +338,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
setupMoveUpEvents(
this,
e,
- action((e, [], []) => {
+ action(moveEv => {
if (rect && this._props.isContentActive()) {
- this._trimStart = Math.min(Math.max(this.trimStart + (e.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength);
+ this._trimStart = Math.min(Math.max(this.trimStart + (moveEv.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength);
}
return false;
}),
emptyFunction,
- action((e, doubleTap) => {
+ action((clickEv, doubleTap) => {
doubleTap && (this._trimStart = this.clipStart);
})
);
@@ -347,14 +358,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
setupMoveUpEvents(
this,
e,
- action((e, [], []) => {
+ action(moveEv => {
if (rect && this._props.isContentActive()) {
- this._trimEnd = Math.max(Math.min(this.trimEnd + (e.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength);
+ this._trimEnd = Math.max(Math.min(this.trimEnd + (moveEv.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength);
}
return false;
}),
emptyFunction,
- action((e, doubleTap) => {
+ action((clickEv, doubleTap) => {
doubleTap && (this._trimEnd = this.clipEnd);
})
);
@@ -383,7 +394,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
// handles dragging and dropping markers in timeline
@action
- internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) {
+ internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData) {
if (super.onInternalDrop(e, de)) {
// determine x coordinate of drop and assign it to the documents being dragged --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view
const localPt = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y);
@@ -403,7 +414,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
}
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
- if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, 0);
+ if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData);
return false;
};
@@ -441,7 +452,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
}
@action
- playOnClick = (anchorDoc: Doc, clientX: number) => {
+ playOnClick = (anchorDoc: Doc /* , clientX: number */) => {
const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05;
const endTime = this.anchorEnd(anchorDoc);
if (this.layoutDoc.autoPlayAnchors) {
@@ -450,17 +461,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._props.playFrom(seekTimeInSeconds, endTime);
this.scrollToTime(seekTimeInSeconds);
}
- } else {
- if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) && endTime > NumCast(this.layoutDoc._layout_currentTimecode)) {
- if (!this.layoutDoc.autoPlayAnchors && this._props.playing()) {
- this._props.Pause();
- } else {
- this._props.Play();
- }
+ } else if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) && endTime > NumCast(this.layoutDoc._layout_currentTimecode)) {
+ if (!this.layoutDoc.autoPlayAnchors && this._props.playing()) {
+ this._props.Pause();
} else {
- this._props.playFrom(seekTimeInSeconds, endTime);
- this.scrollToTime(seekTimeInSeconds);
+ this._props.Play();
}
+ } else {
+ this._props.playFrom(seekTimeInSeconds, endTime);
+ this.scrollToTime(seekTimeInSeconds);
}
return { select: true };
};
@@ -468,7 +477,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
@action
clickAnchor = (anchorDoc: Doc, clientX: number) => {
if (IsFollowLinkScript(anchorDoc.onClick)) {
- LinkFollower.FollowLink(undefined, anchorDoc, false);
+ DocumentView.FollowLink(undefined, anchorDoc, false);
}
const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05;
const endTime = this.anchorEnd(anchorDoc);
@@ -479,19 +488,17 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
const rect = this._timeline?.getBoundingClientRect();
rect && this._props.setTime(this.toTimeline(clientX - rect.x, rect.width));
}
+ } else if (this.layoutDoc.autoPlayAnchors) {
+ this._props.playFrom(seekTimeInSeconds, endTime);
} else {
- if (this.layoutDoc.autoPlayAnchors) {
- this._props.playFrom(seekTimeInSeconds, endTime);
- } else {
- this._props.setTime(seekTimeInSeconds);
- }
+ this._props.setTime(seekTimeInSeconds);
}
return { select: true };
};
// makes sure no anchors overlaps each other by setting the correct position and width
getLevel = (m: Doc, placed: { anchorStartTime: number; anchorEndTime: number; level: number }[]) => {
- const timelineContentWidth = this.timelineContentWidth;
+ const { timelineContentWidth } = this;
const x1 = this.anchorStart(m);
const x2 = this.anchorEnd(m, x1 + (10 / timelineContentWidth) * this.clipDuration);
let max = 0;
@@ -503,6 +510,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
max = Math.max(max, p.level);
return p.level;
}
+ return undefined;
})
);
let level = max + 1;
@@ -564,10 +572,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
onWheel={e => this.isContentActive() && e.stopPropagation()}
onScroll={this.setScroll}
onMouseMove={e => this.isContentActive() && this.onHover(e)}
- ref={wrapper => (this._timelineWrapper = wrapper)}>
+ ref={wrapper => {
+ this._timelineWrapper = wrapper;
+ }}>
<div
className="collectionStackedTimeline"
- ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)}
+ ref={(timeline: HTMLDivElement | null) => {
+ this._timeline = timeline;
+ }}
onClick={e => this.isContentActive() && StopEvent(e)}
onPointerDown={e => this.isContentActive() && this.onPointerDownTimeline(e)}
style={{ width: this.timelineContentWidth }}>
@@ -582,7 +594,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
const height = this._props.PanelHeight() / maxLevel;
return this.Document.hideAnchors ? null : (
<div
- className={'collectionStackedTimeline-marker-timeline'}
+ className="collectionStackedTimeline-marker-timeline"
key={d.anchor[Id]}
style={{
left,
@@ -592,6 +604,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
pointerEvents: 'none',
}}>
<StackedTimelineAnchor
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
mark={d.anchor}
containerViewPath={this._props.containerViewPath}
@@ -646,7 +659,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
{this.IsTrimming !== TrimScope.None && (
<>
- <div className="collectionStackedTimeline-trim-shade" style={{ width: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%` }}></div>
+ <div className="collectionStackedTimeline-trim-shade" style={{ width: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%` }} />
<div
className="collectionStackedTimeline-trim-controls"
@@ -654,8 +667,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
left: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%`,
width: `${((this.trimEnd - this.trimStart) / this.clipDuration) * 100}%`,
}}>
- <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimLeft}></div>
- <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimRight}></div>
+ <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimLeft} />
+ <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimRight} />
</div>
<div
@@ -663,7 +676,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
style={{
left: `${((this.trimEnd - this.clipStart) / this.clipDuration) * 100}%`,
width: `${((this.clipEnd - this.trimEnd) / this.clipDuration) * 100}%`,
- }}></div>
+ }}
+ />
</>
)}
</div>
@@ -685,7 +699,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
interface StackedTimelineAnchorProps {
mark: Doc;
whenChildContentsActiveChanged: (isActive: boolean) => void;
- addDocTab: (doc: Doc, where: OpenWhere) => boolean;
+ addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean;
rangeClickScript: () => ScriptField;
rangePlayScript: () => ScriptField;
left: number;
@@ -735,20 +749,20 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
this._disposer = reaction(
() => this._props.currentTimecode(),
time => {
- const dictationDoc = Cast(this._props.layoutDoc.data_dictation, Doc, null);
- const isDictation = dictationDoc && LinkManager.Links(this._props.mark).some(link => Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc);
+ // const dictationDoc = Cast(this._props.layoutDoc.data_dictation, Doc, null);
+ // const isDictation = dictationDoc && LinkManager.Links(this._props.mark).some(link => Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc);
if (
!LightboxView.LightboxDoc &&
// bcz: when should links be followed? we don't want to move away from the video to follow a link but we can open it in a sidebar/etc. But we don't know that upfront.
// for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video.
- /*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this._props.layoutDoc))*/
+ /* (isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this._props.layoutDoc)) */
!this._props.layoutDoc.dontAutoFollowLinks &&
- LinkManager.Links(this._props.mark).length &&
+ Doc.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);
+ DocumentView.FollowLink(undefined, this._props.mark, false);
}
this._lastTimecode = time;
}
@@ -763,34 +777,33 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
// starting the drag event for anchor resizing
@action
onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => {
- //this._props._timeline?.setPointerCapture(e.pointerId);
- const newTime = (e: PointerEvent) => {
- const rect = (e.target as any).getBoundingClientRect();
- return this._props.toTimeline(e.clientX - rect.x, rect.width);
+ const newTime = (timeDownEv: PointerEvent) => {
+ const rect = (timeDownEv.target as any).getBoundingClientRect();
+ return this._props.toTimeline(timeDownEv.clientX - rect.x, rect.width);
};
- const changeAnchor = (anchor: Doc, left: boolean, time: number | undefined) => {
+ const changeAnchor = (time: number | undefined) => {
const timelineOnly = Cast(anchor[this._props.startTag], 'number', null) !== undefined;
if (timelineOnly) {
- if (!left && time !== undefined && time <= NumCast(anchor[this._props.startTag])) time = undefined;
- Doc.SetInPlace(anchor, left ? this._props.startTag : this._props.endTag, time, true);
- if (!left) Doc.SetInPlace(anchor, 'layout_borderRounding', time !== undefined ? undefined : '100%', true);
+ const timeMod = !left && time !== undefined && time <= NumCast(anchor[this._props.startTag]) ? undefined : time;
+ Doc.SetInPlace(anchor, left ? this._props.startTag : this._props.endTag, timeMod, true);
+ if (!left) Doc.SetInPlace(anchor, 'layout_borderRounding', timeMod !== undefined ? undefined : '100%', true);
} else {
anchor[left ? '_timecodeToShow' : '_timecodeToHide'] = time;
}
return false;
};
this.noEvents = true;
- var undo: UndoManager.Batch | undefined;
+ let undo: UndoManager.Batch | undefined;
setupMoveUpEvents(
this,
e,
- e => {
+ moveEv => {
if (!undo) undo = UndoManager.StartBatch('drag anchor');
- this._props.setTime(newTime(e));
- return changeAnchor(anchor, left, newTime(e));
+ this._props.setTime(newTime(moveEv));
+ return changeAnchor(newTime(moveEv));
},
- action(e => {
- this._props.setTime(newTime(e));
+ action(upEv => {
+ this._props.setTime(newTime(upEv));
undo?.end();
this.noEvents = false;
}),
@@ -828,7 +841,9 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
{...this._props}
NativeWidth={returnZero}
NativeHeight={returnZero}
- ref={action((r: DocumentView | null) => (anchor.view = r))}
+ ref={action((r: DocumentView | null) => {
+ anchor.view = r;
+ })}
Document={mark}
TemplateDataDocument={undefined}
containerViewPath={this._props.containerViewPath}
@@ -851,7 +866,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
onClickScript={script}
onDoubleClickScript={this._props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
ignoreAutoHeight={false}
- hideResizeHandles={true}
+ hideResizeHandles
contextMenuItems={this.contextMenuItems}
/>
),
@@ -877,12 +892,15 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
);
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function formatToTime(time: number): any {
return formatTime(time);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function min(num1: number, num2: number): number {
return Math.min(num1, num2);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function max(num1: number, num2: number): number {
return Math.max(num1, num2);
});
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index bf0393883..3f12a281e 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,8 +1,11 @@
+/* eslint-disable react/jsx-props-no-spreading */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+// eslint-disable-next-line import/no-extraneous-dependencies
import * as CSS from 'csstype';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { ClientUtils, DivHeight, returnEmptyDoclist, returnNone, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils';
import { Doc, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -11,10 +14,12 @@ import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { DivHeight, emptyFunction, returnEmptyDoclist, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
+import { emptyFunction } from '../../../Utils';
+import { Docs } from '../../documents/Documents';
import { CollectionViewType } from '../../documents/DocumentTypes';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DocUtils } from '../../documents/DocUtils';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { SettingsManager } from '../../util/SettingsManager';
import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from '../../util/UndoManager';
@@ -24,13 +29,14 @@ import { EditableView } from '../EditableView';
import { LightboxView } from '../LightboxView';
import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
import { DocumentView } from '../nodes/DocumentView';
-import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView';
-import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
-import { StyleProp } from '../StyleProvider';
+import { FieldViewProps } from '../nodes/FieldView';
+import { FocusViewOptions } from '../nodes/FocusViewOptions';
+import { StyleProp } from '../StyleProp';
import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
import './CollectionStackingView.scss';
import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn';
import { CollectionSubView } from './CollectionSubView';
+
const _global = (window /* browser */ || global) /* node */ as any;
export type collectionStackingViewProps = {
@@ -125,11 +131,16 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// TODO: plj - these are the children
children = (docs: Doc[]) => {
- //TODO: can somebody explain me to what exactly TraceMobX is?
+ // TODO: can somebody explain me to what exactly TraceMobX is?
TraceMobx();
// appears that we are going to reset the _docXfs. TODO: what is Xfs?
this._docXfs.length = 0;
- this._renderCount < docs.length && setTimeout(action(() => (this._renderCount = Math.min(docs.length, this._renderCount + 5))));
+ this._renderCount < docs.length &&
+ setTimeout(
+ action(() => {
+ this._renderCount = Math.min(docs.length, this._renderCount + 5);
+ })
+ );
return docs.map((d, i) => {
const height = () => this.getDocHeight(d);
const width = () => this.getDocWidth(d);
@@ -152,19 +163,21 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
};
// is sections that all collections inherit? I think this is how we show the masonry/columns
- //TODO: this seems important
+ // TODO: this seems important
get Sections() {
// appears that pivot field IS actually for sorting
if (!this.pivotField || this.colHeaderData instanceof Promise) return new Map<SchemaHeaderField, Doc[]>();
if (this.colHeaderData === undefined) {
- setTimeout(() => (this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>()), 0);
+ setTimeout(() => {
+ this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>();
+ });
return new Map<SchemaHeaderField, Doc[]>();
}
const colHeaderData = Array.from(this.colHeaderData);
const fields = new Map<SchemaHeaderField, Doc[]>(colHeaderData.map(sh => [sh, []] as [SchemaHeaderField, []]));
let changed = false;
- this.filteredChildren.map(d => {
+ this.filteredChildren.forEach(d => {
const sectionValue = (d[this.pivotField] ? d[this.pivotField] : `NO ${this.pivotField.toUpperCase()} VALUE`) as object;
// the next five lines ensures that floating point rounding errors don't create more than one section -syip
const parsed = parseInt(sectionValue.toString());
@@ -186,7 +199,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
if (this.layoutDoc._columnsHideIfEmpty) {
Array.from(fields.keys())
.filter(key => !fields.get(key)!.length)
- .map(header => {
+ .forEach(header => {
fields.delete(header);
colHeaderData.splice(colHeaderData.indexOf(header), 1);
changed = true;
@@ -207,11 +220,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// reset section headers when a new filter is inputted
this._disposers.pivotField = reaction(
() => this.pivotField,
- () => (this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List())
+ () => {
+ this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List();
+ }
);
this._disposers.autoHeight = reaction(
() => this.layoutDoc._layout_autoHeight,
- layout_autoHeight => layout_autoHeight && this._props.setHeight?.(this.headerMargin + (this.isStackingView ? Math.max(...this._refList.map(DivHeight)) : this._refList.reduce((p, r) => p + DivHeight(r), 0)))
+ layoutAutoHeight => layoutAutoHeight && this._props.setHeight?.(this.headerMargin + (this.isStackingView ? Math.max(...this._refList.map(DivHeight)) : this._refList.reduce((p, r) => p + DivHeight(r), 0)))
);
this._disposers.refList = reaction(
() => ({ refList: this._refList.slice(), autoHeight: this.layoutDoc._layout_autoHeight && !LightboxView.Contains(this.DocumentView?.()) }),
@@ -231,9 +246,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
isAnyChildContentActive = () => this._props.isAnyChildContentActive();
- moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
- return this._props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
- };
+ moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => !!(this._props.removeDocument?.(doc) && addDocument?.(doc));
onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
@computed get onChildDoubleClickHandler() {
@@ -250,10 +263,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
if (found) {
- const top = found.getBoundingClientRect().top;
+ const { top } = found.getBoundingClientRect();
const localTop = this.ScreenToLocalBoxXf().transformPoint(0, top);
if (Math.floor(localTop[1]) !== 0) {
- let focusSpeed = options.zoomTime ?? 500;
+ const focusSpeed = options.zoomTime ?? 500;
smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
return focusSpeed;
}
@@ -276,18 +289,18 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
if (['Enter'].includes(e.key) && e.ctrlKey) {
e.stopPropagation?.();
- const below = !e.altKey && e.key !== 'Tab';
- const layout_fieldKey = StrCast(fieldProps.fieldKey);
+ const layoutFieldKey = StrCast(fieldProps.fieldKey);
const newDoc = Doc.MakeCopy(fieldProps.Document, true);
const dataField = fieldProps.Document[Doc.LayoutFieldKey(newDoc)];
newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
- if (layout_fieldKey !== 'layout' && fieldProps.Document[layout_fieldKey] instanceof Doc) {
- newDoc[layout_fieldKey] = fieldProps.Document[layout_fieldKey];
+ if (layoutFieldKey !== 'layout' && fieldProps.Document[layoutFieldKey] instanceof Doc) {
+ newDoc[layoutFieldKey] = fieldProps.Document[layoutFieldKey];
}
newDoc[DocData].text = undefined;
- FormattedTextBox.SetSelectOnLoad(newDoc);
+ Doc.SetSelectOnLoad(newDoc);
return this.addDocument?.(newDoc);
}
+ return false;
};
isContentActive = () => (this._props.isContentActive() ? true : this._props.isSelected() === false || this._props.isContentActive() === false ? false : undefined);
@@ -327,7 +340,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive}
onKey={this.onKeyDown}
DataTransition={trans}
- onBrowseClickScript={this._props.onBrowseClickScript}
isDocumentActive={this.isContentActive}
LayoutTemplate={this._props.childLayoutTemplate}
LayoutTemplateString={this._props.childLayoutString}
@@ -363,7 +375,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
getDocTransform(doc: Doc) {
const dref = this.docRefs.get(doc);
this._scroll; // must be referenced for document decorations to update when the text box container is scrolled
- const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv);
+ const { translateX, translateY } = ClientUtils.GetScreenTransform(dref?.ContentDiv);
// the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off
return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.ScreenToLocalBoxXf().Scale);
}
@@ -399,7 +411,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// This following three functions must be from the view Mehek showed
columnDividerDown = (e: React.PointerEvent) => {
- runInAction(() => (this._cursor = 'grabbing'));
+ runInAction(() => {
+ this._cursor = 'grabbing';
+ });
const batch = UndoManager.StartBatch('stacking width');
setupMoveUpEvents(
this,
@@ -425,7 +439,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
onPointerDown={this.columnDividerDown}
ref={this._draggerRef}
style={{ cursor: this._cursor, color: SettingsManager.userColor, left: `${this.columnWidth + this.xMargin}px`, top: `${Math.max(0, this.yMargin - 9)}px` }}>
- <FontAwesomeIcon icon={'arrows-alt-h'} />
+ <FontAwesomeIcon icon="arrows-alt-h" />
</div>
);
}
@@ -439,7 +453,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
let dropAfter = 0;
if (de.complete.docDragData) {
// going to re-add the docs to the _docXFs based on position of where we just dropped
- this._docXfs.map((cd, i) => {
+ this._docXfs.forEach((cd, i) => {
const pos = cd
.stackedDocTransform()
.inverse()
@@ -468,7 +482,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
return true;
}
- } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
+ } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.Document && CollectionFreeFormDocumentView.from(de.complete.linkDragData?.linkDragView)) {
const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' });
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
@@ -496,7 +510,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
onExternalDrop = async (e: React.DragEvent): Promise<void> => {
const where = [e.clientX, e.clientY];
let targInd = -1;
- this._docXfs.map((cd, i) => {
+ this._docXfs.forEach((cd, i) => {
const pos = cd
.stackedDocTransform()
.inverse()
@@ -521,10 +535,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// what a section looks like if we're in stacking view
sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
const key = this.pivotField;
- let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined = undefined;
+ let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
if (this.pivotField) {
const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
+ // eslint-disable-next-line prefer-destructuring
type = types[0];
}
}
@@ -557,9 +572,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// what a section looks like if we're in masonry. Shouldn't actually need to use this.
sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[], first: boolean) => {
const key = this.pivotField;
- let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined = undefined;
+ let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
+ // eslint-disable-next-line prefer-destructuring
type = types[0];
}
const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this._props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))));
@@ -609,9 +625,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
const cm = ContextMenu.Instance;
const options = cm.findByDescription('Options...');
const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
- optionItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' });
- optionItems.push({ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight), icon: 'plus' });
- optionItems.push({ description: 'Clear All', event: () => (this.dataDoc[this.fieldKey ?? 'data'] = new List([])), icon: 'times' });
+ optionItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => { this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill; }, icon: 'plus' }); // prettier-ignore
+ optionItems.push({ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => { this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight; }, icon: 'plus' }); // prettier-ignore
+ optionItems.push({ description: 'Clear All', event: () => { this.dataDoc[this.fieldKey ?? 'data'] = new List([]); } , icon: 'times' }); // prettier-ignore
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
}
};
@@ -639,7 +655,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
addDocument={this._props.addDocument}
moveDocument={this._props.moveDocument}
addDocTab={this._props.addDocTab}
- onBrowseClickScript={this._props.onBrowseClickScript}
pinToPres={emptyFunction}
rootSelected={this.rootSelected}
removeDocument={this._props.removeDocument}
@@ -700,7 +715,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
className={this.isStackingView ? 'collectionStackingView' : 'collectionMasonryView'}
ref={ele => {
this._masonryGridRef = ele;
- this.createDashEventsTarget(ele); //so the whole grid is the drop target?
+ this.createDashEventsTarget(ele); // so the whole grid is the drop target?
this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
this._oldWheel = ele;
// prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
@@ -711,7 +726,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor),
pointerEvents: (this._props.pointerEvents?.() as any) ?? this.backgroundEvents,
}}
- onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
+ onScroll={action(e => {
+ this._scroll = e.currentTarget.scrollTop;
+ })}
onDrop={this.onExternalDrop.bind(this)}
onContextMenu={this.onContextMenu}
onWheel={e => this.isContentActive() && e.stopPropagation()}>
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index c5292f880..e2ad5b31d 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -1,7 +1,11 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { DivHeight, DivWidth, returnEmptyString, setupMoveUpEvents } from '../../../ClientUtils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { RichTextField } from '../../../fields/RichTextField';
import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
@@ -9,10 +13,13 @@ import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, NumCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { DivHeight, DivWidth, emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
+import { emptyFunction } from '../../../Utils';
+import { DocumentFromField } from '../../documents/DocFromField';
+import { Docs } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DocUtils } from '../../documents/DocUtils';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
@@ -95,7 +102,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
this._ele && this.props.refList.push(this._ele);
this._disposers.collapser = reaction(
() => this._props.headingObject?.collapsed,
- collapsed => (this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false),
+ collapsed => { this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false; }, // prettier-ignore
{ fireImmediately: true }
);
}
@@ -105,7 +112,6 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
this._ele = null;
}
- //TODO: what is scripting? I found it in SetInPlace def but don't know what that is
@undoBatch
columnDrop = action((e: Event, de: DragManager.DropEvent) => {
const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) };
@@ -121,13 +127,13 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
};
@action
- headingChanged = (value: string, shiftDown?: boolean) => {
+ headingChanged = (value: string /* , shiftDown?: boolean */) => {
const castedValue = this.getValue(value);
if (castedValue) {
if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
return false;
}
- this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue));
+ this._props.pivotField && this._props.docList.forEach(d => { d[this._props.pivotField] = castedValue; }) // prettier-ignore
if (this._props.headingObject) {
this._props.headingObject.setHeading(castedValue.toString());
this._heading = this._props.headingObject.heading;
@@ -143,9 +149,9 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
this._color = color;
};
- @action pointerEntered = () => SnappingManager.IsDragging && (this._background = '#b4b4b4');
- @action pointerLeave = () => (this._background = 'inherit');
- @undoBatch typedNote = (char: string) => this.addNewTextDoc('-typed text-', false, true);
+ @action pointerEntered = () => { SnappingManager.IsDragging && (this._background = '#b4b4b4'); } // prettier-ignore
+ @action pointerLeave = () => { this._background = 'inherit'}; // prettier-ignore
+ @undoBatch typedNote = () => this.addNewTextDoc('-typed text-', false, true);
@action
addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
@@ -153,17 +159,20 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
const key = this._props.pivotField;
const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true });
key && (newDoc[key] = this.getValue(this._props.heading));
- const maxHeading = this._props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0);
+ const maxHeading = this._props.docList.reduce((prevHeading, doc) => (NumCast(doc.heading) > prevHeading ? NumCast(doc.heading) : prevHeading), 0);
const heading = maxHeading === 0 || this._props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
newDoc.heading = heading;
- FormattedTextBox.SetSelectOnLoad(newDoc);
+ Doc.SetSelectOnLoad(newDoc);
FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
return this._props.addDocument?.(newDoc) || false;
};
@action
deleteColumn = () => {
- this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = undefined));
+ this._props.pivotField &&
+ this._props.docList.forEach(d => {
+ d[this._props.pivotField] = undefined;
+ });
if (this._props.colHeaderData && this._props.headingObject) {
const index = this._props.colHeaderData.indexOf(this._props.headingObject);
this._props.colHeaderData.splice(index, 1);
@@ -178,8 +187,8 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
headerDown = (e: React.PointerEvent<HTMLDivElement>) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
- //TODO: I think this is where I'm supposed to edit stuff
- startDrag = (e: PointerEvent, down: number[], delta: number[]) => {
+ // TODO: I think this is where I'm supposed to edit stuff
+ startDrag = (e: PointerEvent) => {
// is MakeEmbedding a way to make a copy of a doc without rendering it?
const embedding = Doc.MakeEmbedding(this._props.Document);
embedding._width = this._props.columnWidth / (this._props.colHeaderData?.length || 1);
@@ -210,23 +219,21 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
);
};
- renderMenu = () => {
- return (
- <div className="collectionStackingView-optionPicker">
- <div className="optionOptions">
- <div className={'optionPicker' + (true ? ' active' : '')} onClick={action(() => {})}>
- Add options here
- </div>
- </div>
+ renderMenu = () => (
+ <div className="collectionStackingView-optionPicker">
+ <div className="optionOptions">
+ <div className="optionPicker active">Add options here</div>
</div>
- );
- };
+ </div>
+ );
@observable private collapsed: boolean = false;
- private toggleVisibility = action(() => (this.collapsed = !this.collapsed));
+ private toggleVisibility = action(() => {
+ this.collapsed = !this.collapsed;
+ });
- menuCallback = (x: number, y: number) => {
+ menuCallback = () => {
ContextMenu.Instance.clearItems();
const layoutItems: ContextMenuProps[] = [];
const docItems: ContextMenuProps[] = [];
@@ -235,7 +242,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
const height = this._ele ? DivHeight(this._ele) : 0;
DocUtils.addDocumentCreatorMenuItems(
doc => {
- FormattedTextBox.SetSelectOnLoad(doc);
+ Doc.SetSelectOnLoad(doc);
return this._props.addDocument?.(doc);
},
this._props.addDocument,
@@ -250,13 +257,14 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
docItems.push({
description: ':' + fieldKey,
event: () => {
- const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document));
+ const created = DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document));
if (created) {
if (this._props.Document.isTemplateDoc) {
Doc.MakeMetadataFieldTemplate(created, this._props.Document);
}
return this._props.addDocument?.(created);
}
+ return false;
},
icon: 'compress-arrows-alt',
})
@@ -276,6 +284,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
}
return this._props.addDocument?.(created) || false;
}
+ return false;
},
icon: 'compress-arrows-alt',
})
@@ -307,7 +316,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
const columnYMargin = this._props.headingObject ? 0 : this._props.yMargin;
const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
const noValueHeader = `NO ${key.toUpperCase()} VALUE`;
- const evContents = heading ? heading : this._props?.type === 'number' ? '0' : noValueHeader;
+ const evContents = heading || (this._props?.type === 'number' ? '0' : noValueHeader);
const headingView = this._props.headingObject ? (
<div
key={heading}
@@ -324,14 +333,19 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
onPointerDown={this.headerDown}
title={evContents === noValueHeader ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''}
style={{ background: evContents !== noValueHeader ? this._color : 'inherit' }}>
- <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} />
+ <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine />
<div className="collectionStackingView-sectionColor" style={{ display: evContents === noValueHeader ? 'none' : undefined }}>
- <button className="collectionStackingView-sectionColorButton" onClick={action(e => (this._paletteOn = !this._paletteOn))}>
+ <button
+ type="button"
+ className="collectionStackingView-sectionColorButton"
+ onClick={action(() => {
+ this._paletteOn = !this._paletteOn;
+ })}>
<FontAwesomeIcon icon="palette" size="lg" />
</button>
{this._paletteOn ? this.renderColorPicker() : null}
</div>
- <button className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}>
+ <button type="button" className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}>
<FontAwesomeIcon icon="trash" size="lg" />
</button>
{/* {evContents === noValueHeader ? null : (
@@ -352,7 +366,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
</div>
) : null;
const templatecols = `${this._props.columnWidth / this._props.numGroupColumns}px `;
- const type = this._props.Document.type;
+ const { type } = this._props.Document;
return (
<>
{this._props.Document._columnsHideIfEmpty ? null : headingView}
@@ -364,7 +378,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
style={{
padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`,
margin: 'auto',
- width: 'max-content', //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
+ width: 'max-content', // singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
height: 'max-content',
position: 'relative',
gridGap: this._props.gridGap,
@@ -376,10 +390,10 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
{!this._props.chromeHidden && type !== DocumentType.PRES ? (
// TODO: this is the "new" button: see what you can work with here
// change cursor to pointer for this, and update dragging cursor
- //TODO: there is a bug that occurs when adding a freeform document and trying to move it around
- //TODO: would be great if there was additional space beyond the frame, so that you can actually see your
+ // TODO: there is a bug that occurs when adding a freeform document and trying to move it around
+ // TODO: would be great if there was additional space beyond the frame, so that you can actually see your
// bottom note
- //TODO: ok, so we are using a single column, and this is it!
+ // TODO: ok, so we are using a single column, and this is it!
<div
key={`${heading}-add-document`}
onKeyDown={e => e.stopPropagation()}
@@ -390,7 +404,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
SetValue={this.addNewTextDoc}
textCallback={this.typedNote}
placeholder={"Type ':' for commands"}
- contents={<FontAwesomeIcon icon={'plus'} />}
+ contents={<FontAwesomeIcon icon="plus" />}
menuCallback={this.menuCallback}
/>
</div>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 32198e3a2..a4708bed5 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,37 +1,68 @@
import { action, computed, makeObservable, observable } from 'mobx';
import * as React from 'react';
import * as rp from 'request-promise';
-import { Utils, returnFalse } from '../../../Utils';
+import { ClientUtils, returnFalse } from '../../../ClientUtils';
import CursorField from '../../../fields/CursorField';
-import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
-import { AclPrivate, DocData } from '../../../fields/DocSymbols';
+import { Doc, DocListCast, GetDocFromUrl, GetHrefFromHTML, Opt, RTFIsFragment, StrListCast } from '../../../fields/Doc';
+import { AclPrivate } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
+import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
import { DocServer } from '../../DocServer';
import { Networking } from '../../Network';
+import { DocUtils } from '../../documents/DocUtils';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { ViewBoxBaseComponent } from '../DocComponent';
-import { DocUtils, Docs, DocumentOptions } from '../../documents/Documents';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { Docs, DocumentOptions } from '../../documents/Documents';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
-import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
import { UndoManager, undoBatch } from '../../util/UndoManager';
-import { LoadingBox } from '../nodes/LoadingBox';
-import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
-import { CollectionView, CollectionViewProps } from './CollectionView';
+import { ViewBoxBaseComponent } from '../DocComponent';
+import { FieldViewProps } from '../nodes/FieldView';
+import { DocumentView } from '../nodes/DocumentView';
+
+export interface CollectionViewProps extends React.PropsWithChildren<FieldViewProps> {
+ isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc)
+ isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently)
+ layoutEngine?: () => string;
+ setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => void;
+ ignoreUnrendered?: boolean;
+
+ // property overrides for child documents
+ childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
+ childDocumentsActive?: () => boolean | undefined; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
+ childContentsActive?: () => boolean | undefined;
+ childLayoutFitWidth?: (child: Doc) => boolean;
+ childlayout_showTitle?: () => string;
+ childOpacity?: () => number;
+ childContextMenuItems?: () => { script: ScriptField; label: string }[];
+ childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection
+ childHideDecorationTitle?: boolean;
+ childHideResizeHandles?: boolean;
+ childDragAction?: dropActionType;
+ childXPadding?: number;
+ childYPadding?: number;
+ childLayoutString?: string;
+ childIgnoreNativeSize?: boolean;
+ childClickScript?: ScriptField;
+ childDoubleClickScript?: ScriptField;
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
+ hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views)
+}
export interface SubCollectionViewProps extends CollectionViewProps {
isAnyChildContentActive: () => boolean;
}
-export function CollectionSubView<X>(moreProps?: X) {
- class CollectionSubView extends ViewBoxBaseComponent<X & SubCollectionViewProps>() {
+export function CollectionSubView<X>() {
+ class CollectionSubViewInternal extends ViewBoxBaseComponent<X & SubCollectionViewProps>() {
private dropDisposer?: DragManager.DragDropDisposer;
private gestureDisposer?: GestureUtils.GestureEventDisposer;
protected _mainCont?: HTMLDivElement;
@@ -53,7 +84,7 @@ export function CollectionSubView<X>(moreProps?: X) {
}
};
protected CreateDropTarget(ele: HTMLDivElement) {
- //used in schema view
+ // used in schema view
this.createDashEventsTarget(ele);
}
@@ -83,10 +114,11 @@ export function CollectionSubView<X>(moreProps?: X) {
const { Document, TemplateDataDocument } = this._props;
const validPairs = this.childDocs
.map(doc => Doc.GetLayoutDataDocPair(Document, !this._props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc))
- .filter(pair => {
- // filter out any documents that have a proto that we don't have permissions to
- return !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate));
- });
+ .filter(
+ pair =>
+ // filter out any documents that have a proto that we don't have permissions to
+ !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate))
+ );
return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
}
@computed get childDocList() {
@@ -95,9 +127,9 @@ export function CollectionSubView<X>(moreProps?: X) {
collectionFilters = () => this._focusFilters ?? StrListCast(this.Document._childFilters);
collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.Document._childFiltersByRanges, listSpec('string'), []);
// child filters apply to the descendants of the documents in this collection
- childDocFilters = () => [...(this._props.childFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
+ childDocFilters = () => [...(this._props.childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
// unrecursive filters apply to the documents in the collection, but no their children. See Utils.noRecursionHack
- unrecursiveDocFilters = () => [...(this._props.childFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])];
+ unrecursiveDocFilters = () => [...(this._props.childFilters?.().filter(f => !ClientUtils.IsRecursiveFilter(f)) || [])];
childDocRangeFilters = () => [...(this._props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()];
searchFilterDocs = () => this._props.searchFilterDocs?.() ?? DocListCast(this.Document._searchFilterDocs);
@computed.struct get childDocs() {
@@ -129,29 +161,30 @@ export function CollectionSubView<X>(moreProps?: X) {
const docsforFilter: Doc[] = [];
childDocs.forEach(d => {
// dragging facets
- const dragged = this._props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter));
- if (dragged && SnappingManager.CanEmbed && DragManager.docsBeingDragged.includes(d)) return false;
+ const dragged = this._props.childFilters?.().some(f => f.includes(ClientUtils.noDragDocsFilter));
+ if (dragged && SnappingManager.CanEmbed && DragManager.docsBeingDragged.includes(d)) return;
let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.Document).length > 0;
if (notFiltered) {
notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.Document).length > 0;
const fieldKey = Doc.LayoutFieldKey(d);
- const annos = !Field.toString(Doc.LayoutField(d) as Field).includes(CollectionView.name);
- const data = d[annos ? fieldKey + '_annotations' : fieldKey];
- const side = annos && d[fieldKey + '_sidebar'];
- if (data !== undefined || side !== undefined) {
- let subDocs = [...DocListCast(data), ...DocListCast(side)];
+ const isAnnotatableDoc = d[fieldKey] instanceof List && !(d[fieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc));
+ const docChildDocs = d[isAnnotatableDoc ? fieldKey + '_annotations' : fieldKey];
+ const sidebarDocs = isAnnotatableDoc && d[fieldKey + '_sidebar'];
+ if (docChildDocs !== undefined || sidebarDocs !== undefined) {
+ let subDocs = [...DocListCast(docChildDocs), ...DocListCast(sidebarDocs)];
if (subDocs.length > 0) {
let newarray: Doc[] = [];
notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, childDocFilters, childFiltersByRanges, d).length);
while (subDocs.length > 0 && !notFiltered) {
newarray = [];
+ // eslint-disable-next-line no-loop-func
subDocs.forEach(t => {
- const fieldKey = Doc.LayoutFieldKey(t);
- const annos = !Field.toString(Doc.LayoutField(t) as Field).includes(CollectionView.name);
+ const docFieldKey = Doc.LayoutFieldKey(t);
+ const isSubDocAnnotatable = t[docFieldKey] instanceof List && !(t[docFieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc));
notFiltered =
notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !childFiltersByRanges.length) || DocUtils.FilterDocs([t], childDocFilters, childFiltersByRanges, d).length));
- DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
- annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
+ DocListCast(t[isSubDocAnnotatable ? docFieldKey + '_annotations' : docFieldKey]).forEach(newdoc => newarray.push(newdoc));
+ isSubDocAnnotatable && DocListCast(t[docFieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
});
subDocs = newarray;
}
@@ -168,7 +201,7 @@ export function CollectionSubView<X>(moreProps?: X) {
let ind;
const doc = this.Document;
const id = Doc.UserDoc()[Id];
- const email = Doc.CurrentUserEmail;
+ const email = ClientUtils.CurrentUserEmail();
const pos = { x: position[0], y: position[1] };
if (id && email) {
const proto = Doc.GetProto(doc);
@@ -184,6 +217,7 @@ export function CollectionSubView<X>(moreProps?: X) {
if (!cursors) {
proto.cursors = cursors = new List<CursorField>();
}
+ // eslint-disable-next-line no-cond-assign
if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.data.metadata.id === id)) > -1) {
cursors[ind].setPosition(pos);
} else {
@@ -194,6 +228,7 @@ export function CollectionSubView<X>(moreProps?: X) {
}
@undoBatch
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {}
protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) {
@@ -212,12 +247,12 @@ export function CollectionSubView<X>(moreProps?: X) {
addDocument = (doc: Doc | Doc[], annotationKey?: string) => this._props.addDocument?.(doc, annotationKey) || false;
removeDocument = (doc: Doc | Doc[], annotationKey?: string) => this._props.removeDocument?.(doc, annotationKey) || false;
- moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this._props.moveDocument?.(doc, targetCollection, addDocument) || false;
+ moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean) => this._props.moveDocument?.(doc, targetCollection, addDocument) || false;
protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
- const docDragData = de.complete.docDragData;
+ const { docDragData } = de.complete;
if (docDragData) {
- let added = undefined;
+ let added;
const dropAction = docDragData.dropAction || docDragData.userDropAction;
const targetDocments = DocListCast(this.dataDoc[this._props.fieldKey]);
const someMoved = !dropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag));
@@ -245,8 +280,9 @@ export function CollectionSubView<X>(moreProps?: X) {
}
added === false && !this._props.isAnnotationOverlay && e.preventDefault();
added === true && e.stopPropagation();
- return added ? true : false;
- } else if (de.complete.annoDragData) {
+ return !!added;
+ }
+ if (de.complete.annoDragData) {
const dropCreator = de.complete.annoDragData.dropDocCreator;
de.complete.annoDragData.dropDocCreator = () => {
const dropped = dropCreator(this._props.isAnnotationOverlay ? this.Document : undefined);
@@ -280,10 +316,10 @@ export function CollectionSubView<X>(moreProps?: X) {
const addDocument = (doc: Doc | Doc[]) => this.addDocument(doc);
if (html) {
- if (FormattedTextBox.IsFragment(html)) {
- const href = FormattedTextBox.GetHref(html);
+ if (RTFIsFragment(html)) {
+ const href = GetHrefFromHTML(html);
if (href) {
- const docId = FormattedTextBox.GetDocFromUrl(href);
+ const docId = GetDocFromUrl(href);
if (docId) {
// prosemirror text containing link to dash document
DocServer.GetRefField(docId).then(f => {
@@ -316,7 +352,7 @@ export function CollectionSubView<X>(moreProps?: X) {
const result = (await Networking.PostToServer('/uploadRemoteImage', { sources: [imgSrc] })).lastElement();
const newImgSrc =
result.accessPaths.agnostic.client.indexOf('dashblobstore') === -1 //
- ? Utils.prepend(result.accessPaths.agnostic.client)
+ ? ClientUtils.prepend(result.accessPaths.agnostic.client)
: result.accessPaths.agnostic.client;
addDocument(ImageUtils.AssignImgInfo(Docs.Create.ImageDocument(newImgSrc, imgOpts), result));
@@ -325,39 +361,39 @@ export function CollectionSubView<X>(moreProps?: X) {
addDocument(ImageUtils.AssignImgInfo(doc, await ImageUtils.ExtractImgInfo(doc)));
}
return;
+ }
+ const path = window.location.origin + '/doc/';
+ if (text.startsWith(path)) {
+ const docId = text.replace(Doc.globalServerPath(), '').split('?')[0];
+ DocServer.GetRefField(docId).then(f => {
+ const fDoc = f;
+ if (fDoc instanceof Doc) {
+ if (options.x || options.y) {
+ fDoc.x = options.x as number;
+ fDoc.y = options.y as number;
+ } // should be in CollectionFreeFormView
+ addDocument(fDoc);
+ }
+ });
} else {
- const path = window.location.origin + '/doc/';
- if (text.startsWith(path)) {
- const docId = text.replace(Doc.globalServerPath(), '').split('?')[0];
- DocServer.GetRefField(docId).then(f => {
- if (f instanceof Doc) {
- if (options.x || options.y) {
- f.x = options.x as number;
- f.y = options.y as number;
- } // should be in CollectionFreeFormView
- f instanceof Doc && addDocument(f);
- }
- });
- } else {
- const srcWeb = SelectionManager.Views.lastElement();
- const srcUrl = (srcWeb?.Document.data as WebField)?.url?.href?.match(/https?:\/\/[^/]*/)?.[0];
- const reg = new RegExp(Utils.prepend(''), 'g');
- const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
- const backgroundColor = tags.map(tag => tag.match(/.*(background-color: ?[^;]*)/)?.[1]?.replace(/background-color: ?(.*)/, '$1')).filter(t => t)?.[0];
- const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: srcUrl ? 'from:' + srcUrl : '-web clip-', _width: 300, _height: 300, backgroundColor });
- Doc.GetProto(htmlDoc)['data-text'] = Doc.GetProto(htmlDoc).text = text;
- addDocument(htmlDoc);
- if (srcWeb) {
- const iframe = SelectionManager.Views[0].ContentDiv?.getElementsByTagName('iframe')?.[0];
- const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any;
- if (focusNode) {
- const anchor = srcWeb?.ComponentView?.getAnchor?.(true);
- anchor && DocUtils.MakeLink(htmlDoc, anchor, {});
- }
+ const srcWeb = DocumentView.Selected().lastElement();
+ const srcUrl = (srcWeb?.Document.data as WebField)?.url?.href?.match(/https?:\/\/[^/]*/)?.[0];
+ const reg = new RegExp(ClientUtils.prepend(''), 'g');
+ const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
+ const backgroundColor = tags.map(tag => tag.match(/.*(background-color: ?[^;]*)/)?.[1]?.replace(/background-color: ?(.*)/, '$1')).filter(t => t)?.[0];
+ const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: srcUrl ? 'from:' + srcUrl : '-web clip-', _width: 300, _height: 300, backgroundColor });
+ Doc.GetProto(htmlDoc)['data-text'] = Doc.GetProto(htmlDoc).text = text;
+ addDocument(htmlDoc);
+ if (srcWeb) {
+ const iframe = DocumentView.Selected()[0].ContentDiv?.getElementsByTagName('iframe')?.[0];
+ const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any;
+ if (focusNode) {
+ const anchor = srcWeb?.ComponentView?.getAnchor?.(true);
+ anchor && DocUtils.MakeLink(htmlDoc, anchor, {});
}
}
- return;
}
+ return;
}
}
@@ -414,10 +450,15 @@ export function CollectionSubView<X>(moreProps?: X) {
for (let i = 0; i < length; i++) {
const item = e.dataTransfer.items[i];
if (item.kind === 'string' && item.type.includes('uri')) {
- const stringContents = await new Promise<string>(resolve => item.getAsString(resolve));
- const type = (await rp.head(Utils.CorsProxy(stringContents)))['content-type'];
+ // eslint-disable-next-line no-await-in-loop
+ const stringContents = await new Promise<string>(resolve => {
+ item.getAsString(resolve);
+ });
+ // eslint-disable-next-line no-await-in-loop
+ const type = (await rp.head(ClientUtils.CorsProxy(stringContents)))['content-type'];
if (type) {
- const doc = await DocUtils.DocumentFromType(type, Utils.CorsProxy(stringContents), options);
+ // eslint-disable-next-line no-await-in-loop
+ const doc = await DocUtils.DocumentFromType(type, ClientUtils.CorsProxy(stringContents), options);
doc && generatedDocuments.push(doc);
}
}
@@ -426,7 +467,7 @@ export function CollectionSubView<X>(moreProps?: X) {
file?.type && files.push(file);
file?.type === 'application/json' &&
- Utils.readUploadedFileAsText(file).then(result => {
+ ClientUtils.readUploadedFileAsText(file).then(result => {
const json = JSON.parse(result as string);
addDocument(
Docs.Create.TreeDocument(
@@ -450,17 +491,17 @@ export function CollectionSubView<X>(moreProps?: X) {
// create placeholder docs
// inside placeholder docs have some func that
- let pileUpDoc = undefined;
+ let pileUpDoc;
if (typeof files === 'string') {
const loading = Docs.Create.LoadingDocument(files, options);
generatedDocuments.push(loading);
- LoadingBox.addCurrentlyLoading(loading);
+ Doc.addCurrentlyLoading(loading);
DocUtils.uploadYoutubeVideoLoading(files, {}, loading);
} else {
generatedDocuments.push(
...files.map(file => {
const loading = Docs.Create.LoadingDocument(file, options);
- LoadingBox.addCurrentlyLoading(loading);
+ Doc.addCurrentlyLoading(loading);
DocUtils.uploadFileToDoc(file, {}, loading);
return loading;
})
@@ -478,23 +519,19 @@ export function CollectionSubView<X>(moreProps?: X) {
})
: [];
if (completed) completed(set);
- else {
- if (isFreeformView && generatedDocuments.length > 1) {
- pileUpDoc = DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!;
- addDocument(pileUpDoc);
- } else {
- generatedDocuments.forEach(addDocument);
- }
- }
- } else {
- if (text && !text.includes('https://')) {
- addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 }));
+ else if (isFreeformView && generatedDocuments.length > 1) {
+ pileUpDoc = DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!;
+ addDocument(pileUpDoc);
} else {
- alert('Document upload failed - possibly an unsupported file type.');
+ generatedDocuments.forEach(addDocument);
}
+ } else if (text && !text.includes('https://')) {
+ addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 }));
+ } else {
+ alert('Document upload failed - possibly an unsupported file type.');
}
};
}
- return CollectionSubView;
+ return CollectionSubViewInternal;
}
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 37452ddfb..0369e4a2a 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,7 +1,10 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
+import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc, Opt, StrListCast } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
@@ -9,14 +12,12 @@ import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { Docs } from '../../documents/Documents';
-import { DocumentManager } from '../../util/DocumentManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { FieldsDropdown } from '../FieldsDropdown';
+import { PinDocView } from '../PinFuncs';
import { DocumentView } from '../nodes/DocumentView';
-import { FocusViewOptions } from '../nodes/FieldView';
-import { PresBox } from '../nodes/trails';
import { CollectionSubView } from './CollectionSubView';
import './CollectionTimeView.scss';
import { ViewDefBounds, computePivotLayout, computeTimelineLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
@@ -53,7 +54,7 @@ export class CollectionTimeView extends CollectionSubView() {
title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any,
annotationOn: this.Document,
});
- PresBox.pinDocView(anchor, { pinData: { type_collection: true, pivot: true, filters: true } }, this.Document);
+ 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
@@ -67,7 +68,7 @@ export class CollectionTimeView extends CollectionSubView() {
};
@action
- scrollPreview = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: FocusViewOptions) => {
+ scrollPreview = (docView: DocumentView, anchor: Doc /* , focusSpeed: number, options: FocusViewOptions */) => {
// if in preview, then override document's fields with view spec
this._focusFilters = StrListCast(anchor.config_docFilters);
this._focusRangeFilters = StrListCast(anchor.config_docRangeFilters);
@@ -76,13 +77,15 @@ export class CollectionTimeView extends CollectionSubView() {
};
layoutEngine = () => this._layoutEngine;
- toggleVisibility = action(() => (this._collapsed = !this._collapsed));
+ toggleVisibility = action(() => {
+ this._collapsed = !this._collapsed;
+ });
onMinDown = (e: React.PointerEvent) => {
setupMoveUpEvents(
this,
e,
- action((e: PointerEvent, down: number[], delta: number[]) => {
+ action((moveEv: PointerEvent, down: number[], delta: number[]) => {
const minReq = NumCast(this.Document[this._props.fieldKey + '-timelineMinReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMin'], 0));
const maxReq = NumCast(this.Document[this._props.fieldKey + '-timelineMaxReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMax'], 10));
this.Document[this._props.fieldKey + '-timelineMinReq'] = minReq + ((maxReq - minReq) * delta[0]) / this._props.PanelWidth();
@@ -98,7 +101,7 @@ export class CollectionTimeView extends CollectionSubView() {
setupMoveUpEvents(
this,
e,
- action((e: PointerEvent, down: number[], delta: number[]) => {
+ action((moveEv, down: number[], delta: number[]) => {
const minReq = NumCast(this.Document[this._props.fieldKey + '-timelineMinReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMin'], 0));
const maxReq = NumCast(this.Document[this._props.fieldKey + '-timelineMaxReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMax'], 10));
this.Document[this._props.fieldKey + '-timelineMaxReq'] = maxReq + ((maxReq - minReq) * delta[0]) / this._props.PanelWidth();
@@ -113,7 +116,7 @@ export class CollectionTimeView extends CollectionSubView() {
setupMoveUpEvents(
this,
e,
- action((e: PointerEvent, down: number[], delta: number[]) => {
+ action((moveEv: PointerEvent, down: number[], delta: number[]) => {
const minReq = NumCast(this.Document[this._props.fieldKey + '-timelineMinReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMin'], 0));
const maxReq = NumCast(this.Document[this._props.fieldKey + '-timelineMaxReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMax'], 10));
this.Document[this._props.fieldKey + '-timelineMinReq'] = minReq - ((maxReq - minReq) * delta[0]) / this._props.PanelWidth();
@@ -133,7 +136,7 @@ export class CollectionTimeView extends CollectionSubView() {
};
@action
- contentsDown = (e: React.MouseEvent) => {
+ contentsDown = () => {
const prevFilterIndex = NumCast(this.layoutDoc._prevFilterIndex);
if (prevFilterIndex > 0) {
this.goTo(prevFilterIndex - 1);
@@ -146,6 +149,7 @@ export class CollectionTimeView extends CollectionSubView() {
return (
<div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this._props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}>
<CollectionFreeFormView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
engineProps={{ pivotField: this.pivotField, childFilters: this.childDocFilters, childFiltersByRanges: this.childDocRangeFilters }}
fitContentsToBox={returnTrue}
@@ -161,7 +165,7 @@ export class CollectionTimeView extends CollectionSubView() {
const fieldKey = Doc.LayoutFieldKey(doc);
doc[fieldKey + '-timelineCur'] = ComputedField.MakeFunction("(activePresentationItem()[this._pivotField || 'year'] || 0)");
}
- specificMenu = (e: React.MouseEvent) => {
+ specificMenu = () => {
const layoutItems: ContextMenuProps[] = [];
const doc = this.layoutDoc;
@@ -193,9 +197,9 @@ export class CollectionTimeView extends CollectionSubView() {
render() {
let nonNumbers = 0;
- this.childDocs.map(doc => {
+ this.childDocs.forEach(doc => {
const num = NumCast(doc[this.pivotField], Number(StrCast(doc[this.pivotField])));
- if (Number.isNaN(num)) {
+ if (isNaN(num)) {
nonNumbers++;
}
});
@@ -225,13 +229,20 @@ export class CollectionTimeView extends CollectionSubView() {
</>
)}
<div style={{ right: 0, top: 0, position: 'absolute' }}>
- <FieldsDropdown Document={this.Document} selectFunc={fieldKey => (this.layoutDoc._pivotField = fieldKey)} placeholder={StrCast(this.layoutDoc._pivotField)} />
+ <FieldsDropdown
+ Document={this.Document}
+ selectFunc={fieldKey => {
+ this.layoutDoc._pivotField = fieldKey;
+ }}
+ placeholder={StrCast(this.layoutDoc._pivotField)}
+ />
</div>
</div>
);
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
const pivotField = StrCast(pivotDoc._pivotField, 'author');
let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
@@ -245,9 +256,10 @@ ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBou
action(() => {
const filterVals = bounds.payload as string[];
filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, pivotField, filterVal, 'check'));
- const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc);
+ const pivotView = DocumentView.getDocumentView(pivotDoc);
if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) {
if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) {
+ // eslint-disable-next-line prefer-destructuring
pivotDoc._pivotField = filterVals[0];
}
}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 5741fc29b..c1247f5b0 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,19 +1,22 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { DivHeight, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../ClientUtils';
import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { DivHeight, emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero, Utils } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
-import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { emptyFunction, Utils } from '../../../Utils';
+import { Docs } from '../../documents/Documents';
+import { DocUtils } from '../../documents/DocUtils';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from '../../util/UndoManager';
@@ -21,13 +24,14 @@ import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { EditableView } from '../EditableView';
import { DocumentView } from '../nodes/DocumentView';
-import { FieldViewProps } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
import { CollectionFreeFormView } from './collectionFreeForm';
import { CollectionSubView } from './CollectionSubView';
import './CollectionTreeView.scss';
+import { TreeViewType } from './CollectionTreeViewType';
import { TreeView } from './TreeView';
+
const _global = (window /* browser */ || global) /* node */ as any;
export type collectionTreeViewProps = {
@@ -44,12 +48,6 @@ export type collectionTreeViewProps = {
hierarchyIndex?: number[];
};
-export enum TreeViewType {
- outline = 'outline',
- fileSystem = 'fileSystem',
- default = 'default',
-}
-
@observer
export class CollectionTreeView extends CollectionSubView<Partial<collectionTreeViewProps>>() {
public static AddTreeFunc = 'addTreeFolder(this.embedContainer)';
@@ -92,8 +90,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
// these should stay in synch with counterparts in DocComponent.ts ViewBoxAnnotatableComponent
@observable _isAnyChildContentActive = false;
- whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
- isContentActive = (outsideReaction?: boolean) => (this._isAnyChildContentActive ? true : this._props.isContentActive() ? true : false);
+ whenChildContentsActiveChanged = action((isActive: boolean) => {
+ this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
+ });
+ isContentActive = () => (this._isAnyChildContentActive ? true : !!this._props.isContentActive());
componentWillUnmount() {
this._isDisposing = true;
@@ -103,7 +103,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
}
componentDidMount() {
- //this._props.setContentView?.(this);
+ // this._props.setContentView?.(this);
this._disposers.autoheight = reaction(
() => this.layoutDoc.layout_autoHeight,
auto => auto && this.computeHeight(),
@@ -126,20 +126,19 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
observeHeight = (ref: any) => {
if (ref) {
this.refList.add(ref);
- this.observer = new _global.ResizeObserver(
- action((entries: any) => {
- if (this.layoutDoc.layout_autoHeight && ref && this.refList.size && !SnappingManager.IsDragging) {
- this.computeHeight();
- }
- })
- );
+ this.observer = new _global.ResizeObserver(() => {
+ if (this.layoutDoc.layout_autoHeight && ref && this.refList.size && !SnappingManager.IsDragging) {
+ this.computeHeight();
+ }
+ });
this.layoutDoc.layout_autoHeight && this.computeHeight();
this.observer.observe(ref);
}
};
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this._treedropDisposer?.();
- if ((this._mainEle = ele)) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document, this.onInternalPreDrop.bind(this));
+ this._mainEle = ele;
+ if (ele) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document, this.onInternalPreDrop.bind(this));
};
protected onInternalDrop(e: Event, de: DragManager.DropEvent) {
@@ -167,39 +166,39 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
}
};
- dragConfig = (dragData: DragManager.DocumentDragData) => (dragData.treeViewDoc = this.Document);
+ dragConfig = (dragData: DragManager.DocumentDragData) => { dragData.treeViewDoc = this.Document; }; // prettier-ignore
screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, -this._headerHeight);
@action
- remove = (doc: Doc | Doc[]): boolean => {
- const docs = doc instanceof Doc ? [doc] : doc;
+ remove = (docIn: Doc | Doc[]): boolean => {
+ const docs = toList(docIn);
const targetDataDoc = this.Document[DocData];
const value = DocListCast(targetDataDoc[this._props.fieldKey]);
const result = value.filter(v => !docs.includes(v));
- if ((doc instanceof Doc ? [doc] : doc).some(doc => SelectionManager.Views.some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll();
+ if (docs.some(doc => DocumentView.Selected().some(dv => Doc.AreProtosEqual(dv.Document, doc)))) DocumentView.DeselectAll();
if (result.length !== value.length) {
- if (doc instanceof Doc) {
- const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(doc);
+ if (docIn instanceof Doc) {
+ const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(docIn);
const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1];
- this._props.removeDocument?.(doc);
+ this._props.removeDocument?.(docIn);
if (ind > 0 && prev) {
- FormattedTextBox.SetSelectOnLoad(prev);
- DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false);
+ Doc.SetSelectOnLoad(prev);
+ DocumentView.getDocumentView(prev, this.DocumentView?.())?.select(false);
}
return true;
}
- return this._props.removeDocument?.(doc) ?? false;
+ return this._props.removeDocument?.(docIn) ?? false;
}
return false;
};
@action
addDoc = (docs: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => {
- const doclist = docs instanceof Doc ? [docs] : docs;
- const addDocRelativeTo = (doc: Doc | Doc[]) => doclist.reduce((flg, doc) => flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before), true);
+ const addDocRelativeTo = (adocs: Doc | Doc[]) => (adocs as Doc[]).reduce((flg, doc) => flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before), true);
if (this.Document.resolvedDataDoc instanceof Promise) return false;
- const res = relativeTo === undefined ? this._props.addDocument?.(docs) || false : addDocRelativeTo(docs);
+ const doclist = toList(docs);
+ const res = relativeTo === undefined ? this._props.addDocument?.(doclist) || false : addDocRelativeTo(doclist);
res &&
doclist.forEach(doc => {
Doc.SetContainer(doc, this.Document);
@@ -207,7 +206,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
});
return res;
};
- onContextMenu = (e: React.MouseEvent): void => {
+ onContextMenu = (): void => {
// need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
const layoutItems: ContextMenuProps[] = [];
const menuDoc = ScriptCast(Cast(this.layoutDoc.layout_headerButton, Doc, null)?.onClick).script.originalScript === CollectionTreeView.AddTreeFunc;
@@ -215,11 +214,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
if (!Doc.noviceMode) {
layoutItems.push({
description: 'Make tree state ' + (this.Document.treeView_OpenIsTransient ? 'persistent' : 'transient'),
- event: () => (this.Document.treeView_OpenIsTransient = !this.Document.treeView_OpenIsTransient),
+ event: () => { this.Document.treeView_OpenIsTransient = !this.Document.treeView_OpenIsTransient; }, // prettier-ignore
icon: 'paint-brush',
});
- layoutItems.push({ description: (this.Document.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => (this.Document.treeView_HideHeaderFields = !this.Document.treeView_HideHeaderFields), icon: 'paint-brush' });
- layoutItems.push({ description: (this.Document.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.Document.treeView_HideTitle = !this.Document.treeView_HideTitle), icon: 'paint-brush' });
+ layoutItems.push({ description: (this.Document.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => { this.Document.treeView_HideHeaderFields = !this.Document.treeView_HideHeaderFields; }, icon: 'paint-brush' }); // prettier-ignore
+ layoutItems.push({ description: (this.Document.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => { this.Document.treeView_HideTitle = !this.Document.treeView_HideTitle; }, icon: 'paint-brush' }); // prettier-ignore
}
ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' });
if (!Doc.noviceMode) {
@@ -238,9 +237,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
return (
<EditableView
contents={this.dataDoc.title}
- display={'block'}
+ display="block"
maxHeight={72}
- height={'auto'}
+ height="auto"
GetValue={() => StrCast(this.dataDoc.title)}
SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => {
if (enter && this.Document.treeView_Type === TreeViewType.outline) this.makeTextCollection(this.treeChildren);
@@ -251,22 +250,24 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
);
}
- onKey = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
+ onKey = (e: React.KeyboardEvent /* , fieldProps: FieldViewProps */) => {
if (this.outlineMode && e.key === 'Enter') {
e.stopPropagation();
this.makeTextCollection(this.treeChildren);
return true;
}
+ return undefined;
};
get documentTitle() {
return (
<FormattedTextBox
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
fieldKey="text"
renderDepth={this._props.renderDepth + 1}
isContentActive={this.isContentActive}
isDocumentActive={this.isContentActive}
- forceAutoHeight={true} // needed to make the title resize even if the rest of the tree view is not layout_autoHeight
+ forceAutoHeight // needed to make the title resize even if the rest of the tree view is not layout_autoHeight
PanelWidth={this.documentTitleWidth}
PanelHeight={this.documentTitleHeight}
NativeDimScaling={returnOne}
@@ -292,9 +293,14 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
@computed get treeViewElements() {
TraceMobx();
const dragAction = StrCast(this.Document.childDragAction) as any as dropActionType;
- const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
+ const treeAddDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this._props.moveDocument?.(d, target, addDoc) || false;
- if (this._renderCount < this.treeChildren.length) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20))));
+ if (this._renderCount < this.treeChildren.length)
+ setTimeout(
+ action(() => {
+ this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20);
+ })
+ );
return TreeView.GetChildElements(
this.treeChildren,
this,
@@ -303,7 +309,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
this._props.TemplateDataDocument,
undefined,
undefined,
- addDoc,
+ treeAddDoc,
this.remove,
moveDoc,
dragAction,
@@ -324,7 +330,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
this.observeHeight,
this.unobserveHeight,
this.childContextMenuItems(),
- //TODO: [AL] add these
this._props.AddToMap,
this._props.RemFromMap,
this._props.hierarchyIndex,
@@ -335,7 +340,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
return this.dataDoc === null ? null : (
<div
className="collectionTreeView-titleBar"
- ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.ScreenToLocalBoxXf().Scale))}
+ ref={action((r: any) => {
+ (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.ScreenToLocalBoxXf().Scale);
+ })}
key={this.Document[Id]}
style={!this.outlineMode ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}}>
{this.outlineMode ? this.documentTitle : this.editableTitle}
@@ -406,8 +413,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
addAnnotationDocument = (doc: Doc | Doc[]) => this.addDocument(doc, `${this._props.fieldKey}_annotations`) || false;
remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this._props.fieldKey}_annotations`) || false;
- moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) =>
- this.moveDocument(doc, targetCollection, addDocument, `${this._props.fieldKey}_annotations`) || false;
+ moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => this.moveDocument(doc, targetCollection, addDocument) || false;
@observable _headerHeight = 0;
@computed get content() {
@@ -418,7 +424,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', pointerEvents: 'all' }}>
{!this.buttonMenu && !this.noviceExplainer ? null : (
- <div className="documentButtonMenu" ref={action((r: HTMLDivElement | null) => r && (this._headerHeight = DivHeight(r)))}>
+ <div
+ className="documentButtonMenu"
+ ref={action((r: HTMLDivElement | null) => {
+ r && (this._headerHeight = DivHeight(r));
+ })}>
{this.buttonMenu}
{this.noviceExplainer}
</div>
@@ -451,7 +461,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
minHeight: '100%',
}}
onWheel={e => e.stopPropagation()}
- onClick={e => (!this.layoutDoc.forceActive ? this._props.select(false) : SelectionManager.DeselectAll())}
+ onClick={() => (!this.layoutDoc.forceActive ? this._props.select(false) : DocumentView.DeselectAll())}
onDrop={this.onTreeDrop}>
<ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul>
</div>
@@ -468,13 +478,14 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
<div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
{!(this.Document instanceof Doc) || !this.treeChildren ? null : this.Document.treeView_HasOverlay ? (
<CollectionFreeFormView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
pointerEvents={this._props.isContentActive() && SnappingManager.IsDragging ? returnAll : returnNone}
- isAnnotationOverlay={true}
- isAnnotationOverlayScrollable={true}
+ isAnnotationOverlay
+ isAnnotationOverlayScrollable
childDocumentsActive={this._props.isContentActive}
fieldKey={this._props.fieldKey + '_annotations'}
dropAction={dropActionType.move}
@@ -498,6 +509,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function addTreeFolder(doc: Doc) {
CollectionTreeView.addTreeFolder(doc);
});
diff --git a/src/client/views/collections/CollectionTreeViewType.ts b/src/client/views/collections/CollectionTreeViewType.ts
new file mode 100644
index 000000000..53b88160e
--- /dev/null
+++ b/src/client/views/collections/CollectionTreeViewType.ts
@@ -0,0 +1,5 @@
+export enum TreeViewType {
+ outline = 'outline',
+ fileSystem = 'fileSystem',
+ default = 'default',
+}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 18eb4dd1f..7cadd072b 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,21 +1,21 @@
+/* eslint-disable react/jsx-props-no-spreading */
import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { returnEmptyString } from '../../../Utils';
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { returnEmptyString } from '../../../ClientUtils';
+import { Doc, DocListCast } from '../../../fields/Doc';
import { ObjectField } from '../../../fields/ObjectField';
-import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { CollectionViewType } from '../../documents/DocumentTypes';
-import { DocUtils } from '../../documents/Documents';
-import { dropActionType } from '../../util/DragManager';
+import { DocUtils } from '../../documents/DocUtils';
+import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
-import { OpenWhere } from '../nodes/DocumentView';
-import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import { FieldView } from '../nodes/FieldView';
+import { OpenWhere } from '../nodes/OpenWhere';
import { CollectionCalendarView } from './CollectionCalendarView';
import { CollectionCarousel3DView } from './CollectionCarousel3DView';
import { CollectionCarouselView } from './CollectionCarouselView';
@@ -23,7 +23,7 @@ import { CollectionDockingView } from './CollectionDockingView';
import { CollectionNoteTakingView } from './CollectionNoteTakingView';
import { CollectionPileView } from './CollectionPileView';
import { CollectionStackingView } from './CollectionStackingView';
-import { SubCollectionViewProps } from './CollectionSubView';
+import { CollectionViewProps, SubCollectionViewProps } from './CollectionSubView';
import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from './CollectionTreeView';
import './CollectionView.scss';
@@ -33,36 +33,7 @@ import { CollectionLinearView } from './collectionLinear';
import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView';
-export interface CollectionViewProps extends React.PropsWithChildren<FieldViewProps> {
- isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc)
- isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently)
- layoutEngine?: () => string;
- setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => void;
- ignoreUnrendered?: boolean;
- // property overrides for child documents
- childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
- childDocumentsActive?: () => boolean | undefined; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
- childContentsActive?: () => boolean | undefined;
- childLayoutFitWidth?: (child: Doc) => boolean;
- childlayout_showTitle?: () => string;
- childOpacity?: () => number;
- childContextMenuItems?: () => { script: ScriptField; label: string }[];
- childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection
- childHideDecorationTitle?: boolean;
- childHideResizeHandles?: boolean;
- childDragAction?: dropActionType;
- childXPadding?: number;
- childYPadding?: number;
- childLayoutString?: string;
- childIgnoreNativeSize?: boolean;
- childClickScript?: ScriptField;
- childDoubleClickScript?: ScriptField;
- //TODO: [AL] add these fields
- AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
- RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
- hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views)
-}
@observer
export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewProps>() implements ViewBoxInterface {
public static LayoutString(fieldStr: string) {
@@ -89,7 +60,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
// this a reaction, downstream invalidations only occur when the reaction value actually changes.
this.reactionDisposer = reaction(
() => (this.isAnyChildContentActive() ? true : this._props.isContentActive()),
- active => (this._isContentActive = active),
+ active => {
+ this._isContentActive = active;
+ },
{ fireImmediately: true }
);
}
@@ -98,17 +71,16 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
}
get collectionViewType(): CollectionViewType | undefined {
- const viewField = StrCast(this.layoutDoc._type_collection);
+ const viewField = StrCast(this.layoutDoc._type_collection) as any as CollectionViewType;
if (CollectionView._safeMode) {
- switch (viewField) {
+ switch (viewField) {
case CollectionViewType.Freeform:
- case CollectionViewType.Schema:
- return CollectionViewType.Tree;
- case CollectionViewType.Invalid:
- return CollectionViewType.Freeform;
- }
+ case CollectionViewType.Schema: return CollectionViewType.Tree;
+ case CollectionViewType.Invalid: return CollectionViewType.Freeform;
+ default:
+ } // prettier-ignore
}
- return viewField as any as CollectionViewType;
+ return viewField;
}
screenToLocalTransform = () => (this._props.renderDepth ? this.ScreenToLocalBoxXf() : this.ScreenToLocalBoxXf().scale(this._props.PanelWidth() / this.bodyPanelWidth()));
@@ -117,8 +89,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
TraceMobx();
if (type === undefined) return null;
switch (type) {
- default:
- case CollectionViewType.Freeform: return <CollectionFreeFormView key="collview" {...props} />;
case CollectionViewType.Schema: return <CollectionSchemaView key="collview" {...props} />;
case CollectionViewType.Calendar: return <CollectionCalendarView key="collview" {...props} />;
case CollectionViewType.Docking: return <CollectionDockingView key="collview" {...props} />;
@@ -134,6 +104,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
case CollectionViewType.Masonry: return <CollectionStackingView key="collview" {...props} />;
case CollectionViewType.Time: return <CollectionTimeView key="collview" {...props} />;
case CollectionViewType.Grid: return <CollectionGridView key="collview" {...props} />;
+ case CollectionViewType.Freeform:
+ default: return <CollectionFreeFormView key="collview" {...props} />;
}
};
@@ -144,9 +116,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
{ description: 'Freeform', event: () => func(CollectionViewType.Freeform), icon: 'signature' },
{ description: 'Schema', event: () => func(CollectionViewType.Schema), icon: 'th-list' },
{ description: 'Tree', event: () => func(CollectionViewType.Tree), icon: 'tree' },
- { description: 'Stacking', event: () => (func(CollectionViewType.Stacking)._layout_autoHeight = true), icon: 'ellipsis-v' },
+ { description: 'Stacking', event: () => {func(CollectionViewType.Stacking)._layout_autoHeight = true}, icon: 'ellipsis-v' },
{ description: 'Calendar', event: () => func(CollectionViewType.Calendar), icon: 'columns'},
- { description: 'Notetaking', event: () => (func(CollectionViewType.NoteTaking)._layout_autoHeight = true), icon: 'ellipsis-v' },
+ { description: 'Notetaking', event: () => {func(CollectionViewType.NoteTaking)._layout_autoHeight = true}, icon: 'ellipsis-v' },
{ description: 'Multicolumn', event: () => func(CollectionViewType.Multicolumn), icon: 'columns' },
{ description: 'Multirow', event: () => func(CollectionViewType.Multirow), icon: 'columns' },
{ description: 'Masonry', event: () => func(CollectionViewType.Masonry), icon: 'columns' },
@@ -178,14 +150,14 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
const options = cm.findByDescription('Options...');
const optionItems = options && 'subitems' in options ? options.subitems : [];
- !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.Document.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.Document.forceActive = !this.Document.forceActive), icon: 'project-diagram' }) : null;
+ !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.Document.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => {this.Document.forceActive = !this.Document.forceActive}, icon: 'project-diagram' }) : null; // prettier-ignore
if (this.Document.childLayout instanceof Doc) {
optionItems.push({ description: 'View Child Layout', event: () => this._props.addDocTab(this.Document.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' });
}
if (this.Document.childClickedOpenTemplateView instanceof Doc) {
optionItems.push({ description: 'View Child Detailed Layout', event: () => this._props.addDocTab(this.Document.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' });
}
- !Doc.noviceMode && optionItems.push({ description: `${this.layoutDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => (this.layoutDoc._isLightbox = !this.layoutDoc._isLightbox), icon: 'project-diagram' });
+ !Doc.noviceMode && optionItems.push({ description: `${this.layoutDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => { this.layoutDoc._isLightbox = !this.layoutDoc._isLightbox; }, icon: 'project-diagram' }); // prettier-ignore
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' });
@@ -200,7 +172,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
onClicks.push({
description: `Edit ${func.name} script`,
icon: 'edit',
- event: (obj: any) => {
+ event: () => {
const embedding = Doc.MakeEmbedding(this.Document);
DocUtils.makeCustomViewClicked(embedding, undefined, func.key);
this._props.addDocTab(embedding, OpenWhere.addRight);
@@ -211,7 +183,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
onClicks.push({
description: `Set child ${childClick.title}`,
icon: 'edit',
- event: () => (this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data))),
+ event: () => {
+ this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data));
+ },
})
);
!Doc.IsSystem(this.Document) && !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
@@ -229,7 +203,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
bodyPanelWidth = () => this._props.PanelWidth();
childLayoutTemplate = () => this._props.childLayoutTemplate?.() || Cast(this.Document.childLayoutTemplate, Doc, null);
- isContentActive = (outsideReaction?: boolean) => this._isContentActive;
+ isContentActive = () => this._isContentActive;
pointerEvents = () =>
this.layoutDoc._lockedPosition && //
@@ -261,3 +235,19 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
);
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.COL, {
+ layout: { view: CollectionView, dataField: 'data' },
+ options: {
+ acl: '',
+ _layout_fitWidth: true,
+ freeform: '',
+ _freeform_panX: 0,
+ _freeform_panY: 0,
+ _freeform_scale: 1,
+ _layout_nativeDimEditable: true,
+ _layout_reflowHorizontal: true,
+ _layout_reflowVertical: true,
+ systemIcon: 'BsFillCollectionFill',
+ },
+});
diff --git a/src/client/views/collections/KeyRestrictionRow.tsx b/src/client/views/collections/KeyRestrictionRow.tsx
index 4523a4f1e..7dc08389b 100644
--- a/src/client/views/collections/KeyRestrictionRow.tsx
+++ b/src/client/views/collections/KeyRestrictionRow.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/button-has-type */
import { observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -35,11 +36,36 @@ export default class KeyRestrictionRow extends React.Component<IKeyRestrictionPr
return (
<div className="collectionViewBaseChrome-viewSpecsMenu-row">
- <input className="collectionViewBaseChrome-viewSpecsMenu-rowLeft" value={this._key} onChange={e => runInAction(() => (this._key = e.target.value))} placeholder="KEY" />
- <button className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle" style={{ background: this._contains ? '#77dd77' : '#ff6961' }} onClick={() => runInAction(() => (this._contains = !this._contains))}>
+ <input
+ className="collectionViewBaseChrome-viewSpecsMenu-rowLeft"
+ value={this._key}
+ onChange={e =>
+ runInAction(() => {
+ this._key = e.target.value;
+ })
+ }
+ placeholder="KEY"
+ />
+ <button
+ className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
+ style={{ background: this._contains ? '#77dd77' : '#ff6961' }}
+ onClick={() =>
+ runInAction(() => {
+ this._contains = !this._contains;
+ })
+ }>
{this._contains ? 'CONTAINS' : 'DOES NOT CONTAIN'}
</button>
- <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight" value={this._value} onChange={e => runInAction(() => (this._value = e.target.value))} placeholder="VALUE" />
+ <input
+ className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
+ value={this._value}
+ onChange={e =>
+ runInAction(() => {
+ this._value = e.target.value;
+ })
+ }
+ placeholder="VALUE"
+ />
</div>
);
}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 5cb7b149b..2d8b2564d 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -6,40 +6,182 @@ import { IReactionDisposer, ObservableSet, action, computed, makeObservable, obs
import { observer } from 'mobx-react';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
-import { DashColor, Utils, emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../Utils';
+import { ClientUtils, DashColor, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
+import { DocData, DocViews } 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 { Cast, DocCast, NumCast, StrCast, toList } from '../../../fields/Types';
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 { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { UndoManager, undoable } from '../../util/UndoManager';
import { DashboardView } from '../DashboardView';
import { LightboxView } from '../LightboxView';
import { ObservableReactComponent } from '../ObservableReactComponent';
-import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
+import { PinDocView, PinProps } from '../PinFuncs';
+import { StyleProp } from '../StyleProp';
+import { DefaultStyleProvider } from '../StyleProvider';
import { Colors } from '../global/globalEnums';
-import { DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView';
-import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView';
+import { DocumentView, returnEmptyDocViewList } from '../nodes/DocumentView';
+import { FieldViewProps } from '../nodes/FieldView';
import { KeyValueBox } from '../nodes/KeyValueBox';
-import { DashFieldView } from '../nodes/formattedText/DashFieldView';
-import { PinProps, PresBox, PresMovement } from '../nodes/trails';
+import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere';
+import { PresBox, PresMovement } from '../nodes/trails';
import { CollectionDockingView } from './CollectionDockingView';
import { CollectionView } from './CollectionView';
import './TabDocView.scss';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+
const _global = (window /* browser */ || global) /* node */ as any;
+interface TabMinimapViewProps {
+ document: Doc;
+ tabView: () => DocumentView | undefined;
+ addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
+ background: () => string;
+}
+interface TabMiniThumbProps {
+ miniWidth: () => number;
+ miniHeight: () => number;
+ miniTop: () => number;
+ miniLeft: () => number;
+}
+
+@observer
+class TabMiniThumb extends React.Component<TabMiniThumbProps> {
+ render() {
+ const { miniWidth, miniHeight, miniLeft, miniTop } = this.props;
+ return <div className="miniThumb" style={{ width: `${miniWidth()}%`, height: `${miniHeight()}%`, left: `${miniLeft()}%`, top: `${miniTop()}%` }} />;
+ }
+}
+@observer
+export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps> {
+ static miniStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
+ if (doc) {
+ switch (property.split(':')[0]) {
+ case StyleProp.PointerEvents: return 'none';
+ case StyleProp.DocContents: {
+ const background = (() => {
+ switch (doc.type as DocumentType) {
+ case DocumentType.PDF: return 'pink';
+ case DocumentType.AUDIO: return 'lightgreen';
+ case DocumentType.WEB: return 'brown';
+ case DocumentType.IMG: return 'blue';
+ case DocumentType.MAP: return 'orange';
+ case DocumentType.VID: return 'purple';
+ case DocumentType.RTF: return 'yellow';
+ case DocumentType.COL: return undefined;
+ default: return 'gray';
+ } // prettier-ignore
+ })();
+ return !background ? undefined : <div style={{ width: NumCast(doc._width), height: NumCast(doc._height), position: 'absolute', display: 'block', background }} />;
+ }
+ default: return DefaultStyleProvider(doc, props, property);
+ } // prettier-ignore
+ }
+ return undefined;
+ };
+
+ @computed get renderBounds() {
+ const compView = this._props.tabView()?.ComponentView as CollectionFreeFormView;
+ const bounds = compView?.freeformData?.(true)?.bounds;
+ if (!bounds) return undefined;
+ const xbounds = bounds.r - bounds.x;
+ const ybounds = bounds.b - bounds.y;
+ const dim = Math.max(xbounds, ybounds);
+ return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim };
+ }
+ @computed get xPadding() {
+ return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cx - this.renderBounds.l));
+ }
+ @computed get yPadding() {
+ return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cy - this.renderBounds.l));
+ }
+ childLayoutTemplate = () => Cast(this._props.document.childLayoutTemplate, Doc, null);
+ returnMiniSize = () => NumCast(this._props.document._miniMapSize, 150);
+ miniDown = (e: React.PointerEvent) => {
+ const doc = this._props.document;
+ const miniSize = this.returnMiniSize();
+ doc &&
+ setupMoveUpEvents(
+ this,
+ e,
+ action((moveEv, down: number[], delta: number[]) => {
+ const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 };
+ doc._freeform_panX = clamp(NumCast(doc._freeform_panX) + (delta[0] / miniSize) * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim);
+ doc._freeform_panY = clamp(NumCast(doc._freeform_panY) + (delta[1] / miniSize) * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim);
+ return false;
+ }),
+ emptyFunction,
+ emptyFunction
+ );
+ };
+ popup = () => {
+ if (!this.renderBounds) return <div />;
+ const { renderBounds } = this;
+ const miniWidth = () => (this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
+ const miniHeight = () => (this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
+ const miniLeft = () => 50 + ((NumCast(this._props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2;
+ const miniTop = () => 50 + ((NumCast(this._props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2;
+ const miniSize = this.returnMiniSize();
+ return (
+ <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}>
+ <CollectionFreeFormView
+ Document={this._props.document}
+ docViewPath={returnEmptyDocViewList}
+ childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
+ noOverlay // don't render overlay Docs since they won't scale
+ isContentActive={emptyFunction}
+ isAnyChildContentActive={returnFalse}
+ select={emptyFunction}
+ isSelected={returnFalse}
+ dontRegisterView
+ fieldKey={Doc.LayoutFieldKey(this._props.document)}
+ addDocument={returnFalse}
+ moveDocument={returnFalse}
+ removeDocument={returnFalse}
+ PanelWidth={this.returnMiniSize}
+ PanelHeight={this.returnMiniSize}
+ ScreenToLocalTransform={Transform.Identity}
+ renderDepth={0}
+ whenChildContentsActiveChanged={emptyFunction}
+ focus={emptyFunction}
+ styleProvider={TabMinimapView.miniStyleProvider}
+ addDocTab={this._props.addDocTab}
+ // eslint-disable-next-line no-use-before-define
+ pinToPres={TabDocView.PinDoc}
+ childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
+ childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
+ searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
+ fitContentsToBox={returnTrue}
+ xPadding={this.xPadding}
+ yPadding={this.yPadding}
+ />
+ <div className="miniOverlay" onPointerDown={this.miniDown}>
+ <TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} />
+ </div>
+ </div>
+ );
+ };
+ render() {
+ return this._props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.document)) || this._props.document?._type_collection !== CollectionViewType.Freeform ? null : (
+ <div className="miniMap-hidden">
+ <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SnappingManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} />
+ </div>
+ );
+ }
+}
+
interface TabDocViewProps {
documentId: FieldId;
keyValue?: boolean;
@@ -51,9 +193,101 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
_mainCont: HTMLDivElement | null = null;
_tabReaction: IReactionDisposer | undefined;
+ /**
+ * Adds a document to the presentation view
+ * */
+ @action
+ public static PinDoc(docIn: Doc | Doc[], pinProps: PinProps) {
+ const docs = toList(docIn);
+
+ const batch = UndoManager.StartBatch('Pin doc to pres trail');
+ const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true);
+
+ if (!Doc.ActivePresentation) {
+ Doc.AddDocToList(Doc.MyTrails, 'data', curPres);
+ Doc.ActivePresentation = curPres;
+ }
+
+ docs.forEach(doc => {
+ // Edge Case 1: Cannot pin document to itself
+ if (doc === curPres) {
+ alert('Cannot pin presentation document to itself');
+ return;
+ }
+ const anchorDoc = DocumentView.getDocumentView(doc)?.ComponentView?.getAnchor?.(false, pinProps);
+ const pinDoc = anchorDoc?.type === DocumentType.CONFIG ? anchorDoc : Docs.Create.ConfigDocument({});
+ const targDoc = (pinDoc.presentation_targetDoc = anchorDoc ?? doc);
+ pinDoc.title = doc.title + ' - Slide';
+ 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;
+ Doc.SetContainer(pinDoc, curPres);
+ // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time
+ pinDoc.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_FieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
+ pinDoc.treeView_ExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
+ pinDoc.treeView_HideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
+ const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], null);
+
+ if (pinProps.pinViewport) PinDocView(pinDoc, pinProps, anchorDoc ?? doc);
+ if (!pinProps?.audioRange && duration !== undefined) {
+ pinDoc.presentation_mediaStart = 'manual';
+ pinDoc.presentation_mediaStop = 'manual';
+ }
+ if (pinProps?.activeFrame !== undefined) {
+ pinDoc.config_activeFrame = pinProps?.activeFrame;
+ pinDoc.title = doc.title + ' (move)';
+ pinDoc.presentation_movement = PresMovement.Pan;
+ }
+ if (pinProps?.currentFrame !== undefined) {
+ pinDoc.config_currentFrame = pinProps?.currentFrame;
+ pinDoc.title = doc.title + ' (move)';
+ pinDoc.presentation_movement = PresMovement.Pan;
+ }
+ if (pinDoc.stroke_isInkMask) {
+ pinDoc.presentation_hideAfter = true;
+ pinDoc.presentation_hideBefore = true;
+ pinDoc.presentation_movement = PresMovement.None;
+ }
+ if (curPres.expandBoolean) pinDoc.presentation_expandInlineButton = true;
+ Doc.AddDocToList(curPres, 'data', pinDoc, PresBox.Instance?.sortArray()?.lastElement());
+ PresBox.Instance?.clearSelectedArray();
+ pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); // Update selected array
+ });
+ if (
+ // open the presentation trail if it's not already opened
+ !Array.from(CollectionDockingView.Instance?.tabMap ?? [])
+ .map(d => d.DashDoc)
+ .includes(curPres)
+ ) {
+ if (Doc.IsInMyOverlay(curPres)) Doc.RemFromMyOverlay(curPres);
+ CollectionDockingView.AddSplit(curPres, OpenWhereMod.right);
+ setTimeout(() => DocumentView.showDocument(docs.lastElement(), { willPan: true }), 100); // keeps the pinned doc in view since the sidebar shifts things
+ }
+ setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs
+ }
+
+ static Activate = (tabDoc: Doc) => {
+ const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(findTab => findTab.DashDoc === tabDoc && !findTab.contentItem.config.props.keyValue);
+ tab?.header.parent.setActiveContentItem(tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
+ return tab !== undefined;
+ };
+ // static ActivateTabView(doc: Doc) {
+ // const tabView = Array.from(TabDocView._allTabs).find(view => view._document === doc);
+ // if (!tabView?._activated && tabView?._document) {
+ // TabDocView.Activate(tabView?._document);
+ // return tabView;
+ // }
+ // return undefined;
+ // }
constructor(props: any) {
super(props);
makeObservable(this);
+ DocumentView.activateTabView = TabDocView.Activate;
+ DocumentView.PinDoc = TabDocView.PinDoc;
}
@observable _activated: boolean = false;
@@ -62,8 +296,14 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
@observable _hovering = false;
@observable _isActive: boolean = false;
@observable _isAnyChildContentActive = false;
+ public static IsSelected = (doc?: Doc) => {
+ if (Array.from(doc?.[DocViews] ?? []).some(dv => dv?.IsSelected)) {
+ return true;
+ }
+ return false;
+ };
@computed get _isUserActivated() {
- return SelectionManager.IsSelected(this._document) || this._isAnyChildContentActive;
+ return TabDocView.IsSelected(this._document) || this._isAnyChildContentActive;
}
get _isContentActive() {
return this._isUserActivated || this._hovering;
@@ -90,7 +330,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
@action
init = (tab: any, doc: Opt<Doc>) => {
if (tab.contentItem === tab.header.parent.getActiveContentItem()) this._activated = true;
- if (tab.DashDoc !== doc && doc && tab.hasOwnProperty('contentItem') && tab.contentItem.config.type !== 'stack') {
+ if (tab.DashDoc !== doc && doc && tab.contentItem?.config.type !== 'stack') {
tab._disposers = {} as { [name: string]: IReactionDisposer };
tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true);
tab.DashDoc = doc;
@@ -105,7 +345,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
while (child?.children.length) {
const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string');
if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break;
- if (next?.className?.toString().includes(DashFieldView.name)) break;
if (next) child = next;
else break;
}
@@ -129,37 +368,37 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
setupMoveUpEvents(
this,
e,
- e =>
- !e.defaultPrevented &&
- DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY, undefined, () => {
+ moveEv =>
+ !moveEv.defaultPrevented &&
+ DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), moveEv.clientX, moveEv.clientY, undefined, () => {
CollectionDockingView.CloseSplit(doc);
}),
returnFalse,
- action(e => {
+ action(clickEv => {
if (this.view) {
- SelectionManager.SelectView(this.view, false);
+ DocumentView.SelectView(this.view, false);
const child = getChild();
- simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
+ simulateMouseClick(child, clickEv.clientX, clickEv.clientY + 30, clickEv.screenX, clickEv.screenY + 30);
} else {
this._activated = true;
- setTimeout(() => this.view && SelectionManager.SelectView(this.view, false));
+ setTimeout(() => this.view && DocumentView.SelectView(this.view, false));
}
})
);
};
const docIcon = <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} />;
- const closeIcon = <FontAwesomeIcon icon={'eye'} />;
+ const closeIcon = <FontAwesomeIcon icon="eye" />;
ReactDOM.createRoot(iconWrap).render(docIcon);
ReactDOM.createRoot(closeWrap).render(closeIcon);
tab.reactComponents = [iconWrap, closeWrap];
tab.element[0].prepend(iconWrap);
tab._disposers.color = reaction(
- () => ({ variant: SettingsManager.userVariantColor, degree: Doc.GetBrushStatus(doc), highlight: DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting) }),
+ () => ({ variant: SnappingManager.userVariantColor, degree: Doc.GetBrushStatus(doc), highlight: DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting) }),
({ variant, degree, highlight }) => {
const color = highlight?.highlightIndex === Doc.DocBrushStatus.highlighted ? highlight.highlightColor : degree ? ['transparent', variant, variant, 'orange'][degree] : variant;
- const textColor = color === variant ? SettingsManager.userColor : lightOrDark(color);
+ const textColor = color === variant ? SnappingManager.userColor ?? '' : lightOrDark(color);
titleEle.style.color = textColor;
iconWrap.style.color = textColor;
closeWrap.style.color = textColor;
@@ -185,19 +424,19 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
);
}
// shifts the focus to this tab when another tab is dragged over it
- tab.element[0].onmouseenter = (e: MouseEvent) => {
+ tab.element[0].onmouseenter = () => {
if (SnappingManager.IsDragging && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
tab.header.parent.setActiveContentItem(tab.contentItem);
tab.setActive(true);
}
this._document && Doc.BrushDoc(this._document);
};
- tab.element[0].onmouseleave = (e: MouseEvent) => {
+ tab.element[0].onmouseleave = () => {
this._document && Doc.UnBrushDoc(this._document);
};
tab.element[0].oncontextmenu = (e: MouseEvent) => {
- let child = getChild();
+ const child = getChild();
if (child) {
simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
e.stopPropagation();
@@ -208,7 +447,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
// select the tab document when the tab is directly clicked and activate the tab whenver the tab document is selected
titleEle.onpointerdown = action((e: any) => {
if (e.target.className !== 'lm_iconWrap') {
- if (this.view) SelectionManager.SelectView(this.view, false);
+ if (this.view) DocumentView.SelectView(this.view, false);
else this._activated = true;
if (Date.now() - titleEle.lastClick < 1000) titleEle.select();
titleEle.lastClick = Date.now();
@@ -216,15 +455,12 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
}
});
tab._disposers.selectionDisposer = reaction(
- () => SelectionManager.IsSelected(this._document),
+ () => TabDocView.IsSelected(this._document),
action(selected => {
if (selected) this._activated = true;
- const toggle = tab.element[0].children[2].children[0] as HTMLInputElement;
if (selected && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
undoable(() => tab.header.parent.setActiveContentItem(tab.contentItem), 'tab switch')();
}
- //toggle.style.fontWeight = selected ? 'bold' : '';
- // toggle.style.textTransform = selected ? "uppercase" : "";
}),
{ fireImmediately: true }
);
@@ -232,101 +468,27 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
// highlight the tab when the tab document is brushed in any part of the UI
tab._disposers.reactionDisposer = reaction(
() => doc?.title,
- title => (titleEle.value = title),
+ title => {
+ titleEle.value = title;
+ },
{ fireImmediately: true }
);
// clean up the tab when it is closed
tab.closeElement
- .off('click') //unbind the current click handler
- .click(function () {
+ .off('click') // unbind the current click handler
+ .click(() => {
Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
UndoManager.RunInBatch(() => tab.contentItem.remove(), 'delete tab');
});
}
};
- /**
- * Adds a document to the presentation view
- **/
- @action
- public static PinDoc(docs: Doc | Doc[], pinProps: PinProps) {
- const docList = docs instanceof Doc ? [docs] : docs;
-
- const batch = UndoManager.StartBatch('Pin doc to pres trail');
- const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true);
-
- if (!Doc.ActivePresentation) {
- Doc.AddDocToList(Doc.MyTrails, 'data', curPres);
- Doc.ActivePresentation = curPres;
- }
-
- docList.forEach(doc => {
- // Edge Case 1: Cannot pin document to itself
- if (doc === curPres) {
- alert('Cannot pin presentation document to itself');
- return;
- }
- const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false, pinProps);
- const pinDoc = anchorDoc?.type === DocumentType.CONFIG ? anchorDoc : Docs.Create.ConfigDocument({});
- const targDoc = (pinDoc.presentation_targetDoc = anchorDoc ?? doc);
- pinDoc.title = doc.title + ' - Slide';
- 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;
- Doc.SetContainer(pinDoc, curPres);
- // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time
- pinDoc.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_FieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
- pinDoc.treeView_ExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
- pinDoc.treeView_HideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
- const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], null);
-
- if (pinProps.pinViewport) PresBox.pinDocView(pinDoc, pinProps, anchorDoc ?? doc);
- if (!pinProps?.audioRange && duration !== undefined) {
- pinDoc.presentation_mediaStart = 'manual';
- pinDoc.presentation_mediaStop = 'manual';
- }
- if (pinProps?.activeFrame !== undefined) {
- pinDoc.config_activeFrame = pinProps?.activeFrame;
- pinDoc.title = doc.title + ' (move)';
- pinDoc.presentation_movement = PresMovement.Pan;
- }
- if (pinProps?.currentFrame !== undefined) {
- pinDoc.config_currentFrame = pinProps?.currentFrame;
- pinDoc.title = doc.title + ' (move)';
- pinDoc.presentation_movement = PresMovement.Pan;
- }
- if (pinDoc.stroke_isInkMask) {
- pinDoc.presentation_hideAfter = true;
- pinDoc.presentation_hideBefore = true;
- pinDoc.presentation_movement = PresMovement.None;
- }
- if (curPres.expandBoolean) pinDoc.presentation_expandInlineButton = true;
- Doc.AddDocToList(curPres, 'data', pinDoc, PresBox.Instance?.sortArray()?.lastElement());
- PresBox.Instance?.clearSelectedArray();
- pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); //Update selected array
- });
- if (
- // open the presentation trail if it's not already opened
- !Array.from(CollectionDockingView.Instance?.tabMap ?? [])
- .map(d => d.DashDoc)
- .includes(curPres)
- ) {
- if (Doc.IsInMyOverlay(curPres)) Doc.RemFromMyOverlay(curPres);
- CollectionDockingView.AddSplit(curPres, OpenWhereMod.right);
- setTimeout(() => DocumentManager.Instance.showDocument(docList.lastElement(), { willPan: true }), 100); // keeps the pinned doc in view since the sidebar shifts things
- }
- setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs
- }
-
componentDidMount() {
new _global.ResizeObserver(
action((entries: any) => {
+ // eslint-disable-next-line no-restricted-syntax
for (const entry of entries) {
this._panelWidth = entry.contentRect.width;
this._panelHeight = entry.contentRect.height;
@@ -342,12 +504,12 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
}
componentDidUpdate(prevProps: Readonly<TabDocViewProps>) {
super.componentDidUpdate(prevProps);
- this._view && DocumentManager.Instance.AddView(this._view);
+ this._view && DocumentView.addView(this._view);
}
componentWillUnmount() {
this._tabReaction?.();
- this._view && DocumentManager.Instance.RemoveView(this._view);
+ this._view && DocumentView.removeView(this._view);
runInAction(() => TabDocView._allTabs.delete(this));
this._props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged);
@@ -361,7 +523,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
private onActiveContentItemChanged(contentItem: any) {
if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) {
this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab;
- if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => SelectionManager.SelectView(this._view, false));
+ if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => DocumentView.SelectView(this._view, false));
!this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
}
}
@@ -373,50 +535,45 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
// "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name,
// "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right
// lightbox - will add the document to any collection along the path from the document to the docking view that has a field isLightbox. if none is found, it adds to the full screen lightbox
- addDocTab = (doc: Doc, location: OpenWhere) => {
- SelectionManager.DeselectAll();
+ addDocTab = (docsIn: Doc | Doc[], location: OpenWhere) => {
+ const docs = toList(docsIn);
+ DocumentView.DeselectAll();
const whereFields = location.split(':');
const keyValue = whereFields.includes(OpenWhereMod.keyvalue);
const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none;
const panelName = whereFields.length > 1 ? whereFields.lastElement() : '';
- if (doc.dockingConfig && !keyValue) return DashboardView.openDashboard(doc);
+ if (docs[0]?.dockingConfig && !keyValue) return DashboardView.openDashboard(docs[0]);
// prettier-ignore
switch (whereFields[0]) {
case undefined:
case OpenWhere.lightbox: if (this.layoutDoc?._isLightbox) {
- const lightboxView = !doc.annotationOn && DocCast(doc.embedContainer) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.embedContainer)) : undefined;
+ const lightboxView = !docs[0].annotationOn && DocCast(docs[0].embedContainer) ? DocumentView.getFirstDocumentView(DocCast(docs[0].embedContainer)) : undefined;
const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.Document)];
if (lightboxView && (!data || data instanceof List)) {
- lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.Document)] = new List<Doc>([doc]);
+ lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.Document)] = new List<Doc>(docs);
return true;
}
}
- return LightboxView.Instance.AddDocTab(doc, OpenWhere.lightbox);
- case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods);
- case OpenWhere.replace: return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack, panelName, undefined, keyValue);
- case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack, TabDocView.DontSelectOnActivate, keyValue);
- case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack, undefined, keyValue);
+ return LightboxView.Instance.AddDocTab(docs[0], OpenWhere.lightbox);
+ case OpenWhere.close: return CollectionDockingView.CloseSplit(docs[0], whereMods);
+ case OpenWhere.replace: return CollectionDockingView.ReplaceTab(docs[0], whereMods, this.stack, panelName, undefined, keyValue);
+ case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(docs[0], whereMods, this.stack, TabDocView.DontSelectOnActivate, keyValue);
+ case OpenWhere.add:default:return CollectionDockingView.AddSplit(docs[0], whereMods, this.stack, undefined, keyValue);
}
};
remDocTab = (doc: Doc | Doc[]) => {
if (doc === this._document) {
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
CollectionDockingView.CloseSplit(this._document);
return true;
}
return false;
};
- getCurrentFrame = () => {
- return NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame);
- };
- static Activate = (tabDoc: Doc) => {
- const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(tab => tab.DashDoc === tabDoc && !tab.contentItem.config.props.keyValue);
- tab?.header.parent.setActiveContentItem(tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
- return tab !== undefined;
- };
+ getCurrentFrame = () => NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame);
+
@action
- focusFunc = (doc: Doc, options: FocusViewOptions) => {
+ focusFunc = () => {
if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
}
@@ -426,7 +583,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
@observable _forceInvalidateScreenToLocal = 0;
ScreenToLocalTransform = () => {
this._forceInvalidateScreenToLocal;
- const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
+ const { translateX, translateY } = ClientUtils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
return CollectionDockingView.Instance?.ScreenToLocalBoxXf().translate(-translateX, -translateY) ?? Transform.Identity();
};
PanelWidth = () => this._panelWidth;
@@ -434,7 +591,9 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
miniMapColor = () => Colors.MEDIUM_GRAY;
tabView = () => this._view;
disableMinimap = () => !this._document;
- whenChildContentActiveChanges = (isActive: boolean) => (this._isAnyChildContentActive = isActive);
+ whenChildContentActiveChanges = (isActive: boolean) => {
+ this._isAnyChildContentActive = isActive;
+ };
isContentActive = () => this._isContentActive;
waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined);
@computed get docView() {
@@ -443,7 +602,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
<DocumentView
key={this._document[Id]}
ref={action((r: DocumentView) => {
- this._lastView && DocumentManager.Instance.RemoveView(this._lastView);
+ this._lastView && DocumentView.removeView(this._lastView);
this._view = r;
this._lastView = this._view;
})}
@@ -452,7 +611,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
hideTitle={this._props.keyValue}
Document={this._document}
TemplateDataDocument={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined}
- onBrowseClickScript={DocumentView.exploreMode}
waitForDoubleClickToClick={this.waitForDoubleClick}
isContentActive={this.isContentActive}
isDocumentActive={returnFalse}
@@ -465,9 +623,9 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
addDocument={undefined}
removeDocument={this.remDocTab}
addDocTab={this.addDocTab}
- suppressSetHeight={this._document._layout_fitWidth ? true : false}
+ suppressSetHeight={!!this._document._layout_fitWidth}
ScreenToLocalTransform={this.ScreenToLocalTransform}
- dontCenter={'y'}
+ dontCenter="y"
whenChildContentsActiveChanged={this.whenChildContentActiveChanges}
focus={this.focusFunc}
containerViewPath={returnEmptyDoclist}
@@ -485,19 +643,24 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
style={{
fontFamily: Doc.UserDoc().renderStyle === 'comic' ? 'Comic Sans MS' : undefined,
}}
- onPointerOver={action(() => (this._hovering = true))}
- onPointerLeave={action(() => (this._hovering = false))}
- onDragOver={action(() => (this._hovering = true))}
- onDragLeave={action(() => (this._hovering = false))}
+ onPointerOver={action(() => { this._hovering = true; })} // prettier-ignore
+ onPointerLeave={action(() => { this._hovering = false; })} // prettier-ignore
+ onDragOver={action(() => { this._hovering = true; })} // prettier-ignore
+ onDragLeave={action(() => { this._hovering = false; })} // prettier-ignore
ref={ref => {
- if ((this._mainCont = ref)) {
+ this._mainCont = ref;
+ if (this._mainCont) {
if (this._lastTab) {
- this._view && DocumentManager.Instance.RemoveView(this._view);
+ this._view && DocumentView.removeView(this._view);
}
this._lastTab = this.tab;
(this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document);
- DocServer.GetRefField(this._props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document)));
- new _global.ResizeObserver(action((entries: any) => this._forceInvalidateScreenToLocal++)).observe(ref);
+ DocServer.GetRefField(this._props.documentId).then(
+ action(doc => {
+ doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document);
+ })
+ );
+ new _global.ResizeObserver(action(() => this._forceInvalidateScreenToLocal++)).observe(ref);
}
}}>
{this.docView}
@@ -505,142 +668,3 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
);
}
}
-
-interface TabMinimapViewProps {
- document: Doc;
- tabView: () => DocumentView | undefined;
- addDocTab: (doc: Doc, where: OpenWhere) => boolean;
- PanelWidth: () => number;
- PanelHeight: () => number;
- background: () => string;
-}
-interface TabMiniThumbProps {
- miniWidth: () => number;
- miniHeight: () => number;
- miniTop: () => number;
- miniLeft: () => number;
-}
-
-@observer
-class TabMiniThumb extends React.Component<TabMiniThumbProps> {
- render() {
- return <div className="miniThumb" style={{ width: `${this.props.miniWidth()}% `, height: `${this.props.miniHeight()}% `, left: `${this.props.miniLeft()}% `, top: `${this.props.miniTop()}% ` }} />;
- }
-}
-@observer
-export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps> {
- static miniStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
- if (doc) {
- switch (property.split(':')[0]) {
- default:
- return DefaultStyleProvider(doc, props, property);
- case StyleProp.PointerEvents:
- return 'none';
- case StyleProp.DocContents:
- const background = ((type: DocumentType) => {
- // prettier-ignore
- switch (type) {
- case DocumentType.PDF: return 'pink';
- case DocumentType.AUDIO: return 'lightgreen';
- case DocumentType.WEB: return 'brown';
- case DocumentType.IMG: return 'blue';
- case DocumentType.MAP: return 'orange';
- case DocumentType.VID: return 'purple';
- case DocumentType.RTF: return 'yellow';
- case DocumentType.COL: return undefined;
- default: return 'gray';
- }
- })(doc.type as DocumentType);
- return !background ? undefined : <div style={{ width: NumCast(doc._width), height: NumCast(doc._height), position: 'absolute', display: 'block', background }} />;
- }
- }
- };
-
- @computed get renderBounds() {
- const compView = this._props.tabView()?.ComponentView as CollectionFreeFormView;
- const bounds = compView?.freeformData?.(true)?.bounds;
- if (!bounds) return undefined;
- const xbounds = bounds.r - bounds.x;
- const ybounds = bounds.b - bounds.y;
- const dim = Math.max(xbounds, ybounds);
- return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim };
- }
- @computed get xPadding() {
- return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cx - this.renderBounds.l));
- }
- @computed get yPadding() {
- return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cy - this.renderBounds.l));
- }
- childLayoutTemplate = () => Cast(this._props.document.childLayoutTemplate, Doc, null);
- returnMiniSize = () => NumCast(this._props.document._miniMapSize, 150);
- miniDown = (e: React.PointerEvent) => {
- const doc = this._props.document;
- const miniSize = this.returnMiniSize();
- doc &&
- setupMoveUpEvents(
- this,
- e,
- action((e: PointerEvent, down: number[], delta: number[]) => {
- const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 };
- doc._freeform_panX = clamp(NumCast(doc._freeform_panX) + (delta[0] / miniSize) * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim);
- doc._freeform_panY = clamp(NumCast(doc._freeform_panY) + (delta[1] / miniSize) * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim);
- return false;
- }),
- emptyFunction,
- emptyFunction
- );
- };
- popup = () => {
- if (!this.renderBounds) return <></>;
- const renderBounds = this.renderBounds;
- const miniWidth = () => (this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
- const miniHeight = () => (this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
- const miniLeft = () => 50 + ((NumCast(this._props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2;
- const miniTop = () => 50 + ((NumCast(this._props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2;
- const miniSize = this.returnMiniSize();
- return (
- <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}>
- <CollectionFreeFormView
- Document={this._props.document}
- docViewPath={returnEmptyDocViewList}
- childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
- noOverlay={true} // don't render overlay Docs since they won't scale
- isContentActive={emptyFunction}
- isAnyChildContentActive={returnFalse}
- select={emptyFunction}
- isSelected={returnFalse}
- dontRegisterView={true}
- fieldKey={Doc.LayoutFieldKey(this._props.document)}
- addDocument={returnFalse}
- moveDocument={returnFalse}
- removeDocument={returnFalse}
- PanelWidth={this.returnMiniSize}
- PanelHeight={this.returnMiniSize}
- ScreenToLocalTransform={Transform.Identity}
- renderDepth={0}
- whenChildContentsActiveChanged={emptyFunction}
- focus={emptyFunction}
- styleProvider={TabMinimapView.miniStyleProvider}
- addDocTab={this._props.addDocTab}
- pinToPres={TabDocView.PinDoc}
- childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
- childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
- searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
- fitContentsToBox={returnTrue}
- xPadding={this.xPadding}
- yPadding={this.yPadding}
- />
- <div className="miniOverlay" onPointerDown={this.miniDown}>
- <TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} />
- </div>
- </div>
- );
- };
- render() {
- return this._props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.document)) || this._props.document?._type_collection !== CollectionViewType.Freeform ? null : (
- <div className="miniMap-hidden">
- <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SettingsManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} />
- </div>
- );
- }
-}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 4fd49f8fe..6ea6bbfbd 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1,44 +1,51 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { IconButton, Size } from 'browndash-components';
import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Utils, emptyFunction, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick } from '../../../Utils';
-import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../fields/Doc';
+import { ClientUtils, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
+import { Doc, DocListCast, Field, FieldResult, FieldType, Opt, StrListCast } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { RichTextField } from '../../../fields/RichTextField';
import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
+import { DocUtils } from '../../documents/DocUtils';
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 { Docs } from '../../documents/Documents';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { SettingsManager } from '../../util/SettingsManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { UndoManager, undoBatch, undoable } from '../../util/UndoManager';
import { EditableView } from '../EditableView';
import { ObservableReactComponent } from '../ObservableReactComponent';
-import { StyleProp } from '../StyleProvider';
-import { DocumentView, DocumentViewInternal, OpenWhere } from '../nodes/DocumentView';
+import { StyleProp } from '../StyleProp';
+import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
import { FieldViewProps, StyleProviderFuncType } from '../nodes/FieldView';
-import { KeyValueBox } from '../nodes/KeyValueBox';
+import { OpenWhere } from '../nodes/OpenWhere';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
-import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
+import { CollectionTreeView } from './CollectionTreeView';
+import { TreeViewType } from './CollectionTreeViewType';
import { CollectionView } from './CollectionView';
import { TreeSort } from './TreeSort';
import './TreeView.scss';
+
const { TREE_BULLET_WIDTH } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
export interface TreeViewProps {
treeView: CollectionTreeView;
+ // eslint-disable-next-line no-use-before-define
parentTreeView: TreeView | CollectionTreeView | undefined;
observeHeight: (ref: any) => void;
unobserveHeight: (ref: any) => void;
@@ -48,7 +55,7 @@ export interface TreeViewProps {
treeViewParent: Doc;
renderDepth: number;
dragAction: dropActionType;
- addDocTab: (doc: Doc, where: OpenWhere) => boolean;
+ addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean;
panelWidth: () => number;
panelHeight: () => number;
addDocument: (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => boolean;
@@ -87,6 +94,7 @@ const treeBulletWidth = function () {
*/
@observer
export class TreeView extends ObservableReactComponent<TreeViewProps> {
+ // eslint-disable-next-line no-use-before-define
static _editTitleOnLoad: Opt<{ id: string; parent: TreeView | CollectionTreeView | undefined }>;
static _openTitleScript: Opt<ScriptField | undefined>;
static _openLevelScript: Opt<ScriptField | undefined>;
@@ -101,6 +109,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
get treeViewOpenIsTransient() {
return this.treeView.Document.treeView_OpenIsTransient || Doc.IsDataProto(this.Document);
}
+ @computed get treeViewOpen() {
+ return (!this.treeViewOpenIsTransient && Doc.GetT(this.Document, 'treeView_Open', 'boolean', true)) || this._transientOpenState;
+ }
set treeViewOpen(c: boolean) {
if (this.treeViewOpenIsTransient) this._transientOpenState = c;
else {
@@ -137,9 +148,6 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
@computed get Document() {
return this._props.Document;
}
- @computed get treeViewOpen() {
- return (!this.treeViewOpenIsTransient && Doc.GetT(this.Document, 'treeView_Open', 'boolean', true)) || this._transientOpenState;
- }
@computed get treeViewExpandedView() {
return this.validExpandViewTypes.includes(StrCast(this.Document.treeView_ExpandedView)) ? StrCast(this.Document.treeView_ExpandedView) : this.defaultExpandedView;
}
@@ -192,12 +200,13 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
}
return false;
};
- @undoBatch remove = (doc: Doc | Doc[], key: string) => {
+ @undoBatch remove = (docIn: Doc | Doc[], key: string) => {
+ const docs = toList(docIn);
this.treeView._props.select(false);
- const ind = DocListCast(this.dataDoc[key]).indexOf(doc instanceof Doc ? doc : doc.lastElement());
+ const ind = DocListCast(this.dataDoc[key]).indexOf(docs.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.treeView.DocumentView?.())?.select(false);
+ const res = docs.reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
+ res && ind > 0 && DocumentView.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.treeView.DocumentView?.())?.select(false);
return res;
};
@@ -221,7 +230,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
this.treeViewOpen = !this.treeViewOpen;
} else {
// choose an appropriate embedding or make one. --- choose the first embedding that (1) user owns, (2) has no context field ... otherwise make a new embedding
- const bestEmbedding = docView.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.Document) ? docView.Document : Doc.BestEmbedding(docView.Document);
+ const bestEmbedding = docView.Document.author === ClientUtils.CurrentUserEmail() && !Doc.IsDataProto(docView.Document) ? docView.Document : Doc.BestEmbedding(docView.Document);
this._props.addDocTab(bestEmbedding, OpenWhere.lightbox);
}
};
@@ -230,7 +239,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
recurToggle = (childList: Doc[]) => {
if (childList.length > 0) {
childList.forEach(child => {
- child.runProcess = !!!child.runProcess;
+ child.runProcess = !child.runProcess;
TreeView.ToggleChildrenRun.get(child)?.();
});
}
@@ -273,9 +282,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
this.recurToggle(this.childDocs);
});
- TreeView.GetRunningChildren.set(this.Document, () => {
- return this.getRunningChildren(this.childDocs);
- });
+ TreeView.GetRunningChildren.set(this.Document, () => this.getRunningChildren(this.childDocs));
}
_treeEle: any;
@@ -301,7 +308,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
super.componentDidUpdate(prevProps);
this._disposers.opening = reaction(
() => this.treeViewOpen,
- open => !open && (this._renderCount = 20)
+ open => {
+ !open && (this._renderCount = 20);
+ }
);
this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex);
}
@@ -310,7 +319,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex);
}
- onDragUp = (e: PointerEvent) => {
+ onDragUp = () => {
document.removeEventListener('pointerup', this.onDragUp, true);
document.removeEventListener('pointermove', this.onDragMove, true);
};
@@ -324,7 +333,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
document.addEventListener('pointerup', this.onDragUp, true);
}
};
- onPointerLeave = (e: React.PointerEvent): void => {
+ onPointerLeave = (): void => {
Doc.UnBrushDoc(this.dataDoc);
if (this._header.current?.className !== 'treeView-header-editing') {
this._header.current!.className = 'treeView-header';
@@ -368,7 +377,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const bulletData = bullet[DocData];
bulletData.title = ComputedField.MakeFunction('this.text?.Text');
bulletData.data = new List<Doc>([]);
- DocumentManager.Instance.AddViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.());
+ DocumentView.addViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.());
return bullet;
}
@@ -385,7 +394,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
return this.localAdd(folder);
};
- preTreeDrop = (e: Event, de: DragManager.DropEvent, docDropAction: dropActionType) => {
+ preTreeDrop = () => {
// fall through and let the CollectionTreeView handle this since treeView items have no special properties of their own
};
@@ -395,7 +404,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
if (!this._header.current) return false;
const rect = this._header.current.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
- const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
+ const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || !!(!before && this.treeViewOpen && this.childDocs?.length);
if (de.complete.linkDragData) {
const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor();
const destDoc = this.Document;
@@ -403,7 +412,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
e.stopPropagation();
return true;
}
- const docDragData = de.complete.docDragData;
+ const { docDragData } = de.complete;
if (docDragData && pt[0] < rect.left + rect.width) {
if (docDragData.draggedDocuments[0] === this.Document) return true;
const added = this.dropDocuments(
@@ -423,14 +432,14 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
return false;
};
- localAdd = (doc: Doc | Doc[]) => {
- const innerAdd = (doc: Doc) => {
+ localAdd = (docs: Doc | Doc[]): boolean => {
+ const innerAdd = (doc: Doc): boolean => {
const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField;
const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc);
dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer));
return added;
};
- return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
+ return toList(docs).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
};
dropping: boolean = false;
@@ -462,8 +471,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
refTransform = (ref: HTMLDivElement | undefined | null) => {
if (!ref) return this.ScreenToLocalTransform();
- const { scale, translateX, translateY } = Utils.GetScreenTransform(ref);
- const outerXf = Utils.GetScreenTransform(this.treeView.MainEle());
+ const { translateX, translateY } = ClientUtils.GetScreenTransform(ref);
+ const outerXf = ClientUtils.GetScreenTransform(this.treeView.MainEle());
const offset = this.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
return this.ScreenToLocalTransform().translate(offset[0], offset[1]);
};
@@ -490,29 +499,34 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const ids: { [key: string]: string } = {};
const rows: JSX.Element[] = [];
const doc = this.Document;
- doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key));
+ doc &&
+ Object.keys(doc).forEach(key => {
+ !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key);
+ });
+ // eslint-disable-next-line no-restricted-syntax
for (const key of Object.keys(ids).slice().sort()) {
+ // eslint-disable-next-line no-continue
if (this._props.skipFields?.includes(key) || key === 'title' || key === 'treeView_Open') continue;
const contents = doc[key];
let contentElement: (JSX.Element | null)[] | JSX.Element = [];
- let leftOffset = observable({ width: 0 });
+ const leftOffset = observable({ width: 0 });
const expandedWidth = () => this._props.panelWidth() - leftOffset.width;
if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc)) {
- const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key);
- const moveDoc = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(doc, target, addDoc);
- const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => {
- const innerAdd = (doc: Doc) => {
+ const remDoc = (docs: Doc | Doc[]) => this.remove(docs, key);
+ const moveDoc = (docs: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(docs, target, addDoc);
+ const addDoc = (docs: Doc | Doc[], addBefore?: Doc, before?: boolean) => {
+ const innerAdd = (iDoc: Doc) => {
const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
- const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
- dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer));
+ const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, iDoc, addBefore, before, false, true);
+ dataIsComputed && Doc.SetContainer(iDoc, DocCast(this.Document.embedContainer));
return added;
};
- return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
+ return toList(docs).reduce((flg, iDoc) => flg && innerAdd(iDoc), true as boolean);
};
contentElement = TreeView.GetChildElements(
- contents instanceof Doc ? [contents] : DocListCast(contents),
+ toList(contents as any),
this.treeView,
this,
doc,
@@ -549,11 +563,11 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
contentElement = (
<EditableView
key="editableView"
- contents={contents !== undefined ? Field.toString(contents as Field) : 'null'}
+ contents={contents !== undefined ? Field.toString(contents as FieldType) : 'null'}
height={13}
fontSize={12}
GetValue={() => Field.toKeyValueString(doc, key)}
- SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)}
+ SetValue={(value: string) => Doc.SetField(doc, key, value, true)}
/>
);
}
@@ -572,20 +586,20 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
);
}
rows.push(
- <div style={{ display: 'flex', overflow: 'auto' }} key={'newKeyValue'}>
+ <div style={{ display: 'flex', overflow: 'auto' }} key="newKeyValue">
<EditableView
key="editableView"
- contents={'+key=value'}
+ contents="+key=value"
height={13}
fontSize={12}
GetValue={returnEmptyString}
SetValue={input => {
- const match = input.match(/([a-zA-Z0-9_-]+)(=|=:=)([a-zA-Z,_@\?\+\-\*\/\ 0-9\(\)]+)/);
+ const match = input.match(/([a-zA-Z0-9_-]+)(=|=:=)([a-zA-Z,_@?+\-*/ 0-9()]+)/);
if (match) {
const key = match[1];
const assign = match[2];
const val = match[3];
- KeyValueBox.SetField(doc, key, assign + val, false);
+ Doc.SetField(doc, key, assign + val, false);
return true;
}
return false;
@@ -620,7 +634,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const docs = TreeView.sortDocs(this.childDocs || ([] as Doc[]), ordering);
doc.zIndex = addBefore ? NumCast(addBefore.zIndex) + (before ? -0.5 : 0.5) : 1000;
docs.push(doc);
- docs.sort((a, b) => (NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1)).forEach((d, i) => (d.zIndex = i));
+ docs.sort((a, b) => (NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1)).forEach((d, i) => {
+ d.zIndex = i;
+ });
}
const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false);
@@ -628,10 +644,10 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
return added;
};
- const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true);
+ const addDoc = (docs: Doc | Doc[], addBefore?: Doc, before?: boolean) => toList(docs).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true);
const docs = expandKey === 'embeddings' ? this.childEmbeddings : expandKey === 'links' ? this.childLinks : expandKey === 'annotations' ? this.childAnnos : this.childDocs;
- let downX = 0,
- downY = 0;
+ let downX = 0;
+ let downY = 0;
if (docs?.length && this._renderCount < docs?.length) {
this._renderTimer && clearTimeout(this._renderTimer);
this._renderTimer = setTimeout(
@@ -667,7 +683,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
style={{ cursor: 'inherit' }}
key={expandKey + 'more'}
title={`Sorted by : ${this.Document.treeView_SortCriterion}. click to cycle`}
- className="" //this.doc.treeView_HideTitle ? 'no-indent' : ''}
+ className="" // this.doc.treeView_HideTitle ? 'no-indent' : ''}
onPointerDown={e => {
downX = e.clientX;
downY = e.clientY;
@@ -719,7 +735,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
</ul>
</div>
);
- } else if (this.treeViewExpandedView === 'fields') {
+ }
+ if (this.treeViewExpandedView === 'fields') {
return (
<ul key={this.Document[Id] + this.Document.title} style={{ cursor: 'inherit' }}>
<div>{this.expandedField}</div>
@@ -804,7 +821,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
@computed get validExpandViewTypes() {
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 links = () => (Doc.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');
@@ -891,14 +908,15 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
titleStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
if (!doc || doc !== this.Document) return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
- const treeView = this.treeView;
+ const { treeView } = this;
// prettier-ignore
switch (property.split(':')[0]) {
case StyleProp.Opacity: return this.treeView.outlineMode ? undefined : 1;
- case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : undefined;//StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
+ case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : undefined; // StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
case StyleProp.Highlighting: if (this.treeView.outlineMode) return undefined;
+ break;
case StyleProp.BoxShadow: return undefined;
- case StyleProp.DocContents:
+ case StyleProp.DocContents: {
const highlightIndex = this.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.GetBrushHighlightStatus(doc);
const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
return treeView.outlineMode ? null : (
@@ -917,6 +935,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
{StrCast(doc?.title)}
</div>
);
+ }
+ default:
}
return treeView._props.styleProvider?.(doc, props, property);
};
@@ -924,7 +944,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
if (property.startsWith(StyleProp.Decorations)) return null;
return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
};
- onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
+ onKeyDown = (e: React.KeyboardEvent) => {
if (this.Document.treeView_HideHeader || (this.Document.treeView_HideHeaderIfTemplate && this.treeView._props.childLayoutTemplate?.()) || this.treeView.outlineMode) {
switch (e.key) {
case 'Tab':
@@ -944,6 +964,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
e.stopPropagation?.();
e.preventDefault?.();
return UndoManager.RunInBatch(this.makeTextCollection, 'bullet');
+ default:
}
}
return false;
@@ -959,22 +980,24 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const view = this._editTitle ? (
<EditableView
key="_editTitle"
- oneLine={true}
- display={'inline-block'}
+ oneLine
+ display="inline-block"
editing={this._editTitle}
- background={'#7089bb'}
+ background="#7089bb"
contents={StrCast(this.Document.title)}
height={12}
- sizeToContent={true}
+ sizeToContent
fontSize={12}
- isEditingCallback={action(e => (this._editTitle = e))}
+ isEditingCallback={action(e => {
+ this._editTitle = e;
+ })}
GetValue={() => StrCast(this.Document.title)}
OnTab={undoBatch((shift?: boolean) => {
if (!shift) this._props.indentDocument?.(true);
else this._props.outdentDocument?.(true);
})}
OnEmpty={undoBatch(() => this.treeView.outlineMode && this._props.removeDoc?.(this.Document))}
- OnFillDown={val => this.treeView.fileSysMode && this.makeFolder()}
+ OnFillDown={() => this.treeView.fileSysMode && this.makeFolder()}
SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => {
Doc.SetInPlace(this.Document, 'title', value, false);
this.treeView.outlineMode && enterKey && this.makeTextCollection();
@@ -984,7 +1007,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
<DocumentView
key="title"
ref={action((r: any) => {
- this._docRef = r ? r : undefined;
+ this._docRef = r || undefined;
if (this._docRef && TreeView._editTitleOnLoad?.id === this.Document[Id] && TreeView._editTitleOnLoad.parent === this._props.parentTreeView) {
this._docRef.select(false);
this.setEditTitle(this._docRef);
@@ -994,8 +1017,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
Document={this.Document}
layout_fitWidth={returnTrue}
scriptContext={this}
- hideDecorations={true}
- hideClickBehaviors={true}
+ hideDecorations
+ hideClickBehaviors
styleProvider={this.titleStyleProvider}
onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
containerViewPath={this.treeView.childContainerViewPath}
@@ -1015,7 +1038,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
PanelHeight={return18}
contextMenuItems={this.contextMenuItems}
renderDepth={1}
- isContentActive={emptyFunction} //this._props.isContentActive}
+ isContentActive={emptyFunction} // this._props.isContentActive}
isDocumentActive={this._props.isContentActive}
focus={this.refocus}
whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
@@ -1045,99 +1068,101 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
}}>
{view}
</div>
- <div className="treeView-rightButtons" ref={action((r: any) => r && (this.headerEleWidth = r.getBoundingClientRect().width))}>
+ <div
+ className="treeView-rightButtons"
+ ref={action((r: any) => {
+ r && (this.headerEleWidth = r.getBoundingClientRect().width);
+ })}>
{this.titleButtons}
</div>
</>
);
}
- renderBulletHeader = (contents: JSX.Element, editing: boolean) => {
- return (
- <>
+ renderBulletHeader = (contents: JSX.Element, editing: boolean) => (
+ <>
+ <div
+ className={`treeView-header` + (editing ? '-editing' : '')}
+ key="titleheader"
+ ref={this._header}
+ onClick={this.ignoreEvent}
+ onPointerDown={e => {
+ this.treeView.isContentActive() &&
+ setupMoveUpEvents(
+ this,
+ e,
+ () => {
+ (this._dref ?? this._docRef)?.startDragging(e.clientX, e.clientY, '' as any);
+ return true;
+ },
+ returnFalse,
+ emptyFunction
+ );
+ }}
+ onPointerEnter={this.onPointerEnter}
+ onPointerLeave={this.onPointerLeave}>
<div
- className={`treeView-header` + (editing ? '-editing' : '')}
- key="titleheader"
- ref={this._header}
- onClick={this.ignoreEvent}
- onPointerDown={e => {
- this.treeView.isContentActive() &&
- setupMoveUpEvents(
- this,
- e,
- () => {
- (this._dref ?? this._docRef)?.startDragging(e.clientX, e.clientY, '' as any);
- return true;
- },
- returnFalse,
- emptyFunction
- );
+ className="treeView-background"
+ style={{
+ background: SettingsManager.userColor,
}}
- onPointerEnter={this.onPointerEnter}
- onPointerLeave={this.onPointerLeave}>
- <div
- className="treeView-background"
- style={{
- background: SettingsManager.userColor,
- }}
- />
- {contents}
- </div>
- {this.renderBorder}
- </>
- );
- };
-
- fitWidthFilter = (doc: Doc) => (doc.type === DocumentType.IMG ? false : undefined);
- renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => {
- return (
- <div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}>
- <DocumentView
- key={this.Document[Id]}
- ref={action((r: DocumentView | null) => (this._dref = r))}
- Document={this.Document}
- layout_fitWidth={this.fitWidthFilter}
- PanelWidth={this.embeddedPanelWidth}
- PanelHeight={this.embeddedPanelHeight}
- LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined}
- LayoutTemplate={this.treeView._props.childLayoutTemplate}
- isContentActive={isActive}
- isDocumentActive={isActive}
- styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
- fitContentsToBox={returnTrue}
- hideTitle={asText}
- hideDecorations={true}
- hideClickBehaviors={true}
- hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)}
- dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)}
- ScreenToLocalTransform={this.docTransform}
- renderDepth={this._props.renderDepth + 1}
- onClickScript={this.onChildClick}
- onKey={this.onKeyDown}
- containerViewPath={this.treeView.childContainerViewPath}
- childFilters={returnEmptyFilter}
- childFiltersByRanges={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- focus={this.refocus}
- addDocument={this._props.addDocument}
- moveDocument={this.move}
- removeDocument={this._props.removeDoc}
- whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
- xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)}
- yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)}
- addDocTab={this._props.addDocTab}
- pinToPres={this.treeView._props.pinToPres}
- disableBrushing={this.treeView._props.disableBrushing}
- scriptContext={this}
/>
+ {contents}
</div>
- );
- };
+ {this.renderBorder}
+ </>
+ );
+
+ fitWidthFilter = (doc: Doc) => (doc.type === DocumentType.IMG ? false : undefined);
+ renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => (
+ <div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}>
+ <DocumentView
+ key={this.Document[Id]}
+ ref={action((r: DocumentView | null) => {
+ this._dref = r;
+ })}
+ Document={this.Document}
+ layout_fitWidth={this.fitWidthFilter}
+ PanelWidth={this.embeddedPanelWidth}
+ PanelHeight={this.embeddedPanelHeight}
+ LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined}
+ LayoutTemplate={this.treeView._props.childLayoutTemplate}
+ isContentActive={isActive}
+ isDocumentActive={isActive}
+ styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
+ fitContentsToBox={returnTrue}
+ hideTitle={asText}
+ hideDecorations
+ hideClickBehaviors
+ hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)}
+ dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)}
+ ScreenToLocalTransform={this.docTransform}
+ renderDepth={this._props.renderDepth + 1}
+ onClickScript={this.onChildClick}
+ onKey={this.onKeyDown}
+ containerViewPath={this.treeView.childContainerViewPath}
+ childFilters={returnEmptyFilter}
+ childFiltersByRanges={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ focus={this.refocus}
+ addDocument={this._props.addDocument}
+ moveDocument={this.move}
+ removeDocument={this._props.removeDoc}
+ whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+ xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)}
+ yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)}
+ addDocTab={this._props.addDocTab}
+ pinToPres={this.treeView._props.pinToPres}
+ disableBrushing={this.treeView._props.disableBrushing}
+ scriptContext={this}
+ />
+ </div>
+ );
// renders the text version of a document as the header. This is used in the file system mode and in other vanilla tree views.
@computed get renderTitleAsHeader() {
return this.treeView.Document.treeView_HideUnrendered && this.Document.layout_unrendered && !this.Document.treeView_FieldKey ? (
- <div></div>
+ <div />
) : (
<>
{this.renderBullet}
@@ -1147,14 +1172,12 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
}
// renders the document in the header field instead of a text proxy.
- renderDocumentAsHeader = (asText: boolean) => {
- return (
- <>
- {this.renderBullet}
- {this.renderEmbeddedDocument(asText, this._props.isContentActive)}
- </>
- );
- };
+ renderDocumentAsHeader = (asText: boolean) => (
+ <>
+ {this.renderBullet}
+ {this.renderEmbeddedDocument(asText, this._props.isContentActive)}
+ </>
+ );
@computed get renderBorder() {
const sorting = StrCast(this.Document.treeView_SortCriterion, TreeSort.WhenAdded);
@@ -1170,7 +1193,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const pt = [de.clientX, de.clientY];
const rect = this._header.current!.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
- const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
+ const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || !!(!before && this.treeViewOpen && this.childDocs?.length);
this.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, dropActionType.copy, undefined, undefined, false, false));
};
@@ -1185,7 +1208,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
className={`treeView-container${this._props.isContentActive() ? '-active' : ''}`}
ref={this.createTreeDropTarget}
onDrop={this.onTreeDrop}
- //onPointerDown={e => this._props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document
+ // onPointerDown={e => this._props.isContentActive(true) && DocumentView.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document
// onKeyDown={this.onKeyDown}
>
<li className="collection-child">
@@ -1209,11 +1232,10 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const aN = parseInt(a.match(reN)![0], 10);
const bN = parseInt(b.match(reN)![0], 10);
return aN === bN ? 0 : aN > bN ? 1 : -1;
- } else {
- return aA > bA ? 1 : -1;
}
+ return aA > bA ? 1 : -1;
};
- docs.sort(function (d1, d2): 0 | 1 | -1 {
+ docs.sort((d1, d2): 0 | 1 | -1 => {
const a = criterion === TreeSort.AlphaUp ? d2 : d1;
const b = criterion === TreeSort.AlphaUp ? d1 : d2;
const first = a[criterion === TreeSort.Zindex ? 'zIndex' : 'title'];
@@ -1230,7 +1252,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
childDocs: Doc[],
treeView: CollectionTreeView,
parentTreeView: CollectionTreeView | TreeView | undefined,
- treeView_Parent: Doc,
+ treeViewParent: Doc,
dataDoc: Doc | undefined,
parentCollectionDoc: Doc | undefined,
containerPrevSibling: Doc | undefined,
@@ -1238,13 +1260,13 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
remove: undefined | ((doc: Doc | Doc[]) => boolean),
move: DragManager.MoveFunction,
dragAction: dropActionType,
- addDocTab: (doc: Doc, where: OpenWhere) => boolean,
+ addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean,
styleProvider: undefined | StyleProviderFuncType,
screenToLocalXf: () => Transform,
isContentActive: (outsideReaction?: boolean) => boolean,
panelWidth: () => number,
renderDepth: number,
- treeView_HideHeaderFields: () => boolean,
+ treeViewHideHeaderFields: () => boolean,
renderedIds: string[],
onCheckedClick: undefined | (() => ScriptField),
onChildClick: undefined | (() => ScriptField),
@@ -1261,19 +1283,14 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
hierarchyIndex?: number[],
renderCount?: number
) {
- const viewSpecScript = Cast(treeView_Parent.viewSpecScript, ScriptField);
- if (viewSpecScript) {
- childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result);
- }
-
- const docs = TreeView.sortDocs(childDocs, StrCast(treeView_Parent.treeView_SortCriterion, TreeSort.WhenAdded));
+ const docs = TreeView.sortDocs(childDocs, StrCast(treeViewParent.treeView_SortCriterion, TreeSort.WhenAdded));
const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView._props.NativeDimScaling?.() || 1);
- const treeView_Refs = new Map<Doc, TreeView | undefined>();
+ const treeViewRefs = new Map<Doc, TreeView | undefined>();
return docs
.filter(child => child instanceof Doc)
.map((child, i) => {
if (renderCount && i > renderCount) return null;
- const pair = Doc.GetLayoutDataDocPair(treeView_Parent, dataDoc, child);
+ const pair = Doc.GetLayoutDataDocPair(treeViewParent, dataDoc, child);
if (!pair.layout || pair.data instanceof Promise) {
return null;
}
@@ -1283,14 +1300,14 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const fieldKey = Doc.LayoutFieldKey(newParent);
if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) {
remove(child);
- FormattedTextBox.SetSelectOnLoad(child);
+ Doc.SetSelectOnLoad(child);
TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined;
Doc.AddDocToList(newParent, fieldKey, child, addAfter, false);
newParent.treeView_Open = true;
Doc.SetContainer(child, treeView.Document);
}
};
- const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeView_Refs.get(docs[i - 1]));
+ const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1]));
const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView._props.parentTreeView : undefined);
const addDocument = (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false);
const childLayout = Doc.Layout(pair.layout);
@@ -1301,12 +1318,11 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
return (
<TreeView
key={child[Id]}
- ref={r => treeView_Refs.set(child, r ? r : undefined)}
+ ref={r => treeViewRefs.set(child, r || undefined)}
Document={pair.layout}
dataDoc={pair.data}
- treeViewParent={treeView_Parent}
+ treeViewParent={treeViewParent}
prevSibling={docs[i]}
- // TODO: [AL] add these
hierarchyIndex={hierarchyIndex ? [...hierarchyIndex, i + 1] : undefined}
AddToMap={AddToMap}
RemFromMap={RemFromMap}
@@ -1316,7 +1332,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
onCheckedClick={onCheckedClick}
onChildClick={onChildClick}
renderDepth={renderDepth}
- removeDoc={StrCast(treeView_Parent.treeView_FreezeChildren).includes('remove') ? undefined : remove}
+ removeDoc={StrCast(treeViewParent.treeView_FreezeChildren).includes('remove') ? undefined : remove}
addDocument={addDocument}
styleProvider={styleProvider}
panelWidth={rowWidth}
@@ -1327,7 +1343,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
addDocTab={addDocTab}
ScreenToLocalTransform={screenToLocalXf}
isContentActive={isContentActive}
- treeViewHideHeaderFields={treeView_HideHeaderFields}
+ treeViewHideHeaderFields={treeViewHideHeaderFields}
renderedIds={renderedIds}
skipFields={skipFields}
firstLevel={firstLevel}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
index 0acc99360..d2ce17f99 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
@@ -10,6 +10,7 @@ export interface CollectionFreeFormViewBackgroundGridProps {
PanelWidth: () => number;
PanelHeight: () => number;
color: () => string;
+ // eslint-disable-next-line react/require-default-props
isAnnotationOverlay?: boolean;
nativeDimScaling: () => number;
zoomScaling: () => number;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts
new file mode 100644
index 000000000..26a52cd2a
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts
@@ -0,0 +1,223 @@
+import { action, observable } from 'mobx';
+import { CollectionFreeFormView } from '.';
+import { intersectRect } from '../../../../Utils';
+import { Doc, Opt } from '../../../../fields/Doc';
+import { NumCast, StrCast } from '../../../../fields/Types';
+import { DocumentType } from '../../../documents/DocumentTypes';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
+import { StyleProp } from '../../StyleProp';
+import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
+import { DocumentView } from '../../nodes/DocumentView';
+import { FieldViewProps } from '../../nodes/FieldView';
+import './CollectionFreeFormView.scss';
+
+export class CollectionFreeFormClusters {
+ private _view: CollectionFreeFormView;
+ private _clusterDistance: number = 75;
+ private _hitCluster: number = -1;
+ @observable _clusterSets: Doc[][] = [];
+
+ constructor(view: CollectionFreeFormView) {
+ this._view = view;
+ }
+ get Document() { return this._view.Document; } // prettier-ignore
+ get DocumentView() { return this._view.DocumentView?.(); } // prettier-ignore
+ get childDocs() { return this._view.childDocs; } // prettier-ignore
+ get childLayoutPairs() { return this._view.childLayoutPairs; } // prettier-ignore
+ get screenToContentsXf() { return this._view.screenToFreeformContentsXf; } // prettier-ignore
+ get viewStyleProvider() { return this._view._props.styleProvider; } // prettier-ignore
+ get viewMoveDocument() { return this._view._props.moveDocument; } // prettier-ignore
+ get selectDocuments() { return this._view.selectDocuments; } // prettier-ignore
+
+ static overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
+ const doc2Layout = Doc.Layout(doc2);
+ const doc1Layout = Doc.Layout(doc1);
+ const x2 = NumCast(doc2.x) - clusterDistance;
+ const y2 = NumCast(doc2.y) - clusterDistance;
+ const w2 = NumCast(doc2Layout._width) + clusterDistance;
+ const h2 = NumCast(doc2Layout._height) + clusterDistance;
+ const x = NumCast(doc1.x) - clusterDistance;
+ const y = NumCast(doc1.y) - clusterDistance;
+ const w = NumCast(doc1Layout._width) + clusterDistance;
+ const h = NumCast(doc1Layout._height) + clusterDistance;
+ return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
+ }
+ handlePointerDown(probe: number[]) {
+ this._hitCluster = this.childLayoutPairs
+ .map(pair => pair.layout)
+ .reduce((cluster, cd) => {
+ const grouping = this.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;
+ const cy = NumCast(cd.y) - this._clusterDistance / 2;
+ const cw = NumCast(layoutDoc._width) + this._clusterDistance;
+ const ch = NumCast(layoutDoc._height) + this._clusterDistance;
+ return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster;
+ }
+ return cluster;
+ }, -1);
+ return this._hitCluster;
+ }
+
+ tryToDrag(e: PointerEvent) {
+ const cluster = this._hitCluster;
+ if (cluster !== -1) {
+ const ptsParent = e;
+ if (ptsParent) {
+ const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
+ const clusterDocs = eles.map(ele => DocumentView.getDocumentView(ele, this.DocumentView)!);
+ const { left, top } = clusterDocs[0].getBounds || { left: 0, top: 0 };
+ const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? dropActionType.embed : undefined);
+ de.moveDocument = this.viewMoveDocument;
+ de.offset = this.screenToContentsXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
+ DragManager.StartDocumentDrag(
+ clusterDocs.map(v => v.ContentDiv!),
+ de,
+ ptsParent.clientX,
+ ptsParent.clientY,
+ { hideSource: !de.dropAction }
+ );
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ initLayout() {
+ if (this.Document._freeform_useClusters && !this._clusterSets.length && this.childDocs.length) {
+ return this.updateClusters(true);
+ }
+ return false;
+ }
+ @action
+ updateClusters(useClusters: boolean) {
+ this.Document._freeform_useClusters = useClusters;
+ this._clusterSets.length = 0;
+ this.childLayoutPairs.map(pair => pair.layout).map(c => this.addDocument(c));
+ }
+
+ @action
+ addDocuments(docs: Doc[]) {
+ const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
+ if (this.Document._freeform_useClusters) {
+ const docFirst = docs[0];
+ docs.forEach(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)));
+ const preferredInd = NumCast(docFirst.layout_cluster);
+ docs.forEach(doc => {
+ doc.layout_cluster = -1;
+ });
+ docs.map(doc =>
+ this._clusterSets.map((set, i) =>
+ set.forEach(member => {
+ if (docFirst.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && CollectionFreeFormClusters.overlapping(doc, member, this._clusterDistance)) {
+ docFirst.layout_cluster = i;
+ }
+ })
+ )
+ );
+ if (
+ docFirst.layout_cluster === -1 &&
+ preferredInd !== -1 &&
+ this._clusterSets.length > preferredInd &&
+ (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)
+ ) {
+ docFirst.layout_cluster = preferredInd;
+ }
+ this._clusterSets.forEach((set, i) => {
+ if (docFirst.layout_cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
+ docFirst.layout_cluster = i;
+ }
+ });
+ if (docFirst.layout_cluster === -1) {
+ docs.forEach(doc => {
+ doc.layout_cluster = this._clusterSets.length;
+ this._clusterSets.push([doc]);
+ });
+ } else if (this._clusterSets.length) {
+ for (let i = this._clusterSets.length; i <= NumCast(docFirst.layout_cluster); i++) !this._clusterSets[i] && this._clusterSets.push([]);
+ docs.forEach(doc => {
+ this._clusterSets[(doc.layout_cluster = NumCast(docFirst.layout_cluster))].push(doc);
+ });
+ }
+ childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.layout_cluster === i) && this.addDocument(child));
+ }
+ }
+
+ @action
+ addDocument = (doc: Doc) => {
+ const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
+ if (this.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;
+ this._clusterSets.forEach((set, i) =>
+ set.forEach(member => {
+ if (doc.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && CollectionFreeFormClusters.overlapping(doc, member, this._clusterDistance)) {
+ doc.layout_cluster = i;
+ }
+ })
+ );
+ if (doc.layout_cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
+ doc.layout_cluster = preferredInd;
+ }
+ this._clusterSets.forEach((set, i) => {
+ if (doc.layout_cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
+ doc.layout_cluster = i;
+ }
+ });
+ if (doc.layout_cluster === -1) {
+ doc.layout_cluster = this._clusterSets.length;
+ this._clusterSets.push([doc]);
+ } else if (this._clusterSets.length) {
+ for (let i = this._clusterSets.length; i <= doc.layout_cluster; i++) !this._clusterSets[i] && this._clusterSets.push([]);
+ this._clusterSets[doc.layout_cluster ?? 0].push(doc);
+ }
+ }
+ };
+
+ styleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => {
+ let styleProp = this.viewStyleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1
+ if (doc && this.childDocs?.includes(doc))
+ switch (property.split(':')[0]) {
+ case StyleProp.BackgroundColor:
+ {
+ const cluster = NumCast(doc?.layout_cluster);
+ if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) {
+ if (this._clusterSets.length <= cluster) {
+ setTimeout(() => doc && this.addDocument(doc));
+ } else {
+ // choose a cluster color from a palette
+ const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)'];
+ styleProp = colors[cluster % colors.length];
+ const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
+ // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document
+ set?.forEach(s => {
+ styleProp = StrCast(s.backgroundColor);
+ });
+ }
+ }
+ }
+ break;
+ case StyleProp.FillColor:
+ if (doc && this.Document._currentFrame !== undefined) {
+ return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor;
+ }
+ break;
+ default:
+ }
+ return styleProp;
+ };
+
+ tryToSelect = (addToSel: boolean) => {
+ if (addToSel && this._hitCluster !== -1) {
+ !addToSel && DocumentView.DeselectAll();
+ const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
+ this.selectDocuments(eles);
+ return true;
+ }
+ return false;
+ };
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
index 73dd7fea3..fc39cafaa 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
@@ -5,13 +5,13 @@ import * as React from 'react';
import { SettingsManager } from '../../../util/SettingsManager';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import './CollectionFreeFormView.scss';
-import { Doc } from '../../../../fields/Doc';
/**
* An Fsa Arc. The first array element is a test condition function that will be observed.
* The second array element is a function that will be invoked when the first test function
* returns a truthy value
*/
+// eslint-disable-next-line no-use-before-define
export type infoArc = [() => any, (res?: any) => infoState];
export const StateMessage = Symbol('StateMessage');
@@ -46,6 +46,7 @@ export function InfoState(
gif?: string,
entryFunc?: () => any
) {
+ // eslint-disable-next-line new-cap
return new infoState(msg, arcs, gif, entryFunc);
}
@@ -73,14 +74,15 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
}
clearState = () => this._disposers.map(disposer => disposer());
- initState = () => (this._disposers =
- this.Arcs.map(arc => ({ test: arc[0], act: arc[1] })).map(
- arc => reaction(
- arc.test,
- res => res && this._props.next(arc.act(res)),
- { fireImmediately: true }
- )
- )); // prettier-ignore
+ initState = () => {
+ this._disposers = this.Arcs
+ .map(arc => ({ test: arc[0], act: arc[1] }))
+ .map(arc => reaction(
+ arc.test,
+ res => res && this._props.next(arc.act(res)),
+ { fireImmediately: true }
+ )
+ )}; // prettier-ignore
componentDidMount() {
this.initState();
@@ -97,10 +99,15 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
render() {
const gif = this.State?.[StateMessageGIF];
return (
- <div className={'collectionFreeform-infoUI'}>
+ <div className="collectionFreeform-infoUI">
<p className="collectionFreeform-infoUI-msg">
{this.State?.[StateMessage]}
- <button className={'collectionFreeform-' + (!gif ? 'hidden' : 'infoUI-button')} onClick={action(() => (this._expanded = !this._expanded))}>
+ <button
+ type="button"
+ className={'collectionFreeform-' + (!gif ? 'hidden' : 'infoUI-button')}
+ onClick={action(() => {
+ this._expanded = !this._expanded;
+ })}>
{this._expanded ? 'Less...' : 'More...'}
</button>
</p>
@@ -108,7 +115,7 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
<img src={`/assets/${gif}`} alt="state message gif" />
</div>
<div className="collectionFreeform-infoUI-close">
- <IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(e => this.props.close())} />
+ <IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(() => this.props.close())} />
</div>
</div>
);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index cc729decc..5d8373fc7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -1,26 +1,32 @@
import { makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast, Field, FieldResult } from '../../../../fields/Doc';
+import { Doc, DocListCast, FieldType, FieldResult } from '../../../../fields/Doc';
import { InkTool } from '../../../../fields/InkField';
import { StrCast } from '../../../../fields/Types';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { LinkManager } from '../../../util/LinkManager';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton';
import { TopBar } from '../../topbar/TopBar';
import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState';
-import { CollectionFreeFormView } from './CollectionFreeFormView';
import './CollectionFreeFormView.scss';
+import { DocData } from '../../../../fields/DocSymbols';
+import { CollectionFreeFormView } from './CollectionFreeFormView';
export interface CollectionFreeFormInfoUIProps {
Document: Doc;
- Freeform: CollectionFreeFormView;
- close: () => boolean;
+ LayoutDoc: Doc;
+ childDocs: () => Doc[];
+ close: () => void;
}
@observer
export class CollectionFreeFormInfoUI extends ObservableReactComponent<CollectionFreeFormInfoUIProps> {
+ public static Init() {
+ CollectionFreeFormView.SetInfoUICreator((doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => (
+ //
+ <CollectionFreeFormInfoUI Document={doc} LayoutDoc={layout} childDocs={childDocs} close={close} />
+ ));
+ }
_firstDocPos = { x: 0, y: 0 };
constructor(props: any) {
@@ -32,10 +38,10 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
@observable _currState: infoState | undefined = undefined;
get currState() { return this._currState; } // prettier-ignore
- set currState(val) { runInAction(() => (this._currState = val)); } // prettier-ignore
+ set currState(val) { runInAction(() => {this._currState = val;}); } // prettier-ignore
componentWillUnmount(): void {
- this._props.Freeform.dataDoc.backgroundColor = this._originalbackground;
+ this._props.Document[DocData].backgroundColor = this._originalbackground;
}
setCurrState = (state: infoState) => {
@@ -46,16 +52,16 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
};
setupStates = () => {
- this._originalbackground = StrCast(this._props.Freeform.dataDoc.backgroundColor);
+ this._originalbackground = StrCast(this._props.Document[DocData].backgroundColor);
// state entry functions
- const setBackground = (colour: string) => () => (this._props.Freeform.dataDoc.backgroundColor = colour);
- const setOpacity = (opacity: number) => () => (this._props.Freeform.layoutDoc.opacity = opacity);
+ // const setBackground = (colour: string) => () => {this._props.Document[DocData].backgroundColor = colour;} // prettier-ignore
+ // const setOpacity = (opacity: number) => () => {this._props.LayoutDoc.opacity = opacity;} // prettier-ignore
// arc transition trigger conditions
- const firstDoc = () => (this._props.Freeform.childDocs.length ? this._props.Freeform.childDocs[0] : undefined);
- const numDocs = () => this._props.Freeform.childDocs.length;
+ const firstDoc = () => (this._props.childDocs().length ? this._props.childDocs()[0] : undefined);
+ const numDocs = () => this._props.childDocs().length;
- let docX: FieldResult<Field>;
- let docY: FieldResult<Field>;
+ let docX: FieldResult<FieldType>;
+ let docY: FieldResult<FieldType>;
const docNewX = () => firstDoc()?.x;
const docNewY = () => firstDoc()?.y;
@@ -63,7 +69,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
const linkStart = () => DocumentLinksButton.StartLink;
const linkUnstart = () => !DocumentLinksButton.StartLink;
- const numDocLinks = () => LinkManager.Instance.getAllDirectLinks(firstDoc())?.length;
+ const numDocLinks = () => Doc.Links(firstDoc())?.length;
const linkMenuOpen = () => DocButtonState.Instance.LinkEditorDocView;
const activeTool = () => Doc.ActiveTool;
@@ -72,7 +78,6 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
let trail: number;
- const trailView = () => DocumentManager.Instance.DocumentViews.find(view => view.Document === Doc.MyTrails);
const presentationMode = () => Doc.ActivePresentation?.presentation_status;
// set of states
@@ -82,6 +87,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
docCreated: [() => numDocs(), () => {
docX = firstDoc()?.x;
docY = firstDoc()?.y;
+ // eslint-disable-next-line no-use-before-define
return oneDoc;
}],
}
@@ -92,18 +98,20 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
{
// docCreated: [() => numDocs() > 1, () => multipleDocs],
docDeleted: [() => numDocs() < 1, () => start],
- docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => {
+ docMoved: [() => (docX && docX !== docNewX()) || (docY && docY !== docNewY()), () => {
docX = firstDoc()?.x;
docY = firstDoc()?.y;
+ // eslint-disable-next-line no-use-before-define
return movedDoc;
}],
}
); // prettier-ignore
const movedDoc = InfoState(
- 'Great moves. Try creating a second document. You can see the list of supported document types by typing a colon (\":\")',
+ 'Great moves. Try creating a second document. You can see the list of supported document types by typing a colon (":")',
{
- docCreated: [() => numDocs() == 2, () => multipleDocs],
+ // eslint-disable-next-line no-use-before-define
+ docCreated: [() => numDocs() === 2, () => multipleDocs],
docDeleted: [() => numDocs() < 1, () => start],
},
'dash-colon-menu.gif',
@@ -113,6 +121,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
const multipleDocs = InfoState(
'Let\'s create a new link. Click the link icon on one of your documents.',
{
+ // eslint-disable-next-line no-use-before-define
linkStarted: [() => linkStart(), () => startedLink],
docRemoved: [() => numDocs() < 2, () => oneDoc],
},
@@ -123,6 +132,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
'Now click the highlighted link icon on your other document.',
{
linkUnstart: [() => linkUnstart(), () => multipleDocs],
+ // eslint-disable-next-line no-use-before-define
linkCreated: [() => numDocLinks(), () => madeLink],
docRemoved: [() => numDocs() < 2, () => oneDoc],
},
@@ -135,6 +145,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
linkCreated: [() => !numDocLinks(), () => multipleDocs],
linkViewed: [() => linkMenuOpen(), () => {
alert(numDocLinks() + " cheer for " + numDocLinks() + " link!");
+ // eslint-disable-next-line no-use-before-define
return viewedLink;
}],
},
@@ -146,10 +157,12 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
{
linkDeleted: [() => !numDocLinks(), () => multipleDocs],
docRemoved: [() => numDocs() < 2, () => oneDoc],
- docCreated: [() => numDocs() == 3, () => {
+ docCreated: [() => numDocs() === 3, () => {
trail = pin().length;
+ // eslint-disable-next-line no-use-before-define
return presentDocs;
}],
+ // eslint-disable-next-line no-use-before-define
activePen: [() => activeTool() === InkTool.Pen, () => penMode],
},
'documentation.png',
@@ -163,6 +176,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
() => pin().length > trail,
() => {
trail = pin().length;
+ // eslint-disable-next-line no-use-before-define
return pinnedDoc1;
},
],
@@ -185,11 +199,13 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
() => pin().length > trail,
() => {
trail = pin().length;
+ // eslint-disable-next-line no-use-before-define
return pinnedDoc2;
},
],
// editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
// manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode],
+ // eslint-disable-next-line no-use-before-define
autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode],
docRemoved: [() => numDocs() < 3, () => viewedLink],
});
@@ -199,11 +215,13 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
() => pin().length > trail,
() => {
trail = pin().length;
+ // eslint-disable-next-line no-use-before-define
return pinnedDoc3;
},
],
// editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
// manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode],
+ // eslint-disable-next-line no-use-before-define
autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode],
docRemoved: [() => numDocs() < 3, () => viewedLink],
});
@@ -218,6 +236,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
],
// editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
// manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode],
+ // eslint-disable-next-line no-use-before-define
autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode],
docRemoved: [() => numDocs() < 3, () => viewedLink],
});
@@ -235,21 +254,24 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
const manualPresentationMode = InfoState("You're in manual presentation mode.", {
// editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
+ // eslint-disable-next-line no-use-before-define
autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode],
docRemoved: [() => numDocs() < 3, () => viewedLink],
- docCreated: [() => numDocs() == 4, () => completed],
+ // eslint-disable-next-line no-use-before-define
+ docCreated: [() => numDocs() === 4, () => completed],
});
const autoPresentationMode = InfoState("You're in auto presentation mode.", {
// editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode],
docRemoved: [() => numDocs() < 3, () => viewedLink],
- docCreated: [() => numDocs() == 4, () => completed],
+ // eslint-disable-next-line no-use-before-define
+ docCreated: [() => numDocs() === 4, () => completed],
});
const completed = InfoState(
'Eager to learn more? Click the ? icon in the top right corner to read our full documentation.',
- { docRemoved: [() => numDocs() == 1, () => oneDoc] },
+ { docRemoved: [() => numDocs() === 1, () => oneDoc] },
'documentation.png',
() => TopBar.Instance.FlipDocumentationIcon()
); // prettier-ignore
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index c83c26509..a4496a417 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -1,4 +1,5 @@
-import { Doc, Field, FieldResult } from '../../../../fields/Doc';
+/* eslint-disable no-use-before-define */
+import { Doc, Field, FieldType, FieldResult } from '../../../../fields/Doc';
import { Id, ToString } from '../../../../fields/FieldSymbols';
import { ObjectField } from '../../../../fields/ObjectField';
import { RefField } from '../../../../fields/RefField';
@@ -48,9 +49,9 @@ export interface PoolData {
export interface ViewDefResult {
ele: JSX.Element;
bounds?: ViewDefBounds;
- inkMask?: number; //sort elements into either the mask layer (which has a mixedBlendMode appropriate for transparent masks), or the regular documents layer; -1 = no mask, 0 = mask layer but stroke is transparent (hidden, as in during a presentation when you want to smoothly animate it into being a mask), >0 = mask layer and not hidden
+ inkMask?: number; // sort elements into either the mask layer (which has a mixedBlendMode appropriate for transparent masks), or the regular documents layer; -1 = no mask, 0 = mask layer but stroke is transparent (hidden, as in during a presentation when you want to smoothly animate it into being a mask), >0 = mask layer and not hidden
}
-function toLabel(target: FieldResult<Field>) {
+function toLabel(target: FieldResult<FieldType>) {
if (typeof target === 'number' || Number(target)) {
const truncated = Number(Number(target).toFixed(0));
const precise = Number(Number(target).toFixed(2));
@@ -84,9 +85,9 @@ interface PivotColumn {
filters: string[];
}
-export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
+export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] /* , engineProps: any */) {
const docMap = new Map<string, PoolData>();
- childPairs.forEach(({ layout, data }, i) => {
+ childPairs.forEach(({ layout, data }) => {
docMap.set(layout[Id], {
x: NumCast(layout.x),
y: NumCast(layout.y),
@@ -97,10 +98,15 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
replica: '',
});
});
+ // eslint-disable-next-line no-use-before-define
return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []);
}
-export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
+function toNumber(val: FieldResult<FieldType>) {
+ return val === undefined ? undefined : NumCast(val, Number(StrCast(val)));
+}
+
+export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] /* , engineProps: any */) {
const docMap = new Map<string, PoolData>();
const burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)];
const burstScale = NumCast(pivotDoc._starburstDocScale, 1);
@@ -128,23 +134,23 @@ export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc
export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
const docMap = new Map<string, PoolData>();
const fieldKey = 'data';
- const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
+ const pivotColumnGroups = new Map<FieldResult<FieldType>, PivotColumn>();
let nonNumbers = 0;
const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || 'author';
- childPairs.map(pair => {
+ childPairs.forEach(pair => {
const listValue = Cast(pair.layout[pivotFieldKey], listSpec('string'), null);
const num = toNumber(pair.layout[pivotFieldKey]);
- if (num === undefined || Number.isNaN(num)) {
+ if (num === undefined || isNaN(num)) {
nonNumbers++;
}
- const val = Field.toString(pair.layout[pivotFieldKey] as Field);
+ const val = Field.toString(pair.layout[pivotFieldKey] as FieldType);
if (listValue) {
- listValue.forEach((val, i) => {
- !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
- pivotColumnGroups.get(val)!.docs.push(pair.layout);
- pivotColumnGroups.get(val)!.replicas.push(i.toString());
+ listValue.forEach((lval, i) => {
+ !pivotColumnGroups.get(lval) && pivotColumnGroups.set(lval, { docs: [], filters: [lval], replicas: [] });
+ pivotColumnGroups.get(lval)!.docs.push(pair.layout);
+ pivotColumnGroups.get(lval)!.replicas.push(i.toString());
});
} else if (val) {
!pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
@@ -184,11 +190,11 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
const textlen = Array.from(pivotColumnGroups.keys())
.map(c => getTextWidth(toLabel(c), desc))
.reduce((p, c) => Math.max(p, c), 0 as number);
- const max_text = Math.min(Math.ceil(textlen / 120) * 28, panelDim[1] / 2);
+ const maxText = Math.min(Math.ceil(textlen / 120) * 28, panelDim[1] / 2);
const maxInColumn = Array.from(pivotColumnGroups.values()).reduce((p, s) => Math.max(p, s.docs.length), 1);
const colWidth = panelDim[0] / pivotColumnGroups.size;
- const colHeight = panelDim[1] - max_text;
+ const colHeight = panelDim[1] - maxText;
let numCols = 0;
let bestArea = 0;
let pivotAxisWidth = 0;
@@ -212,7 +218,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
let x = 0;
const sortedPivotKeys = pivotNumbers ? Array.from(pivotColumnGroups.keys()).sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : Array.from(pivotColumnGroups.keys()).sort();
sortedPivotKeys.forEach(key => {
- const val = pivotColumnGroups.get(key)!;
+ const val = pivotColumnGroups.get(key);
let y = 0;
let xCount = 0;
const text = toLabel(key);
@@ -222,11 +228,11 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
x,
y: pivotAxisWidth,
width: pivotAxisWidth * expander * numCols,
- height: max_text,
+ height: maxText,
fontSize,
payload: val,
});
- val.docs.forEach((doc, i) => {
+ val?.docs.forEach((doc, i) => {
const layoutDoc = Doc.Layout(doc);
let wid = pivotAxisWidth;
let hgt = pivotAxisWidth / (Doc.NativeAspect(layoutDoc) || 1);
@@ -262,19 +268,16 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
payload: pivotColumnGroups.get(key)!.filters,
}));
groupNames.push(...dividers);
- return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
-}
-
-function toNumber(val: FieldResult<Field>) {
- return val === undefined ? undefined : NumCast(val, Number(StrCast(val)));
+ // eslint-disable-next-line no-use-before-define
+ return normalizeResults(panelDim, maxText, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
}
-export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps?: any) {
+export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] /* , engineProps?: any */) {
const fieldKey = 'data';
const pivotDateGroups = new Map<number, Doc[]>();
const docMap = new Map<string, PoolData>();
const groupNames: ViewDefBounds[] = [];
- const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field);
+ const timelineFieldKey = Field.toString(pivotDoc._pivotField as FieldType);
const curTime = toNumber(pivotDoc[fieldKey + '-timelineCur']);
const curTimeSpan = Cast(pivotDoc[fieldKey + '-timelineSpan'], 'number', null);
const minTimeReq = curTimeSpan === undefined ? Cast(pivotDoc[fieldKey + '-timelineMinReq'], 'number', null) : curTime && curTime - curTimeSpan;
@@ -290,7 +293,7 @@ export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc:
let maxTime = maxTimeReq === undefined ? -Number.MAX_VALUE : maxTimeReq;
childPairs.forEach(pair => {
const num = NumCast(pair.layout[timelineFieldKey], Number(StrCast(pair.layout[timelineFieldKey])));
- if (!Number.isNaN(num) && (!minTimeReq || num >= minTimeReq) && (!maxTimeReq || num <= maxTimeReq)) {
+ if (!isNaN(num) && (!minTimeReq || num >= minTimeReq) && (!maxTimeReq || num <= maxTimeReq)) {
!pivotDateGroups.get(num) && pivotDateGroups.set(num, []);
pivotDateGroups.get(num)!.push(pair.layout);
minTime = Math.min(num, minTime);
@@ -340,6 +343,7 @@ export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc:
if (!stack && (curTime === undefined || Math.abs(x - (curTime - minTime) * scaling) > pivotAxisWidth)) {
groupNames.push({ type: 'text', text: toLabel(key), x: x, y: stack * 25, height: fontHeight, fontSize, payload: undefined });
}
+ // eslint-disable-next-line no-use-before-define
layoutDocsAtTime(keyDocs, key);
});
if (sortedKeys.length && curTime !== undefined && curTime > sortedKeys[sortedKeys.length - 1]) {
@@ -400,11 +404,11 @@ function normalizeResults(
const height = aggBounds.b - aggBounds.y === 0 ? 1 : aggBounds.b - aggBounds.y;
const wscale = panelDim[0] / width;
let scale = wscale * height > panelDim[1] ? panelDim[1] / height : wscale;
- if (Number.isNaN(scale)) scale = 1;
+ if (isNaN(scale)) scale = 1;
Array.from(docMap.entries())
.filter(ele => ele[1].pair)
- .map(ele => {
+ .forEach(ele => {
const newPosRaw = ele[1];
if (newPosRaw) {
const newPos: PoolData = {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
index 69cbae86f..65a2fe0aa 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
@@ -3,27 +3,32 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc } from '../../../../fields/Doc';
import { ScriptField } from '../../../../fields/ScriptField';
-import { PresBox } from '../../nodes/trails/PresBox';
-import { CollectionFreeFormView } from './CollectionFreeFormView';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
import './CollectionFreeFormView.scss';
+
export interface CollectionFreeFormPannableContentsProps {
Document: Doc;
viewDefDivClick?: ScriptField;
children?: React.ReactNode | undefined;
- transition?: string;
+ transition: () => string;
isAnnotationOverlay: boolean | undefined;
+ showPresPaths: () => boolean;
transform: () => string;
brushedView: () => { panX: number; panY: number; width: number; height: number } | undefined;
}
@observer
-export class CollectionFreeFormPannableContents extends React.Component<CollectionFreeFormPannableContentsProps> {
+export class CollectionFreeFormPannableContents extends ObservableReactComponent<CollectionFreeFormPannableContentsProps> {
+ static _overlayPlugin: ((fform: Doc) => React.JSX.Element) | null = null;
+ public static SetOverlayPlugin(plugin: ((fform: Doc) => React.JSX.Element) | null) {
+ CollectionFreeFormPannableContents._overlayPlugin = plugin;
+ }
constructor(props: CollectionFreeFormPannableContentsProps) {
super(props);
makeObservable(this);
}
@computed get presPaths() {
- return CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.pathLines(this.props.Document) : null;
+ return this._props.showPresPaths() ? CollectionFreeFormPannableContents._overlayPlugin?.(this._props.Document) : null;
}
// rectangle highlight used when following trail/link to a region of a collection that isn't a document
showViewport = (viewport: { panX: number; panY: number; width: number; height: number } | undefined) =>
@@ -42,7 +47,7 @@ export class CollectionFreeFormPannableContents extends React.Component<Collecti
render() {
return (
<div
- className={'collectionfreeformview' + (this.props.viewDefDivClick ? '-viewDef' : '-none')}
+ className={'collectionfreeformview' + (this._props.viewDefDivClick ? '-viewDef' : '-none')}
onScroll={e => {
const target = e.target as any;
if (getComputedStyle(target)?.overflow === 'visible') {
@@ -50,13 +55,13 @@ export class CollectionFreeFormPannableContents extends React.Component<Collecti
}
}}
style={{
- transform: this.props.transform(),
- transition: this.props.transition,
- width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
+ transform: this._props.transform(),
+ transition: this._props.transition(),
+ width: this._props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
}}>
{this.props.children}
{this.presPaths}
- {this.showViewport(this.props.brushedView())}
+ {this.showViewport(this._props.brushedView())}
</div>
);
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index fa8218bdd..f64c6715b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -4,26 +4,22 @@ import * as mobxUtils from 'mobx-utils';
import * as React from 'react';
import * as uuid from 'uuid';
import CursorField from '../../../../fields/CursorField';
-import { Doc, FieldResult } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
-import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
import { Cast } from '../../../../fields/Types';
-import { CollectionViewProps } from '../CollectionView';
+import { CollectionViewProps } from '../CollectionSubView';
import './CollectionFreeFormView.scss';
@observer
export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
@computed protected get cursors(): CursorField[] {
- const doc = this.props.Document;
-
- let cursors: FieldResult<List<CursorField>>;
- const id = Doc.UserDoc()[Id];
- if (!id || !(cursors = Cast(doc.cursors, listSpec(CursorField)))) {
+ const { Document } = this.props;
+ const cursors = Cast(Document.cursors, listSpec(CursorField));
+ if (!cursors) {
return [];
}
const now = mobxUtils.now();
- return (cursors || []).filter(({ data: { metadata } }) => metadata.id !== id && now - metadata.timestamp < 1000);
+ return (cursors || []).filter(({ data: { metadata } }) => metadata.id !== Document[Id] && now - metadata.timestamp < 1000);
}
@computed get renderedCursors() {
@@ -33,46 +29,44 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
metadata,
position: { x, y },
},
- }) => {
- return (
- <div key={metadata.id} className="collectionFreeFormRemoteCursors-cont" style={{ transform: `translate(${x - 10}px, ${y - 10}px)` }}>
- <canvas
- className="collectionFreeFormRemoteCursors-canvas"
- ref={el => {
- if (el) {
- const ctx = el.getContext('2d');
- if (ctx) {
- ctx.fillStyle = '#' + uuid.v5(metadata.id, uuid.v5.URL).substring(0, 6).toUpperCase() + '22';
- ctx.fillRect(0, 0, 20, 20);
+ }) => (
+ <div key={metadata.id} className="collectionFreeFormRemoteCursors-cont" style={{ transform: `translate(${x - 10}px, ${y - 10}px)` }}>
+ <canvas
+ className="collectionFreeFormRemoteCursors-canvas"
+ ref={el => {
+ if (el) {
+ const ctx = el.getContext('2d');
+ if (ctx) {
+ ctx.fillStyle = '#' + uuid.v5(metadata.id, uuid.v5.URL).substring(0, 6).toUpperCase() + '22';
+ ctx.fillRect(0, 0, 20, 20);
- ctx.fillStyle = 'black';
- ctx.lineWidth = 0.5;
+ ctx.fillStyle = 'black';
+ ctx.lineWidth = 0.5;
- ctx.beginPath();
+ ctx.beginPath();
- ctx.moveTo(10, 0);
- ctx.lineTo(10, 8);
+ ctx.moveTo(10, 0);
+ ctx.lineTo(10, 8);
- ctx.moveTo(10, 20);
- ctx.lineTo(10, 12);
+ ctx.moveTo(10, 20);
+ ctx.lineTo(10, 12);
- ctx.moveTo(0, 10);
- ctx.lineTo(8, 10);
+ ctx.moveTo(0, 10);
+ ctx.lineTo(8, 10);
- ctx.moveTo(20, 10);
- ctx.lineTo(12, 10);
+ ctx.moveTo(20, 10);
+ ctx.lineTo(12, 10);
- ctx.stroke();
- }
+ ctx.stroke();
}
- }}
- width={20}
- height={20}
- />
- <p className="collectionFreeFormRemoteCursors-symbol">{metadata.identifier[0].toUpperCase()}</p>
- </div>
- );
- }
+ }
+ }}
+ width={20}
+ height={20}
+ />
+ <p className="collectionFreeFormRemoteCursors-symbol">{metadata.identifier[0].toUpperCase()}</p>
+ </div>
+ )
);
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 079a5d977..64398a60a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,60 +1,68 @@
+/* eslint-disable react/jsx-props-no-spreading */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { Bezier } from 'bezier-js';
import { Colors } from 'browndash-components';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import * as React from 'react';
+import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../../ClientUtils';
import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
+import { ActiveInkWidth, Doc, DocListCast, Field, FieldType, Opt, SetActiveInkColor, SetActiveInkWidth } from '../../../../fields/Doc';
import { DocData, Height, Width } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
-import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField';
+import { InkData, InkField, InkTool, Segment } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
import { RichTextField } from '../../../../fields/RichTextField';
import { listSpec } from '../../../../fields/Schema';
import { ScriptField } from '../../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast, toList } from '../../../../fields/Types';
import { ImageField } from '../../../../fields/URLField';
import { TraceMobx } from '../../../../fields/util';
+import { Gestures, PointData } from '../../../../pen-gestures/GestureTypes';
import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
-import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
-import { Docs, DocUtils } from '../../../documents/Documents';
+import { aggregateBounds, emptyFunction, intersectRect, Utils } from '../../../../Utils';
+import { Docs } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DocUtils } from '../../../documents/DocUtils';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
import { ReplayMovements } from '../../../util/ReplayMovements';
import { CompileScript } from '../../../util/Scripting';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
-import { SelectionManager } from '../../../util/SelectionManager';
-import { freeformScrollMode } from '../../../util/SettingsManager';
-import { SnappingManager } from '../../../util/SnappingManager';
+import { freeformScrollMode, SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
import { Timeline } from '../../animationtimeline/Timeline';
import { ContextMenu } from '../../ContextMenu';
import { GestureOverlay } from '../../GestureOverlay';
-import { CtrlKey } from '../../GlobalKeyHandler';
-import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
+import { InkingStroke } from '../../InkingStroke';
import { LightboxView } from '../../LightboxView';
import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
-import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
-import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView';
+import { DocumentView } from '../../nodes/DocumentView';
+import { FieldViewProps } from '../../nodes/FieldView';
+import { FocusViewOptions } from '../../nodes/FocusViewOptions';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
-import { PinProps, PresBox } from '../../nodes/trails/PresBox';
-import { CreateImage } from '../../nodes/WebBoxRenderer';
-import { StyleProp } from '../../StyleProvider';
+import { OpenWhere } from '../../nodes/OpenWhere';
+import { PinDocView, PinProps } from '../../PinFuncs';
+import { StyleProp } from '../../StyleProp';
import { CollectionSubView } from '../CollectionSubView';
-import { TreeViewType } from '../CollectionTreeView';
+import { TreeViewType } from '../CollectionTreeViewType';
import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid';
-import { CollectionFreeFormInfoUI } from './CollectionFreeFormInfoUI';
+import { CollectionFreeFormClusters } from './CollectionFreeFormClusters';
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';
+class CollectionFreeFormOverlayView extends React.Component<{ elements: () => ViewDefResult[] }> {
+ render() {
+ return this.props.elements().filter(ele => ele.bounds?.z).map(ele => ele.ele); // prettier-ignore
+ }
+}
export interface collectionFreeformViewProps {
NativeWidth?: () => number;
NativeHeight?: () => number;
@@ -71,26 +79,21 @@ export interface collectionFreeformViewProps {
@observer
export class CollectionFreeFormView extends CollectionSubView<Partial<collectionFreeformViewProps>>() {
public get displayName() {
- return 'CollectionFreeFormView(' + this.Document.title?.toString() + ')';
+ return 'CollectionFreeFormView(' + (this.Document.title?.toString() ?? '') + ')';
} // this makes mobx trace() statements more descriptive
-
- @observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, '');
- @computed get paintFunc() {
- const field = this.dataDoc[this.fieldKey];
- const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as Field)).trim();
- return !paintFunc
- ? ''
- : paintFunc.includes('dashDiv')
- ? `const dashDiv = document.querySelector('#${this._paintedId}');
- (async () => { ${paintFunc} })()`
- : paintFunc;
+ public unprocessedDocs: Doc[] = [];
+ public static collectionsWithUnprocessedInk = new Set<CollectionFreeFormView>();
+ public static from(dv?: DocumentView): CollectionFreeFormView | undefined {
+ const parent = CollectionFreeFormDocumentView.from(dv)?._props.parent;
+ return parent instanceof CollectionFreeFormView ? parent : undefined;
}
+
+ _oldWheel: any;
+ _clusters = new CollectionFreeFormClusters(this);
constructor(props: any) {
super(props);
makeObservable(this);
}
- @observable
- public static ShowPresPaths = false;
private _panZoomTransitionTimer: any;
private _lastX: number = 0;
@@ -98,41 +101,48 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
private _downX: number = 0;
private _downY: number = 0;
private _downTime = 0;
- private _clusterDistance: number = 75;
- private _hitCluster: number = -1;
private _disposers: { [name: string]: IReactionDisposer } = {};
private _renderCutoffData = observable.map<string, boolean>();
private _batch: UndoManager.Batch | undefined = undefined;
private _brushtimer: any;
private _brushtimer1: any;
+ private _eraserLock = 0;
+ private _keyTimer: NodeJS.Timeout | undefined; // timer for turning off transition flag when key frame change has completed. Need to clear this if you do a second navigation before first finishes, or else first timer can go off during second naviation.
- public get isAnnotationOverlay() {
- return this._props.isAnnotationOverlay;
- }
- public get scaleFieldKey() {
- return (this._props.viewField ?? '') + '_freeform_scale';
- }
- private get panXFieldKey() {
- return (this._props.viewField ?? '') + '_freeform_panX';
- }
- private get panYFieldKey() {
- return (this._props.viewField ?? '') + '_freeform_panY';
- }
- private get autoResetFieldKey() {
- return (this._props.viewField ?? '') + '_freeform_autoReset';
- }
+ private get isAnnotationOverlay() { return this._props.isAnnotationOverlay; } // prettier-ignore
+ private get scaleFieldKey() { return (this._props.viewField ?? '') + '_freeform_scale'; } // prettier-ignore
+ private get panXFieldKey() { return (this._props.viewField ?? '') + '_freeform_panX'; } // prettier-ignore
+ private get panYFieldKey() { return (this._props.viewField ?? '') + '_freeform_panY'; } // prettier-ignore
+ private get autoResetFieldKey() { return (this._props.viewField ?? '') + '_freeform_autoReset'; } // prettier-ignore
@observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
@observable _panZoomTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan. set to 0 to use document's transition time or default of 0
@observable _firstRender = false; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown. could be used for performance improvement
@observable _showAnimTimeline = false;
- @observable _clusterSets: Doc[][] = [];
@observable _deleteList: DocumentView[] = [];
@observable _timelineRef = React.createRef<Timeline>();
@observable _marqueeViewRef = React.createRef<MarqueeView>();
@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.
+ @observable _childPointerEvents: 'none' | 'all' | 'visiblepainted' | undefined = undefined;
+ @observable _lightboxDoc: Opt<Doc> = undefined;
+ @observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, '');
+ @observable _keyframeEditing = false;
+ @computed get layoutEngine() {
+ return this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
+ }
+ @computed get childPointerEvents() {
+ const engine = this._props.layoutEngine?.() || StrCast(this.Document._layoutEngine);
+ return SnappingManager.IsResizing
+ ? 'none'
+ : this._props.childPointerEvents?.() ??
+ (this._props.viewDefDivClick || //
+ (engine === computePassLayout.name && !this._props.isSelected()) ||
+ this.isContentActive() === false
+ ? 'none'
+ : this._props.pointerEvents?.());
+ }
@computed get contentViews() {
const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1 && ele.inkMask !== undefined).map(ele => ele.ele);
const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && (ele.inkMask === -1 || ele.inkMask === undefined)).map(ele => ele.ele);
@@ -154,14 +164,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return (this._props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay;
}
@computed get contentBounds() {
- const cb = Cast(this.dataDoc.contentBounds, listSpec('number'));
- return cb
- ? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] }
- : aggregateBounds(
- this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!),
- NumCast(this.layoutDoc._xPadding, this._props.xPadding ?? 10),
- NumCast(this.layoutDoc._yPadding, this._props.yPadding ?? 10)
- );
+ return aggregateBounds(
+ this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!),
+ NumCast(this.layoutDoc._xPadding, this._props.xPadding ?? 10),
+ NumCast(this.layoutDoc._yPadding, this._props.yPadding ?? 10)
+ );
}
@computed get nativeWidth() {
return this._props.NativeWidth?.() || Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
@@ -192,31 +199,31 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
.translate(-this.centeringShiftX, -this.centeringShiftY)
.transform(this.panZoomXf);
}
+ @computed get backgroundColor() {
+ return this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor);
+ }
+ @computed get nativeDimScaling() {
+ if (this._firstRender || (this._props.isAnnotationOverlay && !this._props.annotationLayerHostsContent)) return 1;
+ 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._props.layout_fitWidth?.(this.Document) ?? this.layoutDoc.layout_fitWidth) ? wscale : hscale;
+ }
+ @computed get paintFunc() {
+ const field = this.dataDoc[this.fieldKey];
+ const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as FieldType)).trim();
+ return !paintFunc
+ ? ''
+ : paintFunc.includes('dashDiv')
+ ? `const dashDiv = document.querySelector('#${this._paintedId}');
+ (async () => { ${paintFunc} })()`
+ : paintFunc;
+ }
public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) {
return DocumentView.SetViewTransition(docs, 'all', duration, timer, undefined, true);
}
- public static updateKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], time: number) {
- const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, timer, undefined, true);
- const timecode = Math.round(time);
- docs.forEach(doc => {
- CollectionFreeFormDocumentView.animFields.forEach(val => {
- 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);
- 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);
- findexed?.length <= timecode + 1 && findexed.push(undefined as any);
- });
- });
- return newTimer;
- }
-
- _keyTimer: NodeJS.Timeout | undefined; // timer for turning off transition flag when key frame change has completed. Need to clear this if you do a second navigation before first finishes, or else first timer can go off during second naviation.
changeKeyFrame = (back = false) => {
const currentFrame = Cast(this.Document._currentFrame, 'number', null);
if (currentFrame === undefined) {
@@ -227,19 +234,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], 1000);
this.Document._currentFrame = Math.max(0, (currentFrame || 0) - 1);
} else {
- this._keyTimer = CollectionFreeFormView.updateKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], currentFrame || 0);
+ this._keyTimer = CollectionFreeFormDocumentView.updateKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], currentFrame || 0);
this.Document._currentFrame = Math.max(0, (currentFrame || 0) + 1);
this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame));
}
};
- @observable _keyframeEditing = false;
- @action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set);
+ @action setKeyFrameEditing = (set: boolean) => {
+ this._keyframeEditing = set;
+ };
getKeyFrameEditing = () => this._keyframeEditing;
- onBrowseClickHandler = () => this._props.onBrowseClickScript?.() || ScriptCast(this.layoutDoc.onBrowseClick);
+
onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
elementFunc = () => this._layoutElements;
viewTransition = () => (this._panZoomTransition ? '' + this._panZoomTransition : undefined);
+ panZoomTransition = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null)));
fitContentOnce = () => {
const vals = this.fitToContentVals;
this.layoutDoc._freeform_panX = vals.bounds.cx;
@@ -251,41 +260,44 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1));
panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1));
- zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); //, NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1));
+ zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); // , NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1));
PanZoomCenterXf = () => (this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.centeringShiftX}px, ${this.centeringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`);
ScreenToContentsXf = () => this.screenToFreeformContentsXf.copy();
getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
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
+ Doc.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.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true));
+ DocumentView.DeselectAll();
+ docs.map(doc => DocumentView.getDocumentView(doc, this.DocumentView?.())).forEach(dv => dv && DocumentView.SelectView(dv, true));
};
addDocument = (newBox: Doc | Doc[]) => {
let retVal = false;
if (newBox instanceof Doc) {
- if ((retVal = this._props.addDocument?.(newBox) || false)) {
+ retVal = this._props.addDocument?.(newBox) || false;
+ if (retVal) {
this.bringToFront(newBox);
- this.updateCluster(newBox);
+ this._clusters.addDocument(newBox);
}
} else {
retVal = this._props.addDocument?.(newBox) || false;
// bcz: deal with clusters
}
if (retVal) {
- const newBoxes = newBox instanceof Doc ? [newBox] : newBox;
- for (const newBox of newBoxes) {
- if (newBox.activeFrame !== undefined) {
- const vals = CollectionFreeFormDocumentView.animFields.map(field => newBox[field.key]);
- CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[`${field.key}_indexed`]);
- CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[field.key]);
- delete newBox.activeFrame;
- CollectionFreeFormDocumentView.animFields.forEach((field, i) => field.key !== 'opacity' && (newBox[field.key] = vals[i]));
+ const newBoxes = toList(newBox);
+ newBoxes.forEach(box => {
+ if (box.activeFrame !== undefined) {
+ const vals = CollectionFreeFormDocumentView.animFields.map(field => box[field.key]);
+ CollectionFreeFormDocumentView.animFields.forEach(field => delete box[`${field.key}_indexed`]);
+ CollectionFreeFormDocumentView.animFields.forEach(field => delete box[field.key]);
+ delete box.activeFrame;
+ CollectionFreeFormDocumentView.animFields.forEach((field, i) => {
+ field.key !== 'opacity' && (box[field.key] = vals[i]);
+ });
}
- }
+ });
if (this.Document._currentFrame !== undefined && !this._props.isAnnotationOverlay) {
CollectionFreeFormDocumentView.setupKeyframes(newBoxes, NumCast(this.Document._currentFrame), true);
}
@@ -300,22 +312,63 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return dispTime === -1 || curTime === -1 || (curTime - dispTime >= -1e-4 && curTime <= endTime);
}
+ /**
+ * focuses on a specified point in the freeform coordinate space. (alternative to focusing on a Document)
+ * @param options
+ * @returns how long a transition it will be to focus on the point, or undefined the doc is a group or something else already moved
+ */
+ focusOnPoint = (options: FocusViewOptions) => {
+ const { pointFocus, zoomTime, didMove } = options;
+ if (!this.Document.isGroup && pointFocus && !didMove) {
+ const dfltScale = this.isAnnotationOverlay ? 1 : 0.5;
+ if (this.layoutDoc[this.scaleFieldKey] !== dfltScale) {
+ this.zoomSmoothlyAboutPt(this.screenToFreeformContentsXf.transformPoint(pointFocus.X, pointFocus.Y), dfltScale, zoomTime);
+ options.didMove = true;
+ return zoomTime;
+ }
+ }
+ return undefined;
+ };
+
+ /**
+ * Focusing on a member of a group -
+ * Since groups can't pan and zoom like regular collections, this method focuses on a Doc in a group by
+ * focusing on the group with an additional transformation to force the final focus to be on the center of the group item.
+ * @param anchor
+ * @param options
+ * @returns
+ */
groupFocus = (anchor: Doc, options: FocusViewOptions) => {
- options.docTransform = new Transform(-NumCast(this.layoutDoc[this.panXFieldKey]) + NumCast(anchor.x), -NumCast(this.layoutDoc[this.panYFieldKey]) + NumCast(anchor.y), 1);
+ if (options.pointFocus) return undefined;
+ options.docTransform = new Transform(NumCast(anchor.x) + NumCast(anchor._width)/2 - NumCast(this.layoutDoc[this.panXFieldKey]),
+ NumCast(anchor.y) + NumCast(anchor._height)/2- NumCast(this.layoutDoc[this.panYFieldKey]), 1); // prettier-ignore
const res = this._props.focus(this.Document, options);
options.docTransform = undefined;
return res;
};
- focus = (anchor: Doc, options: FocusViewOptions) => {
- if (this._lightboxDoc) return;
- if (anchor === this.Document) {
- // if (options.willZoomCentered && options.zoomScale) {
- // this.fitContentOnce();
- // options.didMove = true;
- // }
+ /**
+ * focuses the freeform view on the anchor subject to options.
+ * If a pointFocus is specified, then groupFocus is triggered instad
+ * Otherwise, this shifts the pan and zoom to the anchor target (as specified by options).
+ * NOTE: focusing on a group only has an effet if the options contextPath is empty.
+ * @param anchor
+ * @param options
+ * @returns
+ */
+ focus = (anchor: Doc, options: FocusViewOptions): any => {
+ if (anchor.isGroup && !options.docTransform && options.contextPath?.length) {
+ // don't focus on group if there's a context path because we're about to focus on a group item
+ // which will override any group focus. (If we allowed the group to focus, it would mark didMove even if there were no net movement)
+ return undefined;
+ }
+ if (this._lightboxDoc) return undefined;
+ if (options.pointFocus) return this.focusOnPoint(options);
+ const anchorInCollection = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor);
+ const anchorInChildViews = this.childLayoutPairs.map(pair => pair.layout).includes(anchor);
+ if (!anchorInCollection && !anchorInChildViews) {
+ return undefined;
}
- if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor) && !this.childLayoutPairs.map(pair => pair.layout).includes(anchor)) return;
const xfToCollection = options?.docTransform ?? Transform.Identity();
const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
const cantTransform = this.fitContentsToBox || ((this.Document.isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
@@ -331,13 +384,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.setPan(panX, panY, focusTime, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
return focusTime;
}
+ return undefined;
};
getView = async (doc: Doc, options: FocusViewOptions): Promise<Opt<DocumentView>> =>
new Promise<Opt<DocumentView>>(res => {
if (doc.hidden && this._lightboxDoc !== doc) options.didMove = !(doc.hidden = false);
- if (doc === this.Document) return res(this.DocumentView?.());
- const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
+ if (doc === this.Document) {
+ res(this.DocumentView?.());
+ return;
+ }
+ const findDoc = (finish: (dv: DocumentView) => void) => DocumentView.addViewRenderedCb(doc, dv => finish(dv));
findDoc(dv => res(dv));
});
@@ -352,7 +409,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
.map(pair => pair.layout)
.slice()
.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
- zsorted.forEach((doc, index) => (doc.zIndex = doc.stroke_isInkMask ? 5000 : index + 1));
+ zsorted.forEach((doc, index) => {
+ doc.zIndex = doc.stroke_isInkMask ? 5000 : index + 1;
+ });
const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000));
const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)];
@@ -378,7 +437,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!d._keepZWhenDragged && (d.zIndex = zsorted.length + 1 + i); // bringToFront
}
- (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments);
+ (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this._clusters.addDocuments(docDragData.droppedDocuments);
return true;
}
@@ -405,7 +464,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
let added = false;
// do nothing if link is dropped into any freeform view parent of dragged document
const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x, y, title: 'dropped annotation' });
- added = this._props.addDocument?.(source) ? true : false;
+ added = !!this._props.addDocument?.(source);
de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
if (de.complete.linkDocument) {
de.complete.linkDocument.layout_isSvg = true;
@@ -420,186 +479,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de, de.complete.annoDragData);
- else if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData);
- else if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData);
+ if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData);
+ if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData);
return false;
};
onExternalDrop = (e: React.DragEvent) => (([x, y]) => super.onExternalDrop(e, { x, y }))(this.screenToFreeformContentsXf.transformPoint(e.pageX, e.pageY));
- static overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
- const doc2Layout = Doc.Layout(doc2);
- const doc1Layout = Doc.Layout(doc1);
- const x2 = NumCast(doc2.x) - clusterDistance;
- const y2 = NumCast(doc2.y) - clusterDistance;
- const w2 = NumCast(doc2Layout._width) + clusterDistance;
- const h2 = NumCast(doc2Layout._height) + clusterDistance;
- const x = NumCast(doc1.x) - clusterDistance;
- const y = NumCast(doc1.y) - clusterDistance;
- const w = NumCast(doc1Layout._width) + clusterDistance;
- const h = NumCast(doc1Layout._height) + clusterDistance;
- return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
- }
- pickCluster(probe: number[]) {
- return this.childLayoutPairs
- .map(pair => pair.layout)
- .reduce((cluster, cd) => {
- const grouping = this.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;
- const cy = NumCast(cd.y) - this._clusterDistance / 2;
- const cw = NumCast(layoutDoc._width) + this._clusterDistance;
- const ch = NumCast(layoutDoc._height) + this._clusterDistance;
- return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster;
- }
- return cluster;
- }, -1);
- }
-
- tryDragCluster(e: PointerEvent, cluster: number) {
- if (cluster !== -1) {
- const ptsParent = e;
- if (ptsParent) {
- const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
- const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.DocumentView?.())!);
- const { left, top } = clusterDocs[0].getBounds || { left: 0, top: 0 };
- const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? dropActionType.embed : undefined);
- de.moveDocument = this._props.moveDocument;
- de.offset = this.screenToFreeformContentsXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
- DragManager.StartDocumentDrag(
- clusterDocs.map(v => v.ContentDiv!),
- de,
- ptsParent.clientX,
- ptsParent.clientY,
- { hideSource: !de.dropAction }
- );
- return true;
- }
- }
-
- return false;
- }
-
- @action
- updateClusters(_freeform_useClusters: boolean) {
- this.Document._freeform_useClusters = _freeform_useClusters;
- this._clusterSets.length = 0;
- this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
- }
-
- @action
- updateClusterDocs(docs: Doc[]) {
- const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
- if (this.Document._freeform_useClusters) {
- const docFirst = docs[0];
- docs.map(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)));
- const preferredInd = NumCast(docFirst.layout_cluster);
- docs.map(doc => (doc.layout_cluster = -1));
- docs.map(doc =>
- this._clusterSets.map((set, i) =>
- set.map(member => {
- if (docFirst.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && CollectionFreeFormView.overlapping(doc, member, this._clusterDistance)) {
- docFirst.layout_cluster = i;
- }
- })
- )
- );
- if (
- docFirst.layout_cluster === -1 &&
- preferredInd !== -1 &&
- this._clusterSets.length > preferredInd &&
- (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)
- ) {
- docFirst.layout_cluster = preferredInd;
- }
- this._clusterSets.map((set, i) => {
- if (docFirst.layout_cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
- docFirst.layout_cluster = i;
- }
- });
- if (docFirst.layout_cluster === -1) {
- docs.map(doc => {
- doc.layout_cluster = this._clusterSets.length;
- this._clusterSets.push([doc]);
- });
- } else if (this._clusterSets.length) {
- for (let i = this._clusterSets.length; i <= NumCast(docFirst.layout_cluster); i++) !this._clusterSets[i] && this._clusterSets.push([]);
- docs.map(doc => this._clusterSets[(doc.layout_cluster = NumCast(docFirst.layout_cluster))].push(doc));
- }
- childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.layout_cluster === i) && this.updateCluster(child));
- }
- }
-
- @action
- updateCluster = (doc: Doc) => {
- const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
- if (this.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;
- this._clusterSets.forEach((set, i) =>
- set.forEach(member => {
- if (doc.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && CollectionFreeFormView.overlapping(doc, member, this._clusterDistance)) {
- doc.layout_cluster = i;
- }
- })
- );
- if (doc.layout_cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
- doc.layout_cluster = preferredInd;
- }
- this._clusterSets.forEach((set, i) => {
- if (doc.layout_cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
- doc.layout_cluster = i;
- }
- });
- if (doc.layout_cluster === -1) {
- doc.layout_cluster = this._clusterSets.length;
- this._clusterSets.push([doc]);
- } else if (this._clusterSets.length) {
- for (let i = this._clusterSets.length; i <= doc.layout_cluster; i++) !this._clusterSets[i] && this._clusterSets.push([]);
- this._clusterSets[doc.layout_cluster ?? 0].push(doc);
- }
- }
- };
-
- clusterStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => {
- let styleProp = this._props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1
- if (doc && this.childDocList?.includes(doc))
- switch (property.split(':')[0]) {
- case StyleProp.BackgroundColor:
- const cluster = NumCast(doc?.layout_cluster);
- if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) {
- if (this._clusterSets.length <= cluster) {
- setTimeout(() => doc && this.updateCluster(doc));
- } else {
- // choose a cluster color from a palette
- const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)'];
- styleProp = colors[cluster % colors.length];
- const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
- // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document
- set?.map(s => (styleProp = StrCast(s.backgroundColor)));
- }
- }
- 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 (addToSel && this._hitCluster !== -1) {
- !addToSel && SelectionManager.DeselectAll();
- const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
- this.selectDocuments(eles);
- return true;
- }
- return false;
- };
-
@action
onPointerDown = (e: React.PointerEvent): void => {
this._downX = this._lastX = e.pageX;
@@ -620,27 +506,33 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
break;
case InkTool.None:
if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
- this._hitCluster = this.pickCluster(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY));
- setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false, false);
+ const hit = this._clusters.handlePointerDown(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY));
+ setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, hit !== -1, false);
}
break;
+ default:
}
}
}
};
- public unprocessedDocs: Doc[] = [];
- public static collectionsWithUnprocessedInk = new Set<CollectionFreeFormView>();
@undoBatch
onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
switch (ge.gesture) {
- default:
- case GestureUtils.Gestures.Line:
- case GestureUtils.Gestures.Circle:
- case GestureUtils.Gestures.Rectangle:
- case GestureUtils.Gestures.Triangle:
- case GestureUtils.Gestures.Stroke:
- const points = ge.points;
+ case Gestures.Text:
+ if (ge.text) {
+ const B = this.screenToFreeformContentsXf.transformPoint(ge.points[0].X, ge.points[0].Y);
+ this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] }));
+ e.stopPropagation();
+ }
+ break;
+ case Gestures.Line:
+ case Gestures.Circle:
+ case Gestures.Rectangle:
+ case Gestures.Triangle:
+ case Gestures.Stroke:
+ default: {
+ const { points } = ge;
const B = this.screenToFreeformContentsXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
const inkWidth = ActiveInkWidth() * this.ScreenToLocalBoxXf().Scale;
const inkDoc = Docs.Create.InkDocument(
@@ -659,29 +551,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
this.addDocument(inkDoc);
e.stopPropagation();
- break;
- case GestureUtils.Gestures.Rectangle:
- const strokes = this.getActiveDocuments()
- .filter(doc => doc.type === DocumentType.INK)
- .map(i => {
- const d = Cast(i.stroke, InkField);
- const x = NumCast(i.x) - Math.min(...(d?.inkData.map(pd => pd.X) ?? [0]));
- const y = NumCast(i.y) - Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0]));
- return !d ? [] : d.inkData.map(pd => ({ X: x + pd.X, Y: y + pd.Y }));
- });
-
- CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {});
- break;
- case GestureUtils.Gestures.Text:
- if (ge.text) {
- const B = this.screenToFreeformContentsXf.transformPoint(ge.points[0].X, ge.points[0].Y);
- this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] }));
- e.stopPropagation();
- }
+ }
}
};
@action
- onEraserUp = (e: PointerEvent): void => {
+ onEraserUp = (): void => {
this._deleteList.forEach(ink => ink._props.removeDocument?.(ink.Document));
this._deleteList = [];
this._batch?.end();
@@ -690,12 +564,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
onClick = (e: React.MouseEvent) => {
if (this._lightboxDoc) this._lightboxDoc = undefined;
- if (Utils.isClick(e.pageX, e.pageY, this._downX, this._downY, this._downTime)) {
- if (this.onBrowseClickHandler()) {
- this.onBrowseClickHandler().script.run({ documentView: this.DocumentView?.(), clientX: e.clientX, clientY: e.clientY });
- e.stopPropagation();
- e.preventDefault();
- } else if (this.isContentActive() && e.shiftKey) {
+ if (ClientUtils.isClick(e.pageX, e.pageY, this._downX, this._downY, this._downTime)) {
+ if (this.isContentActive() && e.shiftKey) {
// reset zoom of freeform view to 1-to-1 on a shift + double click
this.zoomSmoothlyAboutPt(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY), 1);
e.stopPropagation();
@@ -704,9 +574,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
};
- @action
scrollPan = (e: WheelEvent | { deltaX: number; deltaY: number }): void => {
- PresBox.Instance?.pauseAutoPres();
+ SnappingManager.TriggerUserPanned();
this.setPan(NumCast(this.Document[this.panXFieldKey]) - e.deltaX, NumCast(this.Document[this.panYFieldKey]) - e.deltaY, 0, true);
};
@@ -714,7 +583,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
pan = (e: PointerEvent): void => {
const ctrlKey = e.ctrlKey && !e.shiftKey;
const shiftKey = e.shiftKey && !e.ctrlKey;
- PresBox.Instance?.pauseAutoPres();
+ SnappingManager.TriggerUserPanned();
this.DocumentView?.().clearViewTransition();
const [dxi, dyi] = this.screenToFreeformContentsXf.transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, this.ScreenToLocalBoxXf().Rotate);
@@ -723,7 +592,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._lastY = e.clientY;
};
- _eraserLock = 0;
/**
* Erases strokes by intersecting them with an invisible "eraser stroke".
* By default this iterates through all intersected ink strokes, determines their segmentation, draws back the non-intersected segments,
@@ -746,7 +614,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
segments.forEach(segment =>
this.forceStrokeGesture(
e,
- GestureUtils.Gestures.Stroke,
+ Gestures.Stroke,
segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[])
)
);
@@ -759,12 +627,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
});
return false;
};
- forceStrokeGesture = (e: PointerEvent, gesture: GestureUtils.Gestures, points: InkData, text?: any) => {
+ forceStrokeGesture = (e: PointerEvent, gesture: Gestures, points: InkData, text?: any) => {
this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, GestureOverlay.getBounds(points), text));
};
onPointerMove = (e: PointerEvent) => {
- if (this.tryDragCluster(e, this._hitCluster)) {
+ if (this._clusters.tryToDrag(e)) {
e.stopPropagation(); // we're moving a cluster, so stop propagation and return true to end panning and let the document drag take over
return true;
}
@@ -783,26 +651,25 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
getEraserIntersections = (lastPoint: { X: number; Y: number }, currPoint: { X: number; Y: number }) => {
const eraserMin = { X: Math.min(lastPoint.X, currPoint.X), Y: Math.min(lastPoint.Y, currPoint.Y) };
const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) };
-
- return this.childDocs
- .map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.()))
+ // prettier-ignore
+ return this.childDocs
+ .map(doc => DocumentView.getDocumentView(doc, this.DocumentView?.()))
.filter(inkView => inkView?.ComponentView instanceof InkingStroke)
- .map(inkView => ({ inkViewBounds: inkView!.getBounds, inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
- .filter(
- ({ inkViewBounds }) =>
+ .map(inkView => inkView!)
+ .map(inkView => ({ inkViewBounds: inkView.getBounds, inkStroke: inkView.ComponentView as InkingStroke, inkView }))
+ .filter(({ inkViewBounds }) =>
inkViewBounds && // bounding box of eraser segment and ink stroke overlap
eraserMin.X <= inkViewBounds.right &&
eraserMin.Y <= inkViewBounds.bottom &&
eraserMax.X >= inkViewBounds.left &&
- eraserMax.Y >= inkViewBounds.top
- )
+ 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) {
+ for (let i = 0; i < inkData.length - 3; i += 4) {
const rawIntersects = InkField.Segment(inkData, i).intersects({
// compute all unique intersections
p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y },
@@ -828,16 +695,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
segmentInkStroke = (ink: DocumentView, excludeT: number): Segment[] => {
const segments: Segment[] = [];
- var segment: Segment = [];
- var startSegmentT = 0;
+ let segment: Segment = [];
+ let startSegmentT = 0;
const { inkData } = (ink?.ComponentView as InkingStroke).inkScaledData();
// This iterates through all segments of the curve and splits them where they intersect another curve.
// if 'excludeT' is specified, then any segment containing excludeT will be skipped (ie, deleted)
- for (var i = 0; i < inkData.length - 3; i += 4) {
+ for (let i = 0; i < inkData.length - 3; i += 4) {
const inkSegment = InkField.Segment(inkData, i);
// Getting all t-value intersections of the current curve with all other curves.
const tVals = this.getInkIntersections(i, ink, inkSegment).sort();
if (tVals.length) {
+ // eslint-disable-next-line no-loop-func
tVals.forEach((t, index) => {
const docCurveTVal = t + Math.floor(i / 4);
if (excludeT < startSegmentT || excludeT > docCurveTVal) {
@@ -886,13 +754,14 @@ 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.DocumentView?.())?.ComponentView as InkingStroke;
+ const otherInk = DocumentView.getDocumentView(doc, this.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));
- for (var j = 0; j < otherCtrlPts.length - 3; j += 4) {
+ for (let j = 0; j < otherCtrlPts.length - 3; j += 4) {
const neighboringSegment = i === j || i === j - 4 || i === j + 4;
// Ensuring that the curve intersected by the eraser is not checked for further ink intersections.
+ // eslint-disable-next-line no-continue
if (ink?.Document === otherInk.Document && neighboringSegment) continue;
const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y })));
@@ -903,7 +772,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (apt.d !== undefined && apt.d < 1 && apt.t !== undefined && !tVals.includes(apt.t)) {
tVals.push(apt.t);
}
- this.bintersects(curve, otherCurve).forEach((val: string | number, i: number) => {
+ this.bintersects(curve, otherCurve).forEach((val: string | number /* , i: number */) => {
// Converting the Bezier.js Split type to a t-value number.
const t = +val.toString().split('/')[0];
if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical).
@@ -948,7 +817,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
onPointerWheel = (e: React.WheelEvent): void => {
if (this.Document.isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
- PresBox.Instance?.pauseAutoPres();
+ SnappingManager.TriggerUserPanned();
if (this.layoutDoc._Transform || this.Document.treeView_OutlineMode === TreeViewType.outline) return;
e.stopPropagation();
const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight);
@@ -956,7 +825,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
switch (
!e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey ?//
Doc.UserDoc().freeformScrollMode : // no modifiers, do assigned mode
- e.ctrlKey && !CtrlKey? // otherwise, if ctrl key (pinch gesture) try to zoom else pan
+ e.ctrlKey && !SnappingManager.CtrlKey? // otherwise, if ctrl key (pinch gesture) try to zoom else pan
freeformScrollMode.Zoom : freeformScrollMode.Pan // prettier-ignore
) {
case freeformScrollMode.Pan:
@@ -966,8 +835,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.scrollPan({ deltaX: -deltaX * this.screenToFreeformContentsXf.Scale, deltaY: e.shiftKey ? 0 : -deltaY * this.screenToFreeformContentsXf.Scale });
break;
}
- default:
+ // eslint-disable-next-line no-fallthrough
case freeformScrollMode.Zoom:
+ default:
if ((e.ctrlKey || !scrollable) && this._props.isContentActive()) {
this.zoom(e.clientX, e.clientY, Math.max(-1, Math.min(1, e.deltaY))); // if (!this._props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
// e.preventDefault();
@@ -977,7 +847,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
@action
- setPan(panX: number, panY: number, panTime: number = 0, clamp: boolean = false) {
+ setPan(panXIn: number, panYIn: number, panTime: number = 0, clamp: boolean = false) {
+ let panX = panXIn;
+ let panY = panYIn;
// this is the easiest way to do this -> will talk with Bob about using mobx to do this to remove this line of code.
if (Doc.UserDoc()?.presentationMode === 'watching') ReplayMovements.Instance.pauseFromInteraction();
@@ -1029,14 +901,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
scale * NumCast(this.dataDoc._panY_max, nativeHeight) +
(!this._props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning
let newPanY = Math.max(minPanY, Math.min(maxPanY, panY));
- if (false && NumCast(this.layoutDoc.layout_scrollTop) && NumCast(this.layoutDoc._freeform_scale, minScale) !== minScale) {
- const relTop = NumCast(this.layoutDoc.layout_scrollTop) / maxScrollTop;
- this.layoutDoc.layout_scrollTop = undefined;
- newPanY = minPanY + relTop * (maxPanY - minPanY);
- } else if (fitYscroll > 2 && this.layoutDoc.layout_scrollTop === undefined && NumCast(this.layoutDoc._freeform_scale, minScale) === minScale) {
- const maxPanY = minPanY + fitYscroll;
- const relTop = (panY - minPanY) / (maxPanY - minPanY);
- setTimeout(() => (this.layoutDoc.layout_scrollTop = relTop * maxScrollTop), 10);
+ if (fitYscroll > 2 && this.layoutDoc.layout_scrollTop === undefined && NumCast(this.layoutDoc._freeform_scale, minScale) === minScale) {
+ const maxPanScrollY = minPanY + fitYscroll;
+ const relTop = (panY - minPanY) / (maxPanScrollY - minPanY);
+ setTimeout(() => {
+ this.layoutDoc.layout_scrollTop = relTop * maxScrollTop;
+ }, 10);
newPanY = minPanY;
}
!this.Document._verticalScroll && (this.Document[this.panXFieldKey] = this.isAnnotationOverlay ? newPanX : panX);
@@ -1088,7 +958,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._panZoomTransition = transitionTime;
this._panZoomTransitionTimer && clearTimeout(this._panZoomTransitionTimer);
this._panZoomTransitionTimer = setTimeout(
- action(() => (this._panZoomTransition = 0)),
+ action(() => {
+ this._panZoomTransition = 0;
+ }),
transitionTime
);
};
@@ -1161,7 +1033,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
newDoc[DocData][Doc.LayoutFieldKey(newDoc, fieldProps.LayoutTemplateString)] = undefined; // the copy should not copy the text contents of it source, just the render style
newDoc.x = NumCast(textDoc.x) + (below ? 0 : NumCast(textDoc._width) + 10);
newDoc.y = NumCast(textDoc.y) + (below ? NumCast(textDoc._height) + 10 : 0);
- FormattedTextBox.SetSelectOnLoad(newDoc);
+ Doc.SetSelectOnLoad(newDoc);
FormattedTextBox.DontSelectInitialText = true;
return this.addDocument?.(newDoc);
}, 'copied text note');
@@ -1171,20 +1043,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
e.stopPropagation?.();
return this.createTextDocCopy(fieldProps, !e.altKey && e.key !== 'Tab');
}
+ return undefined;
};
- @computed get childPointerEvents() {
- const engine = this._props.layoutEngine?.() || StrCast(this.Document._layoutEngine);
- return SnappingManager.IsResizing
- ? 'none'
- : this._props.childPointerEvents?.() ??
- (this._props.viewDefDivClick || //
- (engine === computePassLayout.name && !this._props.isSelected()) ||
- this.isContentActive() === false
- ? 'none'
- : this._props.pointerEvents?.());
- }
-
- @observable _childPointerEvents: 'none' | 'all' | 'visiblepainted' | undefined = undefined;
childPointerEventsFunc = () => this._childPointerEvents;
childContentsActive = () => (this._props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)();
getChildDocView(entry: PoolData) {
@@ -1192,20 +1052,22 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const childData = entry.pair.data;
return (
<CollectionFreeFormDocumentView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...OmitKeys(entry, ['replica', 'pair']).omit}
key={childLayout[Id] + (entry.replica || '')}
Document={childLayout}
+ parent={this}
containerViewPath={this.DocumentView?.().docViewPath}
- styleProvider={this.clusterStyleProvider}
+ styleProvider={this._clusters.styleProvider}
TemplateDataDocument={childData}
dragStarting={this.dragStarting}
dragEnding={this.dragEnding}
+ isAnyChildContentActive={this.isAnyChildContentActive}
isGroupActive={this._props.isGroupActive}
renderDepth={this._props.renderDepth + 1}
hideDecorations={BoolCast(childLayout._layout_isSvg && childLayout.type === DocumentType.LINK)}
- suppressSetHeight={this.layoutEngine ? true : false}
+ suppressSetHeight={!!this.layoutEngine}
RenderCutoffProvider={this.renderCutoffProvider}
- CollectionFreeFormView={this}
LayoutTemplate={childLayout.z ? undefined : this._props.childLayoutTemplate}
LayoutTemplateString={childLayout.z ? undefined : this._props.childLayoutString}
rootSelected={childData ? this.rootSelected : returnFalse}
@@ -1213,7 +1075,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onClickScript={this.onChildClickHandler}
onKey={this.onKeyDown}
onDoubleClickScript={this.onChildDoubleClickHandler}
- onBrowseClickScript={this.onBrowseClickHandler}
bringToFront={this.bringToFront}
ScreenToLocalTransform={childLayout.z ? this.ScreenToLocalBoxXf : this.ScreenToContentsXf}
PanelWidth={childLayout[Width]}
@@ -1237,39 +1098,43 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
/>
);
}
- addDocTab = action((doc: Doc, where: OpenWhere) => {
- if (this._props.isAnnotationOverlay) return this._props.addDocTab(doc, where);
+ addDocTab = action((docsIn: Doc | Doc[], where: OpenWhere) => {
+ const docs = toList(docsIn);
+ if (this._props.isAnnotationOverlay) return this._props.addDocTab(docs, where);
switch (where) {
case OpenWhere.inParent:
- return this._props.addDocument?.(doc) || false;
- case OpenWhere.inParentFromScreen:
- const docContext = DocCast((doc instanceof Doc ? doc : doc?.[0])?.embedContainer);
+ return this._props.addDocument?.(docs) || false;
+ case OpenWhere.inParentFromScreen: {
+ const docContext = DocCast(docs[0]?.embedContainer);
return (
(this.addDocument?.(
- (doc instanceof Doc ? [doc] : doc).map(doc => {
- const pt = this.screenToFreeformContentsXf.transformPoint(NumCast(doc.x), NumCast(doc.y));
- doc.x = pt[0];
- doc.y = pt[1];
+ toList(docs).map(doc => {
+ [doc.x, doc.y] = this.screenToFreeformContentsXf.transformPoint(NumCast(doc.x), NumCast(doc.y));
return doc;
})
) &&
(!docContext || this._props.removeDocument?.(docContext))) ||
false
);
+ }
case undefined:
case OpenWhere.lightbox:
- if (this.layoutDoc._isLightbox) {
- this._lightboxDoc = doc;
- return true;
- }
- if (doc === this.Document || this.childDocList?.includes(doc) || this.childLayoutPairs.map(pair => pair.layout)?.includes(doc)) {
- if (doc.hidden) doc.hidden = false;
- return true;
+ {
+ const firstDoc = docs[0];
+ if (this.layoutDoc._isLightbox) {
+ this._lightboxDoc = firstDoc;
+ return true;
+ }
+ if (firstDoc === this.Document || this.childDocList?.includes(firstDoc) || this.childLayoutPairs.map(pair => pair.layout)?.includes(firstDoc)) {
+ if (firstDoc.hidden) firstDoc.hidden = false;
+ return true;
+ }
}
+ break;
+ default:
}
- return this._props.addDocTab(doc, where);
+ return this._props.addDocTab(docs, where);
});
- @observable _lightboxDoc: Opt<Doc> = undefined;
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);
@@ -1287,16 +1152,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const rotation = Cast(_rotation,'number',
!this.layoutDoc._rotation_jitter ? null
: NumCast(this.layoutDoc._rotation_jitter) * random(-1, 1, NumCast(x), NumCast(y)) );
- const childProps = { ...this._props, fieldKey: '', styleProvider: this.clusterStyleProvider };
+ const childProps = { ...this._props, fieldKey: '', styleProvider: this._clusters.styleProvider };
return {
- x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
- y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
+ x: isNaN(NumCast(x)) ? 0 : NumCast(x),
+ y: isNaN(NumCast(y)) ? 0 : NumCast(y),
z: Cast(z, 'number'),
autoDim,
rotation,
- color: Cast(color, 'string') ? StrCast(color) : this.clusterStyleProvider(childDoc, childProps, StyleProp.Color),
- backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, childProps, StyleProp.BackgroundColor),
- opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.clusterStyleProvider?.(childDoc, childProps, StyleProp.Opacity),
+ color: Cast(color, 'string') ? StrCast(color) : this._clusters.styleProvider(childDoc, childProps, StyleProp.Color),
+ backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this._clusters.styleProvider(childDoc, childProps, StyleProp.BackgroundColor),
+ opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this._clusters.styleProvider?.(childDoc, childProps, StyleProp.Opacity),
zIndex: Cast(zIndex, 'number'),
width: _width,
height: _height,
@@ -1312,9 +1177,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
e.stopPropagation();
};
- viewDefsToJSX = (views: ViewDefBounds[]) => {
- return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!);
- };
+ viewDefsToJSX = (views: ViewDefBounds[]) => (!Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!));
viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
const { x, y, z } = viewDef;
@@ -1335,7 +1198,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
),
bounds: viewDef,
};
- } else if (viewDef.type === 'div') {
+ }
+ if (viewDef.type === 'div') {
return [x, y].some(val => val === undefined)
? undefined
: {
@@ -1351,13 +1215,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
bounds: viewDef,
};
}
+ return undefined;
}
- renderCutoffProvider = computedFn(
- function renderCutoffProvider(this: any, doc: Doc) {
- return this.Document.isTemplateDoc ? false : !this._renderCutoffData.get(doc[Id] + '');
- }.bind(this)
- );
+ /**
+ * Determines whether the passed doc should be rendered
+ * since rendering a large collection of documents can be slow, at startup, docs are rendered in batches.
+ * each doc's render() method will call the cutoff provider which will let the doc know if it should render itself yet, or wait
+ */
+ renderCutoffProvider = computedFn((doc: Doc) => (this.Document.isTemplateDoc ? false : !this._renderCutoffData.get(doc[Id] + '')));
doEngineLayout(
poolData: Map<string, PoolData>,
@@ -1367,27 +1233,22 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
doFreeformLayout(poolData: Map<string, PoolData>) {
- this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => poolData.set(pair.layout[Id], this.getCalculatedPositions(pair)));
+ this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => poolData.set(pair.layout[Id], this.getCalculatedPositions(pair)));
return [] as ViewDefResult[];
}
- @computed get layoutEngine() {
- return this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
- }
@computed get doInternalLayoutComputation() {
TraceMobx();
const newPool = new Map<string, PoolData>();
- // prettier-ignore
switch (this.layoutEngine) {
case computePassLayout.name : return { newPool, computedElementData: this.doEngineLayout(newPool, computePassLayout) };
case computeTimelineLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
case computePivotLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
case computeStarburstLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeStarburstLayout) };
- }
- return { newPool, computedElementData: this.doFreeformLayout(newPool) };
+ default: return { newPool, computedElementData: this.doFreeformLayout(newPool) };
+ } // prettier-ignore
}
- @action
doLayoutComputation = (newPool: Map<string, PoolData>, computedElementData: ViewDefResult[]) => {
const elements = computedElementData.slice();
Array.from(newPool.entries())
@@ -1400,7 +1261,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
})
);
- this.Document._freeform_useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
+ this._clusters.initLayout();
return elements;
};
@@ -1412,7 +1273,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
presentation_transition: 500,
annotationOn: this.Document,
});
- PresBox.pinDocView(
+ PinDocView(
anchor,
{ pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ? { ...pinProps.pinData, poslayoutview: pinProps.pinData.dataview } : {}), pannable: !this.Document.isGroup, type_collection: true, filters: true } },
this.Document
@@ -1428,8 +1289,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return anchor;
};
- @action closeInfo = () => (Doc.IsInfoUIDisabled = true);
- infoUI = () => (Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} close={this.closeInfo} />);
+ childDocsFunc = () => this.childDocs;
+ closeInfo = action(() => { Doc.IsInfoUIDisabled = true }); // prettier-ignore
+ static _infoUI: ((doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => JSX.Element) | null = null;
+ static SetInfoUICreator(func: (doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => JSX.Element) {
+ CollectionFreeFormView._infoUI = func;
+ }
+ infoUI = () =>
+ Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth
+ ? null //
+ : CollectionFreeFormView._infoUI?.(this.Document, this.layoutDoc, this.childDocsFunc, this.closeInfo) || null;
componentDidMount() {
this._props.setContentViewBox?.(this);
@@ -1470,7 +1339,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._disposers.pointerevents = reaction(
() => this.childPointerEvents,
- pointerevents => (this._childPointerEvents = pointerevents as any),
+ pointerevents => {
+ this._childPointerEvents = pointerevents as any;
+ },
{ fireImmediately: true }
);
@@ -1487,6 +1358,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (!code.includes('dashDiv')) {
const script = CompileScript(code, { params: { docView: 'any' }, typecheck: false, editable: true });
if (script.compiled) script.run({ this: this.DocumentView?.() });
+ // eslint-disable-next-line no-eval
} else code && !first && eval?.(code);
},
{ fireImmediately: true }
@@ -1495,45 +1367,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._disposers.layoutElements = reaction(
// layoutElements can't be a computed value because doLayoutComputation() is an action that has side effect of updating clusters
() => this.doInternalLayoutComputation,
- computation => (this._layoutElements = this.doLayoutComputation(computation.newPool, computation.computedElementData)),
+ computation => {
+ this._layoutElements = this.doLayoutComputation(computation.newPool, computation.computedElementData);
+ },
{ fireImmediately: true }
);
}
- static replaceCanvases(oldDiv: HTMLElement, newDiv: HTMLElement) {
- if (oldDiv.childNodes && newDiv) {
- for (let i = 0; i < oldDiv.childNodes.length; i++) {
- this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement);
- }
- }
- if (oldDiv instanceof HTMLCanvasElement) {
- if (oldDiv.className === 'collectionFreeFormView-grid') {
- const newCan = newDiv as HTMLCanvasElement;
- const parEle = newCan.parentElement as HTMLElement;
- parEle.removeChild(newCan);
- parEle.appendChild(document.createElement('div'));
- } else {
- const canvas = oldDiv;
- const img = document.createElement('img'); // create a Image Element
- try {
- img.src = canvas.toDataURL(); //image source
- } catch (e) {
- console.log(e);
- }
- img.style.width = canvas.style.width;
- img.style.height = canvas.style.height;
- const newCan = newDiv as HTMLCanvasElement;
- if (newCan) {
- const parEle = newCan.parentElement as HTMLElement;
- parEle.removeChild(newCan);
- parEle.appendChild(img);
- }
- }
- }
+ componentWillUnmount() {
+ this.dataDoc[this.autoResetFieldKey] && this.resetView();
+ Object.values(this._disposers).forEach(disposer => disposer?.());
}
updateIcon = () =>
- CollectionFreeFormView.UpdateIcon(
+ UpdateIcon(
this.layoutDoc[Id] + '-icon' + new Date().getTime(),
this.DocumentView?.().ContentDiv!,
NumCast(this.layoutDoc._width),
@@ -1551,55 +1398,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
);
- public static UpdateIcon(
- filename: string,
- docViewContent: HTMLElement,
- width: number,
- height: number,
- panelWidth: number,
- panelHeight: number,
- scrollTop: number,
- realNativeHeight: number,
- noSuffix: boolean,
- replaceRootFilename: string | undefined,
- cb: (iconFile: string, nativeWidth: number, nativeHeight: number) => any
- ) {
- const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
- newDiv.style.width = width.toString();
- newDiv.style.height = height.toString();
- this.replaceCanvases(docViewContent, newDiv);
- const htmlString = new XMLSerializer().serializeToString(newDiv);
- const nativeWidth = width;
- const nativeHeight = height;
- return CreateImage(Utils.prepend(''), document.styleSheets, htmlString, nativeWidth, (nativeWidth * panelHeight) / panelWidth, (scrollTop * panelHeight) / realNativeHeight)
- .then(async (data_url: any) => {
- const returnedFilename = await Utils.convertDataUri(data_url, filename, noSuffix, replaceRootFilename);
- cb(returnedFilename as string, nativeWidth, nativeHeight);
- })
- .catch(function (error: any) {
- console.error('oops, something went wrong!', error);
- });
- }
-
- componentWillUnmount() {
- this.dataDoc[this.autoResetFieldKey] && this.resetView();
- Object.values(this._disposers).forEach(disposer => disposer?.());
- }
-
- @action
- onCursorMove = (e: React.PointerEvent) => {
+ onCursorMove = () => {
// super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
};
@undoBatch
promoteCollection = () => {
const childDocs = this.childDocs.slice();
- childDocs.forEach(doc => {
+ childDocs.forEach(docIn => {
+ const doc = docIn;
const scr = this.screenToFreeformContentsXf.inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
doc.x = scr?.[0];
doc.y = scr?.[1];
});
- this._props.addDocTab(childDocs as any as Doc, OpenWhere.inParentFromScreen);
+ this._props.addDocTab(childDocs, OpenWhere.inParentFromScreen);
};
@undoBatch
@@ -1608,7 +1420,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const width = Math.max(...docs.map(doc => NumCast(doc._width))) + 20;
const height = Math.max(...docs.map(doc => NumCast(doc._height))) + 20;
const dim = Math.ceil(Math.sqrt(docs.length));
- docs.forEach((doc, i) => {
+ docs.forEach((docIn, i) => {
+ const doc = docIn;
doc.x = NumCast(this.Document[this.panXFieldKey]) + (i % dim) * width - (width * dim) / 2;
doc.y = NumCast(this.Document[this.panYFieldKey]) + Math.floor(i / dim) * height - (height * dim) / 2;
});
@@ -1641,7 +1454,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
};
- onContextMenu = (e: React.MouseEvent) => {
+ onContextMenu = () => {
if (this._props.isAnnotationOverlay || !ContextMenu.Instance) return;
const appearance = ContextMenu.Instance.findByDescription('Appearance...');
@@ -1652,7 +1465,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
return;
}
- !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' });
+ !Doc.noviceMode &&
+ Doc.UserDoc().defaultTextLayout &&
+ appearanceItems.push({
+ description: 'Reset default note style',
+ event: () => {
+ Doc.UserDoc().defaultTextLayout = undefined;
+ },
+ icon: 'eye',
+ });
appearanceItems.push({ description: `Pin View`, event: () => this._props.pinToPres(this.Document, { pinViewport: MarqueeView.CurViewBounds(this.dataDoc, this._props.PanelWidth(), this._props.PanelHeight()) }), icon: 'map-pin' });
!Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' });
this._props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
@@ -1660,15 +1481,28 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.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;
+ !Doc.noviceMode ? appearanceItems.push({ description: (this.Document._freeform_useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this._clusters.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null;
!appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
const options = ContextMenu.Instance.findByDescription('Options...');
const optionItems = options && 'subitems' in options ? options.subitems : [];
!this._props.isAnnotationOverlay &&
!Doc.noviceMode &&
- optionItems.push({ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline', event: action(() => (this._showAnimTimeline = !this._showAnimTimeline)), icon: 'eye' });
- this._props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null).backgroundColor = StrCast(this.layoutDoc.backgroundColor)), icon: 'palette' });
+ optionItems.push({
+ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline',
+ event: action(() => {
+ this._showAnimTimeline = !this._showAnimTimeline;
+ }),
+ icon: 'eye',
+ });
+ this._props.renderDepth &&
+ optionItems.push({
+ description: 'Use Background Color as Default',
+ event: () => {
+ Cast(Doc.UserDoc().emptyCollection, Doc, null).backgroundColor = StrCast(this.layoutDoc.backgroundColor);
+ },
+ icon: 'palette',
+ });
this._props.renderDepth && optionItems.push({ description: 'Fit Content Once', event: this.fitContentOnce, icon: 'object-group' });
if (!Doc.noviceMode) {
optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? 'Freeze' : 'Unfreeze') + ' Aspect', event: this.toggleNativeDimensions, icon: 'snowflake' });
@@ -1707,15 +1541,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
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.IsResizing !== doc && !DragManager.docsBeingDragged.includes(doc))
- .forEach(doc => DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited));
+ activeDocs.filter(doc => doc.isGroup && SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc)).forEach(doc => DocumentView.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited));
const horizLines: number[] = [];
const vertLines: number[] = [];
const invXf = this.screenToFreeformContentsXf.inverse();
snappableDocs
- .filter(doc => !doc.isGroup && (snapToDraggedDoc || (SnappingManager.IsResizing !== doc && !DragManager.docsBeingDragged.includes(doc))))
+ .filter(doc => !doc.isGroup && (snapToDraggedDoc || (SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc))))
.forEach(doc => {
const { left, top, width, height } = docDims(doc);
const topLeftInScreen = invXf.transformPoint(left, top);
@@ -1731,29 +1563,41 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
incrementalRender = action(() => {
if (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.())) {
- const layout_unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
+ const layoutUnrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
const loadIncrement = this.Document.isTemplateDoc ? Number.MAX_VALUE : 5;
- for (var i = 0; i < Math.min(layout_unrendered.length, loadIncrement); i++) {
- this._renderCutoffData.set(layout_unrendered[i][Id] + '', true);
+ for (let i = 0; i < Math.min(layoutUnrendered.length, loadIncrement); i++) {
+ this._renderCutoffData.set(layoutUnrendered[i][Id] + '', true);
}
}
this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1);
});
- @computed get placeholder() {
- return (
- <div className="collectionfreeformview-placeholder" style={{ background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) }}>
- <span className="collectionfreeformview-placeholderSpan">{this.Document.annotationOn ? '' : this.Document.title?.toString()}</span>
- </div>
- );
- }
-
+ showPresPaths = () => SnappingManager.ShowPresPaths;
brushedView = () => this._brushedView;
- gridColor = () =>
- DashColor(lightOrDark(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor)))
- .fade(0.5)
- .toString();
- @computed get backgroundGrid() {
+ gridColor = () => DashColor(lightOrDark(this.backgroundColor)).fade(0.5).toString(); // prettier-ignore
+ nativeDim = () => this.nativeDimScaling;
+
+ brushView = action((viewport: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number = 2500) => {
+ this._brushtimer1 && clearTimeout(this._brushtimer1);
+ this._brushtimer && clearTimeout(this._brushtimer);
+ this._brushedView = undefined;
+ this._brushtimer1 = setTimeout(
+ action(() => {
+ this._brushedView = { ...viewport, panX: viewport.panX - viewport.width / 2, panY: viewport.panY - viewport.height / 2 };
+ this._brushtimer = setTimeout(action(() => { this._brushedView = undefined; }), holdTime); // prettier-ignore
+ }),
+ transTime + 1
+ );
+ });
+ lightboxPanelWidth = () => Math.max(0, this._props.PanelWidth() - 30);
+ lightboxPanelHeight = () => Math.max(0, this._props.PanelHeight() - 30);
+ lightboxScreenToLocal = () => this.ScreenToLocalBoxXf().translate(-15, -15);
+ onPassiveWheel = (e: WheelEvent) => {
+ 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();
+ };
+ 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!!?
@@ -1780,7 +1624,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
brushedView={this.brushedView}
isAnnotationOverlay={this.isAnnotationOverlay}
transform={this.PanZoomCenterXf}
- transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null))}
+ showPresPaths={this.showPresPaths}
+ transition={this.panZoomTransition}
viewDefDivClick={this._props.viewDefDivClick}>
{this.props.children ?? null} {/* most likely case of children is document content that's being annoated: eg., an image */}
{this.contentViews}
@@ -1797,7 +1642,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
nudge={this.isAnnotationOverlay || this._props.renderDepth > 0 ? undefined : this.nudge}
addDocTab={this.addDocTab}
slowLoadDocuments={this.slowLoadDocuments}
- trySelectCluster={this.trySelectCluster}
+ trySelectCluster={this._clusters.tryToSelect}
activeDocuments={this.getActiveDocuments}
selectDocuments={this.selectDocuments}
addDocument={this.addDocument}
@@ -1813,39 +1658,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
</MarqueeView>
);
}
-
- @computed get nativeDimScaling() {
- if (this._firstRender || (this._props.isAnnotationOverlay && !this._props.annotationLayerHostsContent)) return 1;
- 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._props.layout_fitWidth?.(this.Document) ?? this.layoutDoc.layout_fitWidth) ? wscale : hscale;
- }
- nativeDim = () => this.nativeDimScaling;
-
- @action
- brushView = (viewport: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number = 2500) => {
- this._brushtimer1 && clearTimeout(this._brushtimer1);
- this._brushtimer && clearTimeout(this._brushtimer);
- this._brushedView = undefined;
- this._brushtimer1 = setTimeout(
- action(() => {
- this._brushedView = { ...viewport, panX: viewport.panX - viewport.width / 2, panY: viewport.panY - viewport.height / 2 };
- this._brushtimer = setTimeout(action(() => (this._brushedView = undefined)), holdTime); // prettier-ignore
- }),
- transTime + 1
+ get placeholder() {
+ return (
+ <div className="collectionfreeformview-placeholder" style={{ background: this.backgroundColor }}>
+ <span className="collectionfreeformview-placeholderSpan">{this.Document.annotationOn ? '' : this.Document.title?.toString()}</span>
+ </div>
);
- };
- lightboxPanelWidth = () => Math.max(0, this._props.PanelWidth() - 30);
- lightboxPanelHeight = () => Math.max(0, this._props.PanelHeight() - 30);
- lightboxScreenToLocal = () => this.ScreenToLocalBoxXf().translate(-15, -15);
- onPassiveWheel = (e: WheelEvent) => {
- 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() {
TraceMobx();
return (
@@ -1889,7 +1708,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onClickScript={this.onChildClickHandler}
onKey={this.onKeyDown}
onDoubleClickScript={this.onChildDoubleClickHandler}
- onBrowseClickScript={this.onBrowseClickHandler}
childFilters={this.childDocFilters}
childFiltersByRanges={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
@@ -1912,47 +1730,24 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
);
}
}
-
-@observer
-class CollectionFreeFormOverlayView extends React.Component<{ elements: () => ViewDefResult[] }> {
- render() {
- return this.props.elements().filter(ele => ele.bounds?.z).map(ele => ele.ele); // prettier-ignore
- }
-}
-
-export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) {
- const browseTransitionTime = 500;
- SelectionManager.DeselectAll();
- dv &&
- DocumentManager.Instance.showDocument(dv.Document, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
- if (!focused) {
- const selfFfview = !dv.Document.isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
- let containers = dv.containerViewPath?.() ?? [];
- let parFfview = dv.CollectionFreeFormView;
- for (var cont of containers) {
- parFfview = parFfview ?? cont.CollectionFreeFormView;
- }
- while (parFfview?.Document.isGroup) parFfview = parFfview.DocumentView?.().CollectionFreeFormView;
- const ffview = selfFfview && selfFfview.layoutDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview
- ffview?.zoomSmoothlyAboutPt(ffview.screenToFreeformContentsXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
- Doc.linkFollowHighlight(dv?.Document, false);
- }
- });
-}
-ScriptingGlobals.add(CollectionBrowseClick);
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
- !readOnly && (SelectionManager.Views[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame();
+ !readOnly && (DocumentView.Selected()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame();
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) {
- !readOnly && (SelectionManager.Views[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true);
+ !readOnly && (DocumentView.Selected()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) {
- const selView = SelectionManager.Views;
+ const selView = DocumentView.Selected();
if (readOnly) return selView[0].ComponentView?.getKeyFrameEditing?.() ? Colors.MEDIUM_BLUE : 'transparent';
runInAction(() => selView[0].ComponentView?.setKeyFrameEditing?.(!selView[0].ComponentView?.getKeyFrameEditing?.()));
+ return undefined;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
- SelectionManager.Views.forEach(view =>
+ DocumentView.Selected().forEach(view =>
view._props.pinToPres(view.Document, {
currentFrame: Cast(view.Document.currentFrame, 'number', null),
pinData: {
@@ -1963,29 +1758,33 @@ ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
})
);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function bringToFront() {
- SelectionManager.Views.forEach(view => view.CollectionFreeFormView?.bringToFront(view.Document));
+ DocumentView.Selected().forEach(view => CollectionFreeFormView.from(view)?.bringToFront(view.Document));
});
-ScriptingGlobals.add(function sendToBack(doc: Doc) {
- SelectionManager.Views.forEach(view => view.CollectionFreeFormView?.bringToFront(view.Document, true));
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function sendToBack() {
+ DocumentView.Selected().forEach(view => CollectionFreeFormView.from(view)?.bringToFront(view.Document, true));
});
-ScriptingGlobals.add(function datavizFromSchema(doc: Doc) {
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function datavizFromSchema() {
// creating a dataviz doc to represent the schema table
- SelectionManager.Views.forEach(view => {
+ DocumentView.Selected().forEach(viewIn => {
+ const view = viewIn;
if (!view.layoutDoc.schema_columnKeys) {
view.layoutDoc.schema_columnKeys = new List<string>(['title', 'type', 'author', 'author_date']);
}
- const keys = Cast(view.layoutDoc.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text');
+ const keys = Cast(view.layoutDoc.schema_columnKeys, listSpec('string'))?.filter(key => key !== 'text');
if (!keys) return;
const children = DocListCast(view.Document[Doc.LayoutFieldKey(view.Document)]);
- let csvRows = [];
+ const csvRows = [];
csvRows.push(keys.join(','));
for (let i = 0; i < children.length; i++) {
- let eachRow = [];
+ const eachRow = [];
for (let j = 0; j < keys.length; j++) {
- var cell = children[i][keys[j]]?.toString();
- if (cell) cell = cell.toString().replace(/\,/g, '');
+ let cell = children[i][keys[j]]?.toString();
+ if (cell) cell = cell.toString().replace(/,/g, '');
eachRow.push(cell);
}
csvRows.push(eachRow);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 79cc534dc..adac5a102 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -9,6 +9,7 @@ import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
@observer
export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
+ // eslint-disable-next-line no-use-before-define
static Instance: MarqueeOptionsMenu;
public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => void = unimplementedFunction;
@@ -29,14 +30,13 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
}
render() {
- const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ width: 19 }} />;
const buttons = (
<>
- <IconButton tooltip={'Create a Collection'} onPointerDown={this.createCollection} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} />
- <IconButton tooltip={'Create a Grouping'} onPointerDown={e => this.createCollection(e, true)} icon={<FontAwesomeIcon icon="layer-group" />} color={this.userColor} />
- <IconButton tooltip={'Summarize Documents'} onPointerDown={this.summarize} icon={<FontAwesomeIcon icon="compress-arrows-alt" />} color={this.userColor} />
- <IconButton tooltip={'Delete Documents'} onPointerDown={this.delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={this.userColor} />
- <IconButton tooltip={'Pin selected region'} onPointerDown={this.pinWithView} icon={<FontAwesomeIcon icon="map-pin" />} color={this.userColor} />
+ <IconButton tooltip="Create a Collection" onPointerDown={this.createCollection} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} />
+ <IconButton tooltip="Create a Grouping" onPointerDown={e => this.createCollection(e, true)} icon={<FontAwesomeIcon icon="layer-group" />} color={this.userColor} />
+ <IconButton tooltip="Summarize Documents" onPointerDown={this.summarize} icon={<FontAwesomeIcon icon="compress-arrows-alt" />} color={this.userColor} />
+ <IconButton tooltip="Delete Documents" onPointerDown={this.delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={this.userColor} />
+ <IconButton tooltip="Pin selected region" onPointerDown={this.pinWithView} icon={<FontAwesomeIcon icon="map-pin" />} color={this.userColor} />
</>
);
return this.getElement(buttons);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 6eca91e9d..b96444024 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,7 +1,9 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Utils, intersectRect, lightOrDark, returnFalse } from '../../../../Utils';
+import { ClientUtils, lightOrDark, returnFalse } from '../../../../ClientUtils';
+import { intersectRect } from '../../../../Utils';
import { Doc, Opt } from '../../../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
@@ -12,22 +14,24 @@ import { Cast, FieldValue, NumCast, StrCast } from '../../../../fields/Types';
import { ImageField } from '../../../../fields/URLField';
import { GetEffectiveAcl } from '../../../../fields/util';
import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
+import { DocUtils } from '../../../documents/DocUtils';
import { DocumentType } from '../../../documents/DocumentTypes';
-import { DocUtils, Docs, DocumentOptions } from '../../../documents/Documents';
-import { SelectionManager } from '../../../util/SelectionManager';
-import { freeformScrollMode } from '../../../util/SettingsManager';
-import { SnappingManager } from '../../../util/SnappingManager';
+import { Docs, DocumentOptions } from '../../../documents/Documents';
+import { SnappingManager, freeformScrollMode } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { UndoManager, undoBatch } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { MarqueeViewBounds } from '../../PinFuncs';
import { PreviewCursor } from '../../PreviewCursor';
-import { OpenWhere } from '../../nodes/DocumentView';
+import { DocumentView } from '../../nodes/DocumentView';
+import { OpenWhere } from '../../nodes/OpenWhere';
import { pasteImageBitmap } from '../../nodes/WebBoxRenderer';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { SubCollectionViewProps } from '../CollectionSubView';
import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
import './MarqueeView.scss';
+
interface MarqueeViewProps {
getContainerTransform: () => Transform;
getTransform: () => Transform;
@@ -44,13 +48,6 @@ interface MarqueeViewProps {
slowLoadDocuments: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>;
}
-export interface MarqueeViewBounds {
- left: number;
- top: number;
- width: number;
- height: number;
-}
-
@observer
export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps & MarqueeViewProps> {
public static CurViewBounds(pinDoc: Doc, panelWidth: number, panelHeight: number) {
@@ -102,7 +99,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
@action
onKeyDown = (e: KeyboardEvent) => {
- //make textbox and add it to this collection
+ // make textbox and add it to this collection
// tslint:disable-next-line:prefer-const
const cm = ContextMenu.Instance;
const [x, y] = this.Transform.transformPoint(this._downX, this._downY);
@@ -141,7 +138,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
}
}
let ypos = y;
- ns.map(line => {
+ ns.forEach(line => {
const indent = line.search(/\S|$/);
const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + (indent / 3) * 10, y: ypos, title: line });
this._props.addDocument?.(newBox);
@@ -155,7 +152,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
pasteImageBitmap((data: any, error: any) => {
error && console.log(error);
data &&
- Utils.convertDataUri(data, this._props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
+ ClientUtils.convertDataUri(data, this._props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
this._props.Document['thumb-frozen'] = new ImageField(returnedfilename);
});
})
@@ -165,11 +162,11 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
const slide = DocUtils.copyDragFactory(DocCast(Doc.UserDoc().emptySlide))!;
slide.x = x;
slide.y = y;
- FormattedTextBox.SelectOnLoad = slide[Id];
+ Doc.SetSelectOnLoad(slide);
TreeView._editTitleOnLoad = { id: slide[Id], parent: undefined };
this._props.addDocument?.(slide);
e.stopPropagation();
- }*/ else if (e.key === 'p' && e.ctrlKey) {
+ } */ else if (e.key === 'p' && e.ctrlKey) {
e.preventDefault();
(async () => {
const text: string = await navigator.clipboard.readText();
@@ -177,14 +174,14 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
this.pasteTable(ns, x, y);
})();
e.stopPropagation();
- } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views.length < 2) {
+ } else if (!e.ctrlKey && !e.metaKey && DocumentView.Selected().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));
e.stopPropagation();
}
};
- //heuristically converts pasted text into a table.
+ // heuristically converts pasted text into a table.
// assumes each entry is separated by a tab
// skips all rows until it gets to a row with more than one entry
// assumes that 1st row has header entry for each column
@@ -192,15 +189,15 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
// any row that has only one column is a section header-- this header is then added as a column to subsequent rows until the next header
// assumes each cell is a string or a number
pasteTable(ns: string[], x: number, y: number) {
- let csvRows = [];
+ const csvRows = [];
const headers = ns[0].split('\t');
csvRows.push(headers.join(','));
ns[0] = '';
const eachCell = ns.join('\t').split('\t');
let eachRow = [];
for (let i = 1; i < eachCell.length; i++) {
- eachRow.push(eachCell[i].replace(/\,/g, ''));
- if (i % headers.length == 0) {
+ eachRow.push(eachCell[i].replace(/,/g, ''));
+ if (i % headers.length === 0) {
csvRows.push(eachRow);
eachRow = [];
}
@@ -233,7 +230,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
this._lastY = e.pageY;
this._lassoPts.push([e.clientX, e.clientY]);
if (!e.cancelBubble) {
- if (!Utils.isClick(this._lastX, this._lastY, this._downX, this._downY, Date.now())) {
+ if (!ClientUtils.isClick(this._lastX, this._lastY, this._downX, this._downY, Date.now())) {
if (!this._commandExecuted) {
this.showMarquee();
}
@@ -251,7 +248,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
if (this._visible) {
const mselect = this.marqueeSelect();
if (!e.shiftKey) {
- SelectionManager.DeselectAll(mselect.length ? undefined : this._props.Document);
+ DocumentView.DeselectAll(mselect.length ? undefined : this._props.Document);
}
const docs = mselect.length ? mselect : [this._props.Document];
this._props.selectDocuments(docs);
@@ -320,7 +317,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
@action
onClick = (e: React.MouseEvent): void => {
if (this._props.pointerEvents?.() === 'none') return;
- if (Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
+ if (ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
if (Doc.ActiveTool === InkTool.None) {
if (!this._props.trySelectCluster(e.shiftKey)) {
!SnappingManager.ExploreMode && this.setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document);
@@ -335,15 +332,21 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
};
@action
- showMarquee = () => (this._visible = true);
+ showMarquee = () => {
+ this._visible = true;
+ };
@action
- hideMarquee = () => (this._visible = false);
+ hideMarquee = () => {
+ this._visible = false;
+ };
@undoBatch
delete = action((e?: React.PointerEvent<Element> | KeyboardEvent | undefined, hide?: boolean) => {
const selected = this.marqueeSelect(false);
- SelectionManager.DeselectAll();
- selected.forEach(doc => (hide ? (doc.hidden = true) : this._props.removeDocument?.(doc)));
+ DocumentView.DeselectAll();
+ selected.forEach(doc => {
+ hide ? (doc.hidden = true) : this._props.removeDocument?.(doc);
+ });
this.cleanupInteractions(false);
MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -374,9 +377,9 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
});
@undoBatch
- pileup = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
+ pileup = action(() => {
const selected = this.marqueeSelect(false);
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
selected.forEach(d => this._props.removeDocument?.(d));
const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2)!;
this._props.addDocument?.(newCollection);
@@ -386,7 +389,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
});
/**
- * This triggers the TabDocView.PinDoc method which is the universal method
+ * This triggers the DocumentView.PinDoc method which is the universal method
* used to pin documents to the currently active presentation trail.
*
* This one is unique in that it includes the bounds associated with marquee view.
@@ -482,7 +485,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
});
@undoBatch
- summary = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
+ summary = action(() => {
const selected = this.marqueeSelect(false).map(d => {
this._props.removeDocument?.(d);
d.x = NumCast(d.x) - this.Bounds.left;
@@ -527,8 +530,8 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
(e as any).propagationIsStopped = true;
if (e.key === 'g') this.collection(e, true);
if (e.key === 'c' || e.key === 't') this.collection(e);
- if (e.key === 's' || e.key === 'S') this.summary(e);
- if (e.key === 'p') this.pileup(e);
+ if (e.key === 's' || e.key === 'S') this.summary();
+ if (e.key === 'p') this.pileup();
this.cleanupInteractions(false);
}
if (e.key === 'r' || e.key === ' ') {
@@ -540,6 +543,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
};
touchesLine(r1: { left: number; top: number; width: number; height: number }) {
+ // eslint-disable-next-line no-restricted-syntax
for (const lassoPt of this._lassoPts) {
const topLeft = this.Transform.transformPoint(lassoPt[0], lassoPt[1]);
if (r1.left < topLeft[0] && topLeft[0] < r1.left + r1.width && r1.top < topLeft[1] && topLeft[1] < r1.top + r1.height) {
@@ -560,6 +564,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
let hasLeft = false;
let hasBottom = false;
let hasRight = false;
+ // eslint-disable-next-line no-restricted-syntax
for (const lassoPt of this._lassoPts) {
const truePoint = this.Transform.transformPoint(lassoPt[0], lassoPt[1]);
hasLeft = hasLeft || (truePoint[0] > tl[0] && truePoint[0] < r1.left && truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height);
@@ -662,6 +667,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
};
render() {
return (
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events
<div
className="marqueeView"
ref={r => {
@@ -673,7 +679,9 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
cursor: [InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) || this._visible ? 'crosshair' : 'pointer',
}}
onDragOver={e => e.preventDefault()}
- onScroll={e => (e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0)}
+ onScroll={e => {
+ e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0;
+ }}
onClick={this.onClick}
onPointerDown={this.onPointerDown}>
{this._visible ? this.marqueeDiv : null}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index f25872c2b..2d9191dd7 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -1,10 +1,11 @@
import { action, computed, Lambda, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
import { Doc, Opt } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
import { DragManager } from '../../../util/DragManager';
import { Transform } from '../../../util/Transform';
@@ -12,10 +13,10 @@ import { undoBatch } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
import { ContextMenuProps } from '../../ContextMenuItem';
import { DocumentView } from '../../nodes/DocumentView';
-import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionGridView.scss';
import Grid, { Layout } from './Grid';
+
@observer
export class CollectionGridView extends CollectionSubView() {
private _containerRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -76,15 +77,13 @@ export class CollectionGridView extends CollectionSubView() {
pairs.forEach((pair, i) => {
const existing = oldLayouts.find(l => l.i === pair.layout[Id]);
if (existing) newLayouts.push(existing);
- else {
- if (Object.keys(this.dropLocation).length) {
- // external drop
- this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.dropLocation as { x: number; y: number }, !this.flexGrid));
- this.dropLocation = {};
- } else {
- // internal drop
- this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
- }
+ else if (Object.keys(this.dropLocation).length) {
+ // external drop
+ this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.dropLocation as { x: number; y: number }, !this.flexGrid));
+ this.dropLocation = {};
+ } else {
+ // internal drop
+ this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
}
});
pairs?.length && this.setLayoutList(newLayouts);
@@ -139,9 +138,7 @@ export class CollectionGridView extends CollectionSubView() {
/**
* Creates a layout object for a grid item
*/
- makeLayoutItem = (doc: Doc, pos: { x: number; y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => {
- return { i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static };
- };
+ makeLayoutItem = (doc: Doc, pos: { x: number; y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => ({ i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static });
/**
* Adds a layout to the list of layouts.
@@ -189,6 +186,7 @@ export class CollectionGridView extends CollectionSubView() {
getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
return (
<DocumentView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
NativeWidth={returnZero}
NativeHeight={returnZero}
@@ -218,7 +216,7 @@ export class CollectionGridView extends CollectionSubView() {
if (this.flexGrid) {
const savedLayouts = this.savedLayoutList;
this.childLayoutPairs.forEach(({ layout: doc }) => {
- const gridLayout = savedLayouts.find(gridLayout => gridLayout.i === doc[Id]);
+ const gridLayout = savedLayouts.find(layout => layout.i === doc[Id]);
if (gridLayout) Object.assign(gridLayout, layoutArray.find(layout => layout.i === doc[Id]) || gridLayout);
});
@@ -317,7 +315,9 @@ export class CollectionGridView extends CollectionSubView() {
e,
returnFalse,
action(() => {
- undoBatch(() => (this.Document.gridRowHeight = this._rowHeight))();
+ undoBatch(() => {
+ this.Document.gridRowHeight = this._rowHeight;
+ })();
this._rowHeight = undefined;
}),
emptyFunction,
@@ -331,8 +331,20 @@ export class CollectionGridView extends CollectionSubView() {
*/
onContextMenu = () => {
const displayOptionsMenu: ContextMenuProps[] = [];
- displayOptionsMenu.push({ description: 'Toggle Content Display Style', event: () => (this.Document.display = this.Document.display ? undefined : 'contents'), icon: 'copy' });
- displayOptionsMenu.push({ description: 'Toggle Vertical Centering', event: () => (this.Document.centerY = !this.Document.centerY), icon: 'copy' });
+ displayOptionsMenu.push({
+ description: 'Toggle Content Display Style',
+ event: () => {
+ this.Document.display = this.Document.display ? undefined : 'contents';
+ },
+ icon: 'copy',
+ });
+ displayOptionsMenu.push({
+ description: 'Toggle Vertical Centering',
+ event: () => {
+ this.Document.centerY = !this.Document.centerY;
+ },
+ icon: 'copy',
+ });
ContextMenu.Instance.addItem({ description: 'Display', subitems: displayOptionsMenu, icon: 'tv' });
};
@@ -346,14 +358,14 @@ export class CollectionGridView extends CollectionSubView() {
e,
returnFalse,
returnFalse,
- (e: PointerEvent, doubleTap?: boolean) => {
- if (doubleTap && !e.button) {
+ (clickEv: PointerEvent, doubleTap?: boolean) => {
+ if (doubleTap && !clickEv.button) {
undoBatch(
action(() => {
const text = Docs.Create.TextDocument('', { _width: 150, _height: 50 });
- FormattedTextBox.SetSelectOnLoad(text); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+ Doc.SetSelectOnLoad(text); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
Doc.AddDocToList(this.Document, this._props.fieldKey, text);
- this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(e.clientX, e.clientY))));
+ this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(clickEv.clientX, clickEv.clientY))));
})
)();
}
@@ -386,7 +398,7 @@ export class CollectionGridView extends CollectionSubView() {
width={this._props.PanelWidth()}
nodeList={this.contents.length ? this.contents : null}
layout={this.contents.length ? this.renderedLayoutList : undefined}
- childrenDraggable={this._props.isSelected() ? true : false}
+ childrenDraggable={!!this._props.isSelected()}
numCols={this.numCols}
rowHeight={this.rowHeight}
setLayout={this.setLayout}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 228af78aa..eac0dc0e1 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -1,25 +1,27 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { Toggle, ToggleType, Type } from 'browndash-components';
import { IReactionDisposer, action, makeObservable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Utils, emptyFunction, returnEmptyDoclist, returnTrue } from '../../../../Utils';
+import { ClientUtils, returnTrue } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
import { Doc, Opt } from '../../../../fields/Doc';
import { Height, Width } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
import { SettingsManager } from '../../../util/SettingsManager';
import { Transform } from '../../../util/Transform';
import { UndoStack } from '../../UndoStack';
import { DocumentLinksButton } from '../../nodes/DocumentLinksButton';
import { DocumentView } from '../../nodes/DocumentView';
import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup';
-import { CollectionStackedTimeline } from '../CollectionStackedTimeline';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionLinearView.scss';
@@ -46,13 +48,15 @@ export class CollectionLinearView extends CollectionSubView() {
this._dropDisposer?.();
this._widthDisposer?.();
this._selectedDisposer?.();
- this.childLayoutPairs.map((pair, ind) => ScriptCast(DocCast(pair.layout.proto)?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log));
+ this.childLayoutPairs.map(pair => ScriptCast(DocCast(pair.layout.proto)?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log));
}
componentDidMount() {
this._widthDisposer = reaction(
() => 5 + NumCast(this.dataDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (NumCast(doc._width) || this.dimension()) + tot + 4, 0) : 0),
- width => this.childDocs.length && (this.layoutDoc._width = width),
+ width => {
+ this.childDocs.length && (this.layoutDoc._width = width);
+ },
{ fireImmediately: true }
);
}
@@ -64,14 +68,14 @@ export class CollectionLinearView extends CollectionSubView() {
dimension = () => NumCast(this.layoutDoc._height);
getTransform = (ele: Opt<HTMLDivElement>) => {
if (!ele) return Transform.Identity();
- const { scale, translateX, translateY } = Utils.GetScreenTransform(ele);
+ const { translateX, translateY } = ClientUtils.GetScreenTransform(ele);
return new Transform(-translateX, -translateY, 1);
};
@action
exitLongLinks = () => {
if (DocumentLinksButton.StartLink?.Document) {
- action((e: React.PointerEvent<HTMLDivElement>) => Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc));
+ action(() => Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc));
}
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
@@ -97,8 +101,8 @@ export class CollectionLinearView extends CollectionSubView() {
e.preventDefault();
};
- getLinkUI = () => {
- return !DocumentLinksButton.StartLink ? null : (
+ getLinkUI = () =>
+ !DocumentLinksButton.StartLink ? null : (
<span className="bottomPopup-background" style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()}>
<span className="bottomPopup-text">
Creating link from:{' '}
@@ -108,7 +112,7 @@ export class CollectionLinearView extends CollectionSubView() {
</b>
</span>
- <Tooltip title={<div className="dash-tooltip">{'Toggle description pop-up'} </div>} placement="top">
+ <Tooltip title={<div className="dash-tooltip">Toggle description pop-up </div>} placement="top">
<span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}>
Labels: {LinkDescriptionPopup.Instance.showDescriptions ? LinkDescriptionPopup.Instance.showDescriptions : 'ON'}
</span>
@@ -121,16 +125,15 @@ export class CollectionLinearView extends CollectionSubView() {
</Tooltip>
</span>
);
- };
- getCurrentlyPlayingUI = () => {
- return !CollectionStackedTimeline.CurrentlyPlaying?.length ? null : (
+ getCurrentlyPlayingUI = () =>
+ !DocumentView.CurrentlyPlaying?.length ? null : (
<span className="bottomPopup-background">
<span className="bottomPopup-text">
Currently playing:
- {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => (
+ {DocumentView.CurrentlyPlaying.map((clip, i) => (
<>
- <span className="audio-title" onPointerDown={() => DocumentManager.Instance.showDocument(clip.Document, { willZoomCentered: true })}>
- {clip.Document.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? ' ' : ',')}
+ <span className="audio-title" onPointerDown={() => DocumentView.showDocument(clip.Document, { willZoomCentered: true })}>
+ {clip.Document.title + (i === DocumentView.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?.()} />{' '}
@@ -139,7 +142,6 @@ export class CollectionLinearView extends CollectionSubView() {
</span>
</span>
);
- };
getDisplayDoc = (doc: Doc, preview: boolean = false) => {
// hack to avoid overhead of making UndoStack,etc into DocumentView style Boxes. If the UndoStack is ever intended to become part of the persisten state of the dashboard, then this would have to change.
@@ -149,6 +151,7 @@ export class CollectionLinearView extends CollectionSubView() {
case '<CurrentlyPlayingUI>': return this.getCurrentlyPlayingUI();
case '<UndoStack>': return <UndoStack key={doc[Id]}/>;
case '<Branching>': return Doc.UserDoc().isBranchingMode ? <BranchingTrailManager key={doc[Id]} /> : null;
+ default:
}
const nested = doc._type_collection === CollectionViewType.Linear;
@@ -161,7 +164,9 @@ export class CollectionLinearView extends CollectionSubView() {
<div
className={preview ? 'preview' : `collectionLinearView-docBtn`}
key={doc[Id]}
- ref={r => (dref = r || undefined)}
+ ref={r => {
+ dref = r || undefined;
+ }}
style={{
pointerEvents: 'all',
width: NumCast(doc._width),
@@ -194,7 +199,7 @@ export class CollectionLinearView extends CollectionSubView() {
childFilters={this._props.childFilters}
childFiltersByRanges={this._props.childFiltersByRanges}
searchFilterDocs={this._props.searchFilterDocs}
- hideResizeHandles={true}
+ hideResizeHandles
/>
</div>
);
@@ -220,30 +225,26 @@ export class CollectionLinearView extends CollectionSubView() {
ScriptCast(this.Document.onClick)?.script.run({ this: this.Document }, console.log);
}}
tooltip={isExpanded ? 'Close' : 'Open'}
- fillWidth={true}
- align={'center'}
+ fillWidth
+ align="center"
/>
);
return (
<div className={`collectionLinearView-outer ${this.layoutDoc.linearView_SubMenu}`} style={{ backgroundColor: this.layoutDoc.linearView_IsOpen ? undefined : 'transparent' }}>
<div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension(), pointerEvents: 'all' }}>
- {
- <>
- {!this.layoutDoc.linearView_Expandable ? null : menuOpener}
- {!this.layoutDoc.linearView_IsOpen ? null : (
- <div
- className="collectionLinearView-content"
- style={{
- height: this.dimension(),
- flexDirection: flexDir as any,
- gap: flexGap,
- }}>
- {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
- </div>
- )}
- </>
- }
+ {!this.layoutDoc.linearView_Expandable ? null : menuOpener}
+ {!this.layoutDoc.linearView_IsOpen ? null : (
+ <div
+ className="collectionLinearView-content"
+ style={{
+ height: this.dimension(),
+ flexDirection: flexDir as any,
+ gap: flexGap,
+ }}>
+ {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
+ </div>
+ )}
</div>
</div>
);
diff --git a/src/client/views/collections/collectionLinear/index.ts b/src/client/views/collections/collectionLinear/index.ts
index ff73e14ae..ab3b4b0b5 100644
--- a/src/client/views/collections/collectionLinear/index.ts
+++ b/src/client/views/collections/collectionLinear/index.ts
@@ -1 +1 @@
-export * from "./CollectionLinearView"; \ No newline at end of file
+export * from './CollectionLinearView';
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 125dd2781..b8509a005 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { Button, IconButton } from 'browndash-components';
@@ -7,7 +9,7 @@ import * as React from 'react';
import { FaChevronRight } from 'react-icons/fa';
import { Doc, DocListCast } from '../../../../fields/Doc';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
import { SettingsManager } from '../../../util/SettingsManager';
import { Transform } from '../../../util/Transform';
import { undoBatch, undoable } from '../../../util/UndoManager';
@@ -16,6 +18,7 @@ import { CollectionSubView } from '../CollectionSubView';
import './CollectionMulticolumnView.scss';
import ResizeBar from './MulticolumnResizer';
import WidthLabel from './MulticolumnWidthLabel';
+import { dropActionType } from '../../../util/DropActionTypes';
interface WidthSpecifier {
magnitude: number;
@@ -80,7 +83,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
private get resolvedLayoutInformation(): LayoutData {
let starSum = 0;
const widthSpecifiers: WidthSpecifier[] = [];
- this.childLayouts.map(layout => {
+ this.childLayouts.forEach(layout => {
const unit = StrCast(layout._dimUnit, '*');
const magnitude = NumCast(layout._dimMagnitude, this.minimumDim);
if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
@@ -140,6 +143,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
return this._props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this.Document._xMargin);
}
+ return undefined;
}
/**
@@ -159,6 +163,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
if (this.resolvedLayoutInformation && this.totalRatioAllocation !== undefined) {
return this.totalRatioAllocation / this.resolvedLayoutInformation.starSum;
}
+ return undefined;
}
/**
@@ -175,7 +180,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
* or the ratio width evaluated to a pixel value
*/
private lookupPixels = (layout: Doc): number => {
- const columnUnitLength = this.columnUnitLength;
+ const { columnUnitLength } = this;
if (columnUnitLength === undefined) {
return 0; // we're still waiting on promises to resolve
}
@@ -193,19 +198,19 @@ export class CollectionMulticolumnView extends CollectionSubView() {
* documents before the target.
*/
private lookupIndividualTransform = (layout: Doc) => {
- const columnUnitLength = this.columnUnitLength;
+ const { columnUnitLength } = this;
if (columnUnitLength === undefined) {
return Transform.Identity(); // we're still waiting on promises to resolve
}
let offset = 0;
- var xf = Transform.Identity();
- this.childLayouts.map(candidate => {
+ // eslint-disable-next-line no-restricted-syntax
+ for (const { layout: candidate } of this.childLayoutPairs) {
if (candidate === layout) {
- return (xf = this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0));
+ return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
}
offset += this.lookupPixels(candidate) + resizerWidth;
- });
- return xf;
+ }
+ return Transform.Identity();
};
@undoBatch
@@ -213,7 +218,9 @@ export class CollectionMulticolumnView extends CollectionSubView() {
let dropInd = -1;
if (de.complete.docDragData && this._mainCont) {
let curInd = -1;
- de.complete.docDragData?.droppedDocuments.forEach(d => (curInd = this.childDocs.indexOf(d)));
+ de.complete.docDragData?.droppedDocuments.forEach(d => {
+ curInd = this.childDocs.indexOf(d);
+ });
Array.from(this._mainCont.children).forEach((child, index) => {
const brect = child.getBoundingClientRect();
if (brect.x < de.x && brect.x + brect.width > de.x) {
@@ -265,7 +272,6 @@ export class CollectionMulticolumnView extends CollectionSubView() {
this.lookupIndividualTransform(childLayout)
.translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin))
.scale(this._props.NativeDimScaling?.() || 1);
- const shouldNotScale = () => this._props.fitContentsToBox?.() || BoolCast(childLayout.freeform_fitContentsToBox);
return (
<DocumentView
Document={childLayout}
@@ -281,11 +287,11 @@ export class CollectionMulticolumnView extends CollectionSubView() {
dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
onClickScript={this.onChildClickHandler}
onDoubleClickScript={this.onChildDoubleClickHandler}
- suppressSetHeight={true}
+ suppressSetHeight
ScreenToLocalTransform={dxf}
isContentActive={this.isChildContentActive}
isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
- hideResizeHandles={childLayout.layout_fitWidth || this._props.childHideResizeHandles ? true : false}
+ hideResizeHandles={!!(childLayout.layout_fitWidth || this._props.childHideResizeHandles)}
hideDecorationTitle={this._props.childHideDecorationTitle}
fitContentsToBox={this._props.fitContentsToBox}
focus={this._props.focus}
@@ -312,17 +318,19 @@ export class CollectionMulticolumnView extends CollectionSubView() {
const collector: JSX.Element[] = [];
this.childLayouts.forEach((layout, i) => {
collector.push(
+ // eslint-disable-next-line react/no-array-index-key
<Tooltip title={'Tab: ' + StrCast(layout.title)} key={'wrapper' + i}>
<div className="document-wrapper" style={{ flexDirection: 'column', width: this.lookupPixels(layout) }}>
{this.getDisplayDoc(layout)}
{this.layoutDoc._chromeHidden ? null : (
- <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(e => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
+ <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(() => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
)}
<WidthLabel layout={layout} collectionDoc={this.Document} />
</div>
</Tooltip>,
<ResizeBar
width={resizerWidth}
+ // eslint-disable-next-line react/no-array-index-key
key={'resizer' + i}
styleProvider={this._props.styleProvider}
isContentActive={this._props.isContentActive}
@@ -353,14 +361,29 @@ export class CollectionMulticolumnView extends CollectionSubView() {
{this.contents}
{!this._startIndex ? null : (
<Tooltip title="scroll back">
- <div style={{ position: 'absolute', bottom: 0, left: 0, background: SettingsManager.userVariantColor }} onClick={action(e => (this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown)))}>
- <Button tooltip="Scroll back" icon={<FontAwesomeIcon icon="chevron-left" size="lg" />} onClick={action(e => (this._startIndex = Math.max(0, this._startIndex - this.maxShown)))} color={SettingsManager.userColor} />
+ <div
+ style={{ position: 'absolute', bottom: 0, left: 0, background: SettingsManager.userVariantColor }}
+ onClick={action(() => {
+ this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown);
+ })}>
+ <Button
+ tooltip="Scroll back"
+ icon={<FontAwesomeIcon icon="chevron-left" size="lg" />}
+ onClick={action(() => {
+ this._startIndex = Math.max(0, this._startIndex - this.maxShown);
+ })}
+ color={SettingsManager.userColor}
+ />
</div>
</Tooltip>
)}
{this._startIndex > this.childLayoutPairs.length - 1 || !this.maxShown ? null : (
<Tooltip title="scroll forward">
- <div style={{ position: 'absolute', bottom: 0, right: 0, background: SettingsManager.userVariantColor }} onClick={action(e => (this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown)))}>
+ <div
+ style={{ position: 'absolute', bottom: 0, right: 0, background: SettingsManager.userVariantColor }}
+ onClick={action(() => {
+ this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown);
+ })}>
<IconButton icon={<FaChevronRight />} color={SettingsManager.userColor} />
</div>
</Tooltip>
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 17bf3e50c..3fe3d5343 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -3,7 +3,8 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast } from '../../../../fields/Doc';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
import { DocumentView } from '../../nodes/DocumentView';
@@ -11,6 +12,7 @@ import { CollectionSubView } from '../CollectionSubView';
import './CollectionMultirowView.scss';
import HeightLabel from './MultirowHeightLabel';
import ResizeBar from './MultirowResizer';
+
interface HeightSpecifier {
magnitude: number;
unit: string;
@@ -63,7 +65,7 @@ export class CollectionMultirowView extends CollectionSubView() {
private get resolvedLayoutInformation(): LayoutData {
let starSum = 0;
const heightSpecifiers: HeightSpecifier[] = [];
- this.childLayoutPairs.map(pair => {
+ this.childLayoutPairs.forEach(pair => {
const unit = StrCast(pair.layout._dimUnit, '*');
const magnitude = NumCast(pair.layout._dimMagnitude, this.minimumDim);
if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
@@ -123,6 +125,7 @@ export class CollectionMultirowView extends CollectionSubView() {
if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
return this._props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this.Document._yMargin);
}
+ return undefined;
}
/**
@@ -142,6 +145,7 @@ export class CollectionMultirowView extends CollectionSubView() {
if (this.resolvedLayoutInformation && this.totalRatioAllocation !== undefined) {
return this.totalRatioAllocation / this.resolvedLayoutInformation.starSum;
}
+ return undefined;
}
/**
@@ -158,13 +162,12 @@ export class CollectionMultirowView extends CollectionSubView() {
* or the ratio width evaluated to a pixel value
*/
private lookupPixels = (layout: Doc): number => {
- const rowUnitLength = this.rowUnitLength;
- if (rowUnitLength === undefined) {
+ if (this.rowUnitLength === undefined) {
return 0; // we're still waiting on promises to resolve
}
let height = NumCast(layout._dimMagnitude, this.minimumDim);
if (StrCast(layout._dimUnit, '*') === DimUnit.Ratio) {
- height *= rowUnitLength;
+ height *= this.rowUnitLength;
}
return height;
};
@@ -176,11 +179,11 @@ export class CollectionMultirowView extends CollectionSubView() {
* documents before the target.
*/
private lookupIndividualTransform = (layout: Doc) => {
- const rowUnitLength = this.rowUnitLength;
- if (rowUnitLength === undefined) {
+ if (this.rowUnitLength === undefined) {
return Transform.Identity(); // we're still waiting on promises to resolve
}
let offset = 0;
+ // eslint-disable-next-line no-restricted-syntax
for (const { layout: candidate } of this.childLayoutPairs) {
if (candidate === layout) {
return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
@@ -195,7 +198,9 @@ export class CollectionMultirowView extends CollectionSubView() {
let dropInd = -1;
if (de.complete.docDragData && this._mainCont) {
let curInd = -1;
- de.complete.docDragData?.droppedDocuments.forEach(d => (curInd = this.childDocs.indexOf(d)));
+ de.complete.docDragData?.droppedDocuments.forEach(d => {
+ curInd = this.childDocs.indexOf(d);
+ });
Array.from(this._mainCont.children).forEach((child, index) => {
const brect = child.getBoundingClientRect();
if (brect.y < de.y && brect.y + brect.height > de.y) {
@@ -247,7 +252,6 @@ export class CollectionMultirowView extends CollectionSubView() {
this.lookupIndividualTransform(layout)
.translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin))
.scale(this._props.NativeDimScaling?.() || 1);
- const shouldNotScale = () => this._props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox);
return (
<DocumentView
Document={layout}
@@ -266,7 +270,7 @@ export class CollectionMultirowView extends CollectionSubView() {
ScreenToLocalTransform={dxf}
isContentActive={this.isChildContentActive}
isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
- hideResizeHandles={layout.layout_fitWidth || this._props.childHideResizeHandles ? true : false}
+ hideResizeHandles={!!(layout.layout_fitWidth || this._props.childHideResizeHandles)}
hideDecorationTitle={this._props.childHideDecorationTitle}
fitContentsToBox={this._props.fitContentsToBox}
focus={this._props.focus}
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
index d580d9c52..931e2c5e0 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
@@ -1,10 +1,11 @@
+/* eslint-disable react/require-default-props */
import { action } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc } from '../../../../fields/Doc';
import { NumCast, StrCast } from '../../../../fields/Types';
import { UndoManager } from '../../../util/UndoManager';
-import { StyleProp } from '../../StyleProvider';
+import { StyleProp } from '../../StyleProp';
import { StyleProviderFuncType } from '../../nodes/FieldView';
import { DimUnit } from './CollectionMulticolumnView';
@@ -52,27 +53,6 @@ export default class ResizeBar extends React.Component<ResizerProps> {
}
};
- private get isActivated() {
- const { toLeft, toRight } = this.props;
- if (toLeft && toRight) {
- if (StrCast(toLeft._dimUnit, '*') === DimUnit.Pixel && StrCast(toRight._dimUnit, '*') === DimUnit.Pixel) {
- return false;
- }
- return true;
- } else if (toLeft) {
- if (StrCast(toLeft._dimUnit, '*') === DimUnit.Pixel) {
- return false;
- }
- return true;
- } else if (toRight) {
- if (StrCast(toRight._dimUnit, '*') === DimUnit.Pixel) {
- return false;
- }
- return true;
- }
- return false;
- }
-
@action
private onPointerUp = () => {
window.removeEventListener('pointermove', this.onPointerMove);
@@ -90,7 +70,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
width: this.props.width,
backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor),
}}>
- <div className={'multiColumnResizer-hdl'} onPointerDown={e => this.registerResizing(e)} />
+ <div className="multiColumnResizer-hdl" onPointerDown={e => this.registerResizing(e)} />
</div>
);
}
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
index a9579d931..a7a0b3457 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
@@ -19,7 +19,7 @@ export default class WidthLabel extends React.Component<WidthLabelProps> {
const getUnit = () => StrCast(layout.dimUnit);
const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(3));
return (
- <div className={'label-wrapper'}>
+ <div className="label-wrapper">
<EditableView
GetValue={getMagnitude}
SetValue={value => {
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
index 878c7ff3c..66215f109 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
import { computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -20,7 +21,7 @@ export default class HeightLabel extends React.Component<HeightLabelProps> {
const getUnit = () => StrCast(layout.dimUnit);
const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(decimals ?? 3));
return (
- <div className={'label-wrapper'}>
+ <div className="label-wrapper">
<EditableView
GetValue={getMagnitude}
SetValue={value => {
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
index 73d08d5ef..cff0a8b4c 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
@@ -1,10 +1,11 @@
+/* eslint-disable react/require-default-props */
import { action } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc } from '../../../../fields/Doc';
import { NumCast, StrCast } from '../../../../fields/Types';
import { UndoManager } from '../../../util/UndoManager';
-import { StyleProp } from '../../StyleProvider';
+import { StyleProp } from '../../StyleProp';
import { StyleProviderFuncType } from '../../nodes/FieldView';
import { DimUnit } from './CollectionMultirowView';
@@ -33,7 +34,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
};
private onPointerMove = ({ movementY }: PointerEvent) => {
- const { toTop: toTop, toBottom: toBottom, columnUnitLength } = this.props;
+ const { toTop, toBottom, columnUnitLength } = this.props;
const movingDown = movementY > 0;
const toNarrow = movingDown ? toBottom : toTop;
const toWiden = movingDown ? toTop : toBottom;
@@ -50,27 +51,6 @@ export default class ResizeBar extends React.Component<ResizerProps> {
}
};
- private get isActivated() {
- const { toTop, toBottom } = this.props;
- if (toTop && toBottom) {
- if (StrCast(toTop._dimUnit, '*') === DimUnit.Pixel && StrCast(toBottom._dimUnit, '*') === DimUnit.Pixel) {
- return false;
- }
- return true;
- } else if (toTop) {
- if (StrCast(toTop._dimUnit, '*') === DimUnit.Pixel) {
- return false;
- }
- return true;
- } else if (toBottom) {
- if (StrCast(toBottom._dimUnit, '*') === DimUnit.Pixel) {
- return false;
- }
- return true;
- }
- return false;
- }
-
@action
private onPointerUp = () => {
window.removeEventListener('pointermove', this.onPointerMove);
@@ -88,7 +68,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
height: this.props.height,
backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor),
}}>
- <div className={'multiRowResizer-hdl'} onPointerDown={e => this.registerResizing(e)} />
+ <div className="multiRowResizer-hdl" onPointerDown={e => this.registerResizing(e)} />
</div>
);
}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 0244db891..023b72778 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,45 +1,39 @@
+/* eslint-disable no-restricted-syntax */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Popup, PopupTrigger, Type } from 'browndash-components';
import { ObservableMap, action, computed, makeObservable, observable, observe } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
-import { Doc, DocListCast, Field, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
+import { returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
+import { Doc, DocListCast, Field, FieldType, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
import { DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
+import { ColumnType } from '../../../../fields/SchemaHeaderField';
import { BoolCast, Cast, NumCast, StrCast } from '../../../../fields/Types';
-import { DocUtils, Docs, DocumentOptions, FInfo } from '../../../documents/Documents';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../../util/DragManager';
-import { SelectionManager } from '../../../util/SelectionManager';
+import { DocUtils } from '../../../documents/DocUtils';
+import { Docs, DocumentOptions, FInfo } from '../../../documents/Documents';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
import { SettingsManager } from '../../../util/SettingsManager';
import { undoBatch, undoable } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
import { EditableView } from '../../EditableView';
import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { DefaultStyleProvider, StyleProp } from '../../StyleProvider';
+import { StyleProp } from '../../StyleProp';
+import { DefaultStyleProvider } from '../../StyleProvider';
import { Colors } from '../../global/globalEnums';
import { DocumentView } from '../../nodes/DocumentView';
-import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView';
-import { KeyValueBox } from '../../nodes/KeyValueBox';
+import { FieldViewProps } from '../../nodes/FieldView';
+import { FocusViewOptions } from '../../nodes/FocusViewOptions';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionSchemaView.scss';
import { SchemaColumnHeader } from './SchemaColumnHeader';
import { SchemaRowBox } from './SchemaRowBox';
-const { SCHEMA_NEW_NODE_HEIGHT } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore
-export enum ColumnType {
- Number,
- String,
- Boolean,
- Date,
- Image,
- RTF,
- Enumeration,
- Any,
-}
+const { SCHEMA_NEW_NODE_HEIGHT } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore
export const FInfotoColType: { [key: string]: ColumnType } = {
string: ColumnType.String,
@@ -102,17 +96,13 @@ export class CollectionSchemaView extends CollectionSubView() {
@computed get _selectedDocs() {
// get all selected documents then filter out any whose parent is not this schema document
- const selected = SelectionManager.Docs.filter(doc => this.childDocs.includes(doc));
- // SelectionManager... filter(doc => this.childDocs.includes(doc))
- //DocCast(doc.embedContainer)[DocData] === this.dataDoc
- //SelectionManager.Docs.forEach(doc => console.log("index: " + this.rowIndex(doc) + " equal: " + Doc.AreProtosEqual(DocCast(doc.embedContainer), this.Document)));
+ const selected = DocumentView.SelectedDocs().filter(doc => this.childDocs.includes(doc));
if (!selected.length) {
- for (const sel of SelectionManager.Docs) {
- const contextPath = DocumentManager.GetContextPath(sel, true);
- if (contextPath.includes(this.Document)) {
- const parentInd = contextPath.indexOf(this.Document);
- return parentInd < contextPath.length - 1 ? [contextPath[parentInd + 1]] : [];
- }
+ // if no schema doc is directly selected, test if a child of a schema doc is selected (such as in the preview window)
+ const childOfSchemaDoc = DocumentView.SelectedDocs().find(sel => DocumentView.getContextPath(sel, true).includes(this.Document));
+ if (childOfSchemaDoc) {
+ const contextPath = DocumentView.getContextPath(childOfSchemaDoc, true);
+ return [contextPath[contextPath.indexOf(childOfSchemaDoc) - 1]]; // the schema doc that is "selected" by virtue of one of its children being selected
}
}
return selected;
@@ -176,8 +166,8 @@ export class CollectionSchemaView extends CollectionSubView() {
Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => this.fieldInfos.set(pair[0], pair[1]));
this._keysDisposer = observe(
this.dataDoc[this.fieldKey ?? 'data'] as List<Doc>,
- change => {
- switch (change.type as any) {
+ (change: any) => {
+ switch (change.type) {
case 'splice':
// prettier-ignore
(change as any).added.forEach((doc: Doc) => // for each document added
@@ -185,7 +175,9 @@ export class CollectionSchemaView extends CollectionSubView() {
Object.keys(proto).forEach(action(key => // check if any of its keys are new, and add them
!this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo("-no description-", key === 'author'))))));
break;
- case 'update': //let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list
+ case 'update': // let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list
+ break;
+ default:
}
},
true
@@ -197,6 +189,9 @@ export class CollectionSchemaView extends CollectionSubView() {
document.removeEventListener('keydown', this.onKeyDown);
}
+ // ViewBoxInterface overrides
+ override isUnstyledView = returnTrue; // used by style provider : turns off opacity, animation effects, scaling
+
rowIndex = (doc: Doc) => this.sortedDocs.docs.indexOf(doc);
@action
@@ -211,7 +206,7 @@ export class CollectionSchemaView extends CollectionSubView() {
if (lastIndex >= 0 && lastIndex < this.childDocs.length - 1) {
const newDoc = this.sortedDocs.docs[lastIndex + 1];
if (this._selectedDocs.includes(newDoc)) {
- SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
+ DocumentView.DeselectView(DocumentView.getFirstDocumentView(curDoc));
this.deselectCell(curDoc);
} else {
const shift: boolean = e.shiftKey;
@@ -232,7 +227,7 @@ export class CollectionSchemaView extends CollectionSubView() {
if (firstIndex > 0 && firstIndex < this.childDocs.length) {
const newDoc = this.sortedDocs.docs[firstIndex - 1];
if (this._selectedDocs.includes(newDoc)) {
- SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
+ DocumentView.DeselectView(DocumentView.getFirstDocumentView(curDoc));
this.deselectCell(curDoc);
} else {
const shift: boolean = e.shiftKey;
@@ -265,7 +260,9 @@ export class CollectionSchemaView extends CollectionSubView() {
}
case 'Escape': {
this.deselectAllCells();
+ break;
}
+ default:
}
}
};
@@ -287,7 +284,7 @@ export class CollectionSchemaView extends CollectionSubView() {
this.addNewKey(newKey, defaultVal);
}
- let currKeys = [...this.columnKeys];
+ const currKeys = [...this.columnKeys];
currKeys[index] = newKey;
this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
};
@@ -304,13 +301,16 @@ export class CollectionSchemaView extends CollectionSubView() {
const newDesiredTableWidth = currWidths.reduce((w, cw) => w + cw, 0);
this.layoutDoc.schema_columnWidths = new List<number>(currWidths.map(w => (w / newDesiredTableWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth)));
- let currKeys = this.columnKeys.slice();
+ const currKeys = this.columnKeys.slice();
currKeys.splice(0, 0, key);
this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
};
@action
- addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[DocData][key] = defaultVal));
+ addNewKey = (key: string, defaultVal: any) =>
+ this.childDocs.forEach(doc => {
+ doc[DocData][key] = defaultVal;
+ });
@undoBatch
removeColumn = (index: number) => {
@@ -320,7 +320,7 @@ export class CollectionSchemaView extends CollectionSubView() {
const newDesiredTableWidth = currWidths.reduce((w, cw) => w + cw, 0);
this.layoutDoc.schema_columnWidths = new List<number>(currWidths.map(w => (w / newDesiredTableWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth)));
- let currKeys = this.columnKeys.slice();
+ const currKeys = this.columnKeys.slice();
currKeys.splice(index, 1);
this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
};
@@ -328,7 +328,7 @@ export class CollectionSchemaView extends CollectionSubView() {
@action
startResize = (e: any, index: number) => {
this._displayColumnWidths = this.storedColumnWidths;
- setupMoveUpEvents(this, e, (e, delta) => this.resizeColumn(e, index), this.finishResize, emptyFunction);
+ setupMoveUpEvents(this, e, moveEv => this.resizeColumn(moveEv, index), this.finishResize, emptyFunction);
};
@action
@@ -370,11 +370,11 @@ export class CollectionSchemaView extends CollectionSubView() {
if (this._selectedCol === fromIndex) this._selectedCol = toIndex;
else if (toIndex === this._selectedCol) this._selectedCol = fromIndex; //keeps selected cell consistent
- let currKeys = this.columnKeys.slice();
+ const currKeys = this.columnKeys.slice();
currKeys.splice(toIndex, 0, currKeys.splice(fromIndex, 1)[0]);
this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
- let currWidths = this.storedColumnWidths.slice();
+ const currWidths = this.storedColumnWidths.slice();
currWidths.splice(toIndex, 0, currWidths.splice(fromIndex, 1)[0]);
this.layoutDoc.schema_columnWidths = new List<number>(currWidths);
@@ -389,15 +389,6 @@ export class CollectionSchemaView extends CollectionSubView() {
const dragEles = [this._colEles[index]];
this.childDocs.forEach(doc => dragEles.push(this._rowEles.get(doc).children[1].children[index]));
DragManager.StartColumnDrag(dragEles, dragData, e.x, e.y);
-
- // document.removeEventListener('pointermove', this.highlightDropColumn);
- // document.addEventListener('pointermove', this.highlightDropColumn);
- // let stopHighlight = (e: PointerEvent) => {
- // document.removeEventListener('pointermove', this.highlightDropColumn);
- // document.removeEventListener('pointerup', stopHighlight);
- // };
- // document.addEventListener('pointerup', stopHighlight);
-
return true;
};
@@ -498,14 +489,14 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@action
- addDocToSelection = (doc: Doc, extendSelection: boolean, index: number) => {
- const rowDocView = DocumentManager.Instance.getDocumentView(doc);
- if (rowDocView) SelectionManager.SelectView(rowDocView, extendSelection);
+ addDocToSelection = (doc: Doc, extendSelection: boolean) => {
+ const rowDocView = DocumentView.getDocumentView(doc);
+ if (rowDocView) DocumentView.SelectView(rowDocView, extendSelection);
};
@action
clearSelection = () => {
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
this.deselectAllCells();
};
@@ -534,10 +525,10 @@ export class CollectionSchemaView extends CollectionSubView() {
if (shiftKey && lastSelected && !this._selectedDocs.includes(doc)) this.selectRows(doc, lastSelected);
else if (ctrlKey) {
if (lastSelected && this._selectedDocs.includes(doc)) {
- SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(doc));
+ DocumentView.DeselectView(DocumentView.getFirstDocumentView(doc));
this.deselectCell(doc);
- } else this.addDocToSelection(doc, true, index);
- } else this.addDocToSelection(doc, false, index);
+ } else this.addDocToSelection(doc, true);
+ } else this.addDocToSelection(doc, false);
this._selectedCol = col;
if (this._lowestSelectedIndex === -1 || index < this._lowestSelectedIndex) this._lowestSelectedIndex = index;
@@ -595,7 +586,7 @@ export class CollectionSchemaView extends CollectionSubView() {
this.dataDoc[this.fieldKey ?? 'data'] = new List<Doc>([...this.sortedDocs.docs]);
this.clearSelection();
draggedDocs.forEach(doc => {
- DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true));
+ DocumentView.addViewRenderedCb(doc, dv => dv.select(true));
});
this._lowestSelectedIndex = Math.min(...draggedDocs?.map(doc => this.rowIndex(doc)));
return true;
@@ -604,13 +595,13 @@ export class CollectionSchemaView extends CollectionSubView() {
};
onExternalDrop = async (e: React.DragEvent): Promise<void> => {
- super.onExternalDrop(e, {}, undoBatch(action(docus => docus.map((doc: Doc) => this.addDocument(doc)))));
+ super.onExternalDrop(e, {}, undoBatch(action(docs => docs.map((doc: Doc) => this.addDocument(doc)))));
};
onDividerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, emptyFunction);
@action
- onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
+ onDividerMove = (e: PointerEvent) => {
const nativeWidth = this._previewRef!.getBoundingClientRect();
const minWidth = 40;
const maxWidth = 1000;
@@ -640,37 +631,76 @@ export class CollectionSchemaView extends CollectionSubView() {
const rect = found.getBoundingClientRect();
const localRect = this.ScreenToLocalBoxXf().transformBounds(rect.left, rect.top, rect.width, rect.height);
if (localRect.y < this.rowHeightFunc() || localRect.y + localRect.height > this._props.PanelHeight()) {
- let focusSpeed = options.zoomTime ?? 50;
+ const focusSpeed = options.zoomTime ?? 50;
smoothScroll(focusSpeed, this._tableContentRef!, localRect.y + this._tableContentRef!.scrollTop - this.rowHeightFunc(), options.easeFunc);
return focusSpeed;
}
}
+ return undefined;
};
@computed get fieldDefaultInput() {
switch (this._newFieldType) {
case ColumnType.Number:
- return <input type="number" name="" id="" value={this._newFieldDefault ?? 0} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} />;
+ return (
+ <input
+ type="number"
+ name=""
+ id=""
+ value={this._newFieldDefault ?? 0}
+ onPointerDown={e => e.stopPropagation()}
+ onChange={action((e: any) => {
+ this._newFieldDefault = e.target.value;
+ })}
+ />
+ );
case ColumnType.Boolean:
return (
<>
- <input type="checkbox" name="" id="" value={this._newFieldDefault} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.checked))} />
+ <input
+ type="checkbox"
+ name=""
+ id=""
+ value={this._newFieldDefault}
+ onPointerDown={e => e.stopPropagation()}
+ onChange={action((e: any) => {
+ this._newFieldDefault = e.target.checked;
+ })}
+ />
{this._newFieldDefault ? 'true' : 'false'}
</>
);
case ColumnType.String:
- return <input type="text" name="" id="" value={this._newFieldDefault ?? ''} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} />;
+ return (
+ <input
+ type="text"
+ name=""
+ id=""
+ value={this._newFieldDefault ?? ''}
+ onPointerDown={e => e.stopPropagation()}
+ onChange={action((e: any) => {
+ this._newFieldDefault = e.target.value;
+ })}
+ />
+ );
+ default:
+ return undefined;
}
}
onSearchKeyDown = (e: React.KeyboardEvent) => {
switch (e.key) {
case 'Enter':
- this._menuKeys.length > 0 && this._menuValue.length > 0 ? this.setKey(this._menuKeys[0]) : action(() => (this._makeNewField = true))();
+ this._menuKeys.length > 0 && this._menuValue.length > 0
+ ? this.setKey(this._menuKeys[0])
+ : action(() => {
+ this._makeNewField = true;
+ })();
break;
case 'Escape':
this.closeColumnMenu();
break;
+ default:
}
};
@@ -693,9 +723,9 @@ export class CollectionSchemaView extends CollectionSubView() {
}
});
if (selectedDocs.length === 1) {
- this.childDocs.forEach(doc => KeyValueBox.SetField(doc, key, value));
+ this.childDocs.forEach(doc => Doc.SetField(doc, key, value));
} else {
- selectedDocs.forEach(doc => KeyValueBox.SetField(doc, key, value));
+ selectedDocs.forEach(doc => Doc.SetField(doc, key, value));
}
return true;
};
@@ -704,7 +734,7 @@ export class CollectionSchemaView extends CollectionSubView() {
this.childDocs.forEach(doc => {
let docIsSelected = this._selectedCells && !(this._selectedCells?.filter(d => d === doc).length === 0);
if (docIsSelected) {
- KeyValueBox.SetField(doc, key, value);
+ Doc.SetField(doc, key, value);
}
});
return true;
@@ -723,7 +753,9 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@action
- closeColumnMenu = () => (this._columnMenuIndex = undefined);
+ closeColumnMenu = () => {
+ this._columnMenuIndex = undefined;
+ };
@action
openFilterMenu = (index: number) => {
@@ -732,7 +764,9 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@action
- closeFilterMenu = () => (this._filterColumnIndex = undefined);
+ closeFilterMenu = () => {
+ this._filterColumnIndex = undefined;
+ };
openContextMenu = (x: number, y: number, index: number) => {
this.closeColumnMenu();
@@ -762,7 +796,7 @@ export class CollectionSchemaView extends CollectionSubView() {
this._menuKeys = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase()));
};
- getFieldFilters = (field: string) => StrListCast(this.Document._childFilters).filter(filter => filter.split(Doc.FilterSep)[0] == field);
+ getFieldFilters = (field: string) => StrListCast(this.Document._childFilters).filter(filter => filter.split(Doc.FilterSep)[0] === field);
removeFieldFilters = (field: string) => {
this.getFieldFilters(field).forEach(filter => Doc.setDocFilter(this.Document, field, filter.split(Doc.FilterSep)[1], 'remove'));
@@ -774,11 +808,14 @@ export class CollectionSchemaView extends CollectionSubView() {
case 'Escape':
this.closeFilterMenu();
break;
+ default:
}
};
@action
- updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => (this._filterSearchValue = e.target.value);
+ updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._filterSearchValue = e.target.value;
+ };
@computed get newFieldMenu() {
return (
@@ -787,7 +824,7 @@ export class CollectionSchemaView extends CollectionSubView() {
<input
type="radio"
name="newFieldType"
- checked={this._newFieldType == ColumnType.Number}
+ checked={this._newFieldType === ColumnType.Number}
onChange={action(() => {
this._newFieldType = ColumnType.Number;
this._newFieldDefault = 0;
@@ -799,7 +836,7 @@ export class CollectionSchemaView extends CollectionSubView() {
<input
type="radio"
name="newFieldType"
- checked={this._newFieldType == ColumnType.Boolean}
+ checked={this._newFieldType === ColumnType.Boolean}
onChange={action(() => {
this._newFieldType = ColumnType.Boolean;
this._newFieldDefault = false;
@@ -811,7 +848,7 @@ export class CollectionSchemaView extends CollectionSubView() {
<input
type="radio"
name="newFieldType"
- checked={this._newFieldType == ColumnType.String}
+ checked={this._newFieldType === ColumnType.String}
onChange={action(() => {
this._newFieldType = ColumnType.String;
this._newFieldDefault = '';
@@ -823,7 +860,7 @@ export class CollectionSchemaView extends CollectionSubView() {
<div className="schema-key-warning">{this._newFieldWarning}</div>
<div
className="schema-column-menu-button"
- onPointerDown={action(e => {
+ onPointerDown={action(() => {
if (this.documentKeys.includes(this._menuValue)) {
this._newFieldWarning = 'Field already exists';
} else if (this._menuValue.length === 0) {
@@ -850,7 +887,7 @@ export class CollectionSchemaView extends CollectionSubView() {
<div className="schema-key-search">
<div
className="schema-column-menu-button"
- onPointerDown={action(e => {
+ onPointerDown={action((e: any) => {
e.stopPropagation();
this._makeNewField = true;
})}>
@@ -888,7 +925,7 @@ export class CollectionSchemaView extends CollectionSubView() {
}
@computed get renderColumnMenu() {
- const x = this._columnMenuIndex! == -1 ? 0 : this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth);
+ const x = this._columnMenuIndex! === -1 ? 0 : this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth);
return (
<div className="schema-column-menu" style={{ left: x, minWidth: CollectionSchemaView._minColWidth }}>
<input className="schema-key-search-input" type="text" onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} />
@@ -897,7 +934,6 @@ export class CollectionSchemaView extends CollectionSubView() {
);
}
get renderKeysMenu() {
- //console.log('RNDERMENUT:' + this._columnMenuIndex);
return (
<div className="schema-column-menu" style={{ left: 0, minWidth: CollectionSchemaView._minColWidth }}>
<input className="schema-key-search-input" type="text" onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} />
@@ -931,7 +967,7 @@ export class CollectionSchemaView extends CollectionSubView() {
type="checkbox"
onPointerDown={e => e.stopPropagation()}
onClick={e => e.stopPropagation()}
- onChange={action(e => {
+ onChange={action((e: any) => {
if (e.target.checked) {
Doc.setDocFilter(this.Document, columnKey, key, 'check');
} else {
@@ -954,7 +990,7 @@ export class CollectionSchemaView extends CollectionSubView() {
{this.renderFilterOptions}
<div
className="schema-column-menu-button"
- onPointerDown={action(e => {
+ onPointerDown={action((e: any) => {
e.stopPropagation();
this.closeFilterMenu();
})}>
@@ -985,9 +1021,9 @@ export class CollectionSchemaView extends CollectionSubView() {
? staticDocs
: [...staticDocs].sort((docA, docB) => {
// this sorts the documents based on the selected field. returning -1 for a before b, 0 for a = b, 1 for a > b
- const aStr = Field.toString(docA[field] as Field);
- const bStr = Field.toString(docB[field] as Field);
- var out = 0;
+ const aStr = Field.toString(docA[field] as FieldType);
+ const bStr = Field.toString(docB[field] as FieldType);
+ let out = 0;
if (aStr < bStr) out = -1;
if (aStr > bStr) out = 1;
if (desc) out *= -1;
@@ -1024,7 +1060,7 @@ export class CollectionSchemaView extends CollectionSubView() {
placement="right"
background={SettingsManager.userBackgroundColor}
color={SettingsManager.userColor}
- toggle={<FontAwesomeIcon onPointerDown={e => this.openColumnMenu(-1, true)} icon="plus" />}
+ toggle={<FontAwesomeIcon onPointerDown={() => this.openColumnMenu(-1, true)} icon="plus" />}
trigger={PopupTrigger.CLICK}
type={Type.TERT}
isOpen={this._columnMenuIndex !== -1 ? false : undefined}
@@ -1033,6 +1069,7 @@ export class CollectionSchemaView extends CollectionSubView() {
</div>
{this.columnKeys.map((key, index) => (
<SchemaColumnHeader
+ // eslint-disable-next-line react/no-array-index-key
key={index}
columnIndex={index}
columnKeys={this.columnKeys}
@@ -1052,28 +1089,42 @@ export class CollectionSchemaView extends CollectionSubView() {
</div>
{this._columnMenuIndex !== undefined && this._columnMenuIndex !== -1 && this.renderColumnMenu}
{this._filterColumnIndex !== undefined && this.renderFilterMenu}
- <CollectionSchemaViewDocs schema={this} childDocs={this.sortedDocsFunc} rowHeight={this.rowHeightFunc} setRef={(ref: HTMLDivElement | null) => (this._tableContentRef = ref)} />
+ {
+ // eslint-disable-next-line no-use-before-define
+ <CollectionSchemaViewDocs
+ schema={this}
+ childDocs={this.sortedDocsFunc}
+ rowHeight={this.rowHeightFunc}
+ setRef={(ref: HTMLDivElement | null) => {
+ this._tableContentRef = ref;
+ }}
+ />
+ }
{this.layoutDoc.chromeHidden ? null : (
<div className="schema-add">
<EditableView
GetValue={returnEmptyString}
SetValue={undoable(value => (value ? this.addRow(Docs.Create.TextDocument(value, { title: value, _layout_autoHeight: true })) : false), 'add text doc')}
placeholder={"Type text to create note or ':' to create specific type"}
- contents={'+ New Node'}
+ contents="+ New Node"
menuCallback={this.menuCallback}
height={CollectionSchemaView._newNodeInputHeight}
/>
</div>
)}
</div>
- {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown}></div>}
+ {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown} />}
{this.previewWidth > 0 && (
- <div style={{ width: `${this.previewWidth}px` }} ref={ref => (this._previewRef = ref)}>
+ <div
+ style={{ width: `${this.previewWidth}px` }}
+ ref={ref => {
+ this._previewRef = ref;
+ }}>
{Array.from(this._selectedDocs).lastElement() && (
<DocumentView
Document={Array.from(this._selectedDocs).lastElement()}
fitContentsToBox={returnTrue}
- dontCenter={'y'}
+ dontCenter="y"
onClickScriptDisable="always"
focus={emptyFunction}
defaultDoubleClick={returnIgnore}
@@ -1118,7 +1169,10 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
<div className="schema-table-content" ref={this.props.setRef} style={{ height: `calc(100% - ${CollectionSchemaView._newNodeInputHeight + this.props.rowHeight()}px)` }}>
{this.props.childDocs().docs.map((doc: Doc, index: number) => (
<div key={doc[Id]} className="schema-row-wrapper" style={{ height: this.props.rowHeight() }}>
- <CollectionSchemaViewDoc doc={doc} schema={this.props.schema} index={index} rowHeight={this.props.rowHeight} />
+ {
+ // eslint-disable-next-line no-use-before-define
+ <CollectionSchemaViewDoc doc={doc} schema={this.props.schema} index={index} rowHeight={this.props.rowHeight} />
+ }
</div>
))}
</div>
@@ -1151,6 +1205,7 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
return (
<DocumentView
key={this._props.doc[Id]}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props.schema._props}
containerViewPath={this._props.schema.childContainerViewPath}
LayoutTemplate={this._props.schema._props.childLayoutTemplate}
@@ -1170,14 +1225,14 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
searchFilterDocs={this._props.schema.searchFilterDocs}
rootSelected={this._props.schema.rootSelected}
ScreenToLocalTransform={this.screenToLocalXf}
- dragWhenActive={true}
+ dragWhenActive
isDocumentActive={this._props.schema._props.childDocumentsActive?.() ? this._props.schema._props.isDocumentActive : this._props.schema.isContentActive}
isContentActive={this.isRowContentActive}
whenChildContentsActiveChanged={this._props.schema._props.whenChildContentsActiveChanged}
- hideDecorations={true}
- hideTitle={true}
- hideDocumentButtonBar={true}
- hideLinkAnchors={true}
+ hideDecorations
+ hideTitle
+ hideDocumentButtonBar
+ hideLinkAnchors
layout_fitWidth={returnTrue}
/>
);
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index 5f8b412be..6b5a34ec0 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -1,8 +1,10 @@
+/* eslint-disable react/no-unused-prop-types */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable } from 'mobx';
+import { action } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, setupMoveUpEvents } from '../../../../Utils';
+import { setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
import { Colors } from '../../global/globalEnums';
import './CollectionSchemaView.scss';
@@ -24,8 +26,6 @@ export interface SchemaColumnHeaderProps {
@observer
export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> {
- @observable _ref: HTMLDivElement | null = null;
-
get fieldKey() {
return this.props.columnKeys[this.props.columnIndex];
}
@@ -34,9 +34,9 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps>
sortClicked = (e: React.PointerEvent) => {
e.stopPropagation();
e.preventDefault();
- if (this.props.sortField == this.fieldKey && this.props.sortDesc) {
+ if (this.props.sortField === this.fieldKey && this.props.sortDesc) {
this.props.setSort(undefined);
- } else if (this.props.sortField == this.fieldKey) {
+ } else if (this.props.sortField === this.fieldKey) {
this.props.setSort(this.fieldKey, true);
} else {
this.props.setSort(this.fieldKey, false);
@@ -45,7 +45,7 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps>
@action
onPointerDown = (e: React.PointerEvent) => {
- this.props.isContentActive(true) && setupMoveUpEvents(this, e, e => this.props.dragColumn(e, this.props.columnIndex), emptyFunction, emptyFunction, false);
+ this.props.isContentActive(true) && setupMoveUpEvents(this, e, moveEv => this.props.dragColumn(moveEv, this.props.columnIndex), emptyFunction, emptyFunction);
};
render() {
@@ -58,19 +58,18 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps>
onPointerDown={this.onPointerDown}
ref={col => {
if (col) {
- this._ref = col;
this.props.setColRef(this.props.columnIndex, col);
}
}}>
- <div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex)}></div>
+ <div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex)} />
<div className="schema-column-title">{this.fieldKey}</div>
<div className="schema-header-menu">
<div className="schema-header-button" onPointerDown={e => this.props.openContextMenu(e.clientX, e.clientY, this.props.columnIndex)}>
<FontAwesomeIcon icon="ellipsis-h" />
</div>
- <div className="schema-sort-button" onPointerDown={this.sortClicked} style={this.props.sortField == this.fieldKey ? { backgroundColor: Colors.MEDIUM_BLUE } : {}}>
- <FontAwesomeIcon icon="caret-right" style={this.props.sortField == this.fieldKey ? { transform: `rotate(${this.props.sortDesc ? '270deg' : '90deg'})` } : {}} />
+ <div className="schema-sort-button" onPointerDown={this.sortClicked} style={this.props.sortField === this.fieldKey ? { backgroundColor: Colors.MEDIUM_BLUE } : {}}>
+ <FontAwesomeIcon icon="caret-right" style={this.props.sortField === this.fieldKey ? { transform: `rotate(${this.props.sortDesc ? '270deg' : '90deg'})` } : {}} />
</div>
</div>
</div>
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index f5618db96..059aa912e 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -5,15 +5,16 @@ import { computedFn } from 'mobx-utils';
import * as React from 'react';
import { CgClose, CgLock, CgLockUnlock } from 'react-icons/cg';
import { FaExternalLinkAlt } from 'react-icons/fa';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
+import { returnFalse, setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
import { Doc } from '../../../../fields/Doc';
import { BoolCast } from '../../../../fields/Types';
import { Transform } from '../../../util/Transform';
import { undoable } from '../../../util/UndoManager';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { Colors } from '../../global/globalEnums';
-import { OpenWhere } from '../../nodes/DocumentView';
import { FieldView, FieldViewProps } from '../../nodes/FieldView';
+import { OpenWhere } from '../../nodes/OpenWhere';
import { CollectionSchemaView } from './CollectionSchemaView';
import './CollectionSchemaView.scss';
import { SchemaTableCell } from './SchemaTableCell';
@@ -78,7 +79,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
}}>
<IconButton
tooltip="close"
- icon={<CgClose size={'16px'} />}
+ icon={<CgClose size="16px" />}
size={Size.XSMALL}
onPointerDown={e =>
setupMoveUpEvents(
@@ -86,8 +87,8 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
e,
returnFalse,
emptyFunction,
- undoable(e => {
- e.stopPropagation();
+ undoable(clickEv => {
+ clickEv.stopPropagation();
this._props.removeDocument?.(this.Document);
}, 'Delete Row')
)
@@ -119,8 +120,8 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
e,
returnFalse,
emptyFunction,
- undoable(e => {
- e.stopPropagation();
+ undoable(clickEv => {
+ clickEv.stopPropagation();
this._props.addDocTab(this.Document, OpenWhere.addRight);
}, 'Open schema Doc preview')
)
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 0085f9938..48c86ac27 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable react/jsx-props-no-spreading */
+/* eslint-disable no-use-before-define */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Popup, Size, Type } from 'browndash-components';
import { action, computed, makeObservable, observable } from 'mobx';
@@ -7,16 +10,16 @@ import * as React from 'react';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
import Select from 'react-select';
-import { StopEvent, Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils';
+import { ClientUtils, StopEvent, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
import { DateField } from '../../../../fields/DateField';
import { Doc, DocListCast, Field } from '../../../../fields/Doc';
import { RichTextField } from '../../../../fields/RichTextField';
-import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast } from '../../../../fields/Types';
+import { ColumnType } from '../../../../fields/SchemaHeaderField';
+import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast, toList } from '../../../../fields/Types';
import { ImageField } from '../../../../fields/URLField';
import { FInfo, FInfoFieldType } from '../../../documents/Documents';
-import { DocFocusOrOpen } from '../../../util/DocumentManager';
-import { dropActionType } from '../../../util/DragManager';
-import { SettingsManager } from '../../../util/SettingsManager';
+import { dropActionType } from '../../../util/DropActionTypes';
import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { undoBatch, undoable } from '../../../util/UndoManager';
@@ -24,11 +27,10 @@ import { EditableView } from '../../EditableView';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { DefaultStyleProvider } from '../../StyleProvider';
import { Colors } from '../../global/globalEnums';
-import { OpenWhere, returnEmptyDocViewList } from '../../nodes/DocumentView';
+import { DocFocusOrOpen, returnEmptyDocViewList } from '../../nodes/DocumentView';
import { FieldViewProps } from '../../nodes/FieldView';
-import { KeyValueBox } from '../../nodes/KeyValueBox';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
-import { ColumnType, FInfotoColType } from './CollectionSchemaView';
+import { FInfotoColType } from './CollectionSchemaView';
import './CollectionSchemaView.scss';
export interface SchemaTableCellProps {
@@ -64,8 +66,8 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
makeObservable(this);
}
- static addFieldDoc = (doc: Doc, where: OpenWhere) => {
- DocFocusOrOpen(doc);
+ static addFieldDoc = (docs: Doc | Doc[] /* , where: OpenWhere */) => {
+ DocFocusOrOpen(toList(docs)[0]);
return true;
};
public static renderProps(props: SchemaTableCellProps) {
@@ -144,7 +146,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
this._props.finishEdit?.();
return true;
}
- const ret = KeyValueBox.SetField(fieldProps.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(fieldProps.Document) ? true : undefined);
+ const ret = Doc.SetField(fieldProps.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(fieldProps.Document) ? true : undefined);
this._props.finishEdit?.();
return ret;
}, 'edit schema cell')}
@@ -177,7 +179,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
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.Enumeration: return <SchemaEnumerationCell {...this._props} options={this._props.getFinfo(this._props.fieldKey)?.values?.map(val => Field.toString(val))} />;
case ColumnType.Date: return <SchemaDateCell {...this._props} />;
default: return this.defaultCellContent;
}
@@ -217,8 +219,8 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
choosePath(url: URL) {
if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href
- if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver
- if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; //Why is this here — good question
+ if (url.href.indexOf(window.location.origin) === -1) return ClientUtils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver
+ if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; // Why is this here — good question
const ext = extname(url.href);
return url.href.replace(ext, '_s' + ext);
@@ -233,7 +235,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
.map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
// If there is a path, follow it; otherwise, follow a link to a default image icon
- const url = paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')];
+ const url = paths.length ? paths : [ClientUtils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')];
return url[0];
}
@@ -257,7 +259,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
};
@action
- removeHoverPreview = (e: React.PointerEvent) => {
+ removeHoverPreview = () => {
if (!this._previewRef) return;
document.body.removeChild(this._previewRef);
};
@@ -269,7 +271,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
const height = this._props.rowHeight() ? this._props.rowHeight() - (this._props.padding || 6) * 2 : undefined;
const width = height ? height * aspect : undefined; // increase the width of the image if necessary to maintain proportionality
- return <img src={this.url} width={width ? width : undefined} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />;
+ return <img src={this.url} width={width || undefined} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />;
}
}
@@ -293,26 +295,26 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp
// } else {
// ^ DateCast is always undefined for some reason, but that is what the field should be set to
date && (this._props.Document[this._props.fieldKey] = new DateField(date));
- //}
+ // }
}, 'date change');
render() {
- const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
+ const { pointerEvents } = SchemaTableCell.renderProps(this._props);
return (
<>
<div style={{ pointerEvents: 'none' }}>
- <DatePicker dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={e => {}} />
+ <DatePicker dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={emptyFunction} />
</div>
{pointerEvents === 'none' ? null : (
<Popup
icon={<FontAwesomeIcon size="sm" icon="caret-down" />}
size={Size.XSMALL}
type={Type.TERT}
- color={SettingsManager.userColor}
- background={SettingsManager.userBackgroundColor}
+ color={SnappingManager.userColor}
+ background={SnappingManager.userBackgroundColor}
popup={
<div style={{ width: 'fit-content', height: '200px' }}>
- <DatePicker open={true} dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={this.handleChange} />
+ <DatePicker open dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={this.handleChange} />
</div>
}
/>
@@ -341,7 +343,7 @@ export class SchemaRTFCell extends ObservableReactComponent<SchemaTableCellProps
fieldProps.isContentActive = this.selectedFunc;
return (
<div className="schemaRTFCell" style={{ fontStyle: this.selected ? undefined : 'italic', color, textDecoration, cursor, pointerEvents }}>
- {this.selected ? <FormattedTextBox {...fieldProps} autoFocus={true} onBlur={() => this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
+ {this.selected ? <FormattedTextBox {...fieldProps} autoFocus onBlur={() => this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
</div>
);
}
@@ -367,8 +369,8 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp
checked={BoolCast(this._props.Document[this._props.fieldKey])}
onChange={undoBatch((value: React.ChangeEvent<HTMLInputElement> | undefined) => {
if ((value?.nativeEvent as any).shiftKey) {
- this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
- } else KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
+ this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? ''));
+ } else Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? ''));
})}
/>
<EditableView
@@ -382,7 +384,7 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp
this._props.finishEdit?.();
return true;
}
- const set = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined);
+ const set = Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined);
this._props.finishEdit?.();
return set;
})}
@@ -403,7 +405,7 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC
return this._props.isRowActive() && selected && selected?.filter(doc => doc === this._props.Document).length !== 0 && this._props.selectedCol() === this._props.col;
}
render() {
- const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
+ const { color, textDecoration, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
const options = this._props.options?.map(facet => ({ value: facet, label: facet }));
return (
<div className="schemaSelectionCell" style={{ color, textDecoration, cursor, pointerEvents }}>
@@ -448,7 +450,7 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC
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 => Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)}
/>
</div>
</div>
diff --git a/src/client/views/global/globalEnums.tsx b/src/client/views/global/globalEnums.tsx
index 610c2b102..46caa3702 100644
--- a/src/client/views/global/globalEnums.tsx
+++ b/src/client/views/global/globalEnums.tsx
@@ -1,44 +1,49 @@
export enum Colors {
- BLACK = "#000000",
- DARK_GRAY = "#323232",
- MEDIUM_GRAY = "#9F9F9F",
- LIGHT_GRAY = "#DFDFDF",
- WHITE = "#FFFFFF",
- MEDIUM_BLUE = "#4476F7",
- MEDIUM_BLUE_ALT = "#4476f73d", // REDUCED OPACITY
- LIGHT_BLUE = "#BDDDF5",
- PINK = "#E0217D",
- ERROR_RED = "#ff0033",
- YELLOW = "#F5D747",
- DROP_SHADOW = "#32323215",
+ BLACK = '#000000',
+ DARK_GRAY = '#323232',
+ MEDIUM_GRAY = '#9F9F9F',
+ LIGHT_GRAY = '#DFDFDF',
+ WHITE = '#FFFFFF',
+ MEDIUM_BLUE = '#4476F7',
+ MEDIUM_BLUE_ALT = '#4476f73d', // REDUCED OPACITY
+ LIGHT_BLUE = '#BDDDF5',
+ PINK = '#E0217D',
+ ERROR_RED = '#ff0033',
+ YELLOW = '#F5D747',
+ DROP_SHADOW = '#32323215',
}
export enum FontSizes {
- //Bolded
- LARGE_HEADER = "16px",
+ // Bolded
+ LARGE_HEADER = '16px',
- //Bolded or unbolded
- BODY_TEXT = "12px",
+ // Bolded or unbolded
+ BODY_TEXT = '12px',
- //Bolded
- SMALL_TEXT = "9px",
+ // Bolded
+ SMALL_TEXT = '9px',
}
export enum Padding {
- MINIMUM_PADDING = "4px",
- SMALL_PADDING = "8px",
- MEDIUM_PADDING = "16px",
- LARGE_PADDING = "32px",
+ MINIMUM_PADDING = '4px',
+ SMALL_PADDING = '8px',
+ MEDIUM_PADDING = '16px',
+ LARGE_PADDING = '32px',
}
export enum IconSizes {
- ICON_SIZE = "28px",
+ ICON_SIZE = '28px',
}
export enum Borders {
- STANDARD = "solid 1px #9F9F9F"
+ STANDARD = 'solid 1px #9F9F9F',
}
export enum Shadows {
- STANDARD_SHADOW = "0px 3px 4px rgba(0, 0, 0, 0.3)"
-} \ No newline at end of file
+ STANDARD_SHADOW = '0px 3px 4px rgba(0, 0, 0, 0.3)',
+}
+
+export enum VideoThumbnails {
+ DENSE = 20,
+ SPARSE = 5,
+}
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 2a5732708..c595681b7 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -1,41 +1,46 @@
import { Colors } from 'browndash-components';
import { action, runInAction } from 'mobx';
import { aggregateBounds } from '../../../Utils';
-import { Doc, Opt } from '../../../fields/Doc';
+import { ActiveFillColor, ActiveInkColor, ActiveInkHideTextLabels, ActiveInkWidth, ActiveIsInkMask, Doc, Opt, SetActiveFillColor, SetActiveInkColor, SetActiveInkHideTextLabels, SetActiveInkWidth, SetActiveIsInkMask } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
import { InkTool } from '../../../fields/InkField';
import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
-import { GestureUtils } from '../../../pen-gestures/GestureUtils';
+import { Gestures } from '../../../pen-gestures/GestureTypes';
import { DocumentType } from '../../documents/DocumentTypes';
import { LinkManager } from '../../util/LinkManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { SelectionManager } from '../../util/SelectionManager';
import { UndoManager, undoable } from '../../util/UndoManager';
import { GestureOverlay } from '../GestureOverlay';
-import { ActiveFillColor, ActiveInkColor, ActiveInkHideTextLabels, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkHideTextLabels, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke';
+import { InkingStroke } from '../InkingStroke';
import { CollectionFreeFormView } from '../collections/collectionFreeForm';
-// import { InkTranscription } from '../InkTranscription';
-import { DocData } from '../../../fields/DocSymbols';
import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
import { DocumentView } from '../nodes/DocumentView';
+import { ImageBox } from '../nodes/ImageBox';
import { VideoBox } from '../nodes/VideoBox';
import { WebBox } from '../nodes/WebBox';
import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
-import { ImageBox } from '../nodes/ImageBox';
+// import { InkTranscription } from '../InkTranscription';
+
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function IsNoneSelected() {
- return SelectionManager.Views.length <= 0;
+ return DocumentView.Selected().length <= 0;
}, 'are no document selected');
// toggle: Set overlay status of selected document
-ScriptingGlobals.add(function setView(view: string) {
- const selected = SelectionManager.Docs.lastElement();
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function setView(view: string, getSelected: boolean) {
+ if (getSelected) return DocumentView.SelectedDocs();
+ const selected = DocumentView.SelectedDocs().lastElement();
selected ? (selected._type_collection = view) : console.log('[FontIconBox.tsx] changeView failed');
+ return undefined;
});
// toggle: Set overlay status of selected document
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) {
- const selectedViews = SelectionManager.Views;
+ const selectedViews = DocumentView.Selected();
if (Doc.ActiveTool !== InkTool.None) {
if (checkResult) {
return ActiveFillColor();
@@ -65,26 +70,29 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
}
});
} else {
- const selected = SelectionManager.Docs.length ? SelectionManager.Docs : LinkManager.Instance.currentLink ? [LinkManager.Instance.currentLink] : [];
+ const selected = DocumentView.SelectedDocs().length ? DocumentView.SelectedDocs() : LinkManager.Instance.currentLink ? [LinkManager.Instance.currentLink] : [];
if (checkResult) {
return selected.lastElement()?._backgroundColor ?? 'transparent';
}
SetActiveFillColor(color ?? 'transparent');
- selected.forEach(doc => (doc[DocData].backgroundColor = color));
+ selected.forEach(doc => { doc[DocData].backgroundColor = color; }); // prettier-ignore
}
+ return '';
});
// toggle: Set overlay status of selected document
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setDefaultTemplate(checkResult?: boolean) {
return DocumentView.setDefaultTemplate(checkResult);
});
// toggle: Set overlay status of selected document
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) {
if (checkResult) {
- return SelectionManager.Views.length ? StrCast(SelectionManager.Docs.lastElement().layout_headingColor) : Doc.SharingDoc().headingColor;
+ return DocumentView.Selected().length ? StrCast(DocumentView.SelectedDocs().lastElement().layout_headingColor) : Doc.SharingDoc().headingColor;
}
- if (SelectionManager.Views.length) {
- SelectionManager.Docs.forEach(doc => {
+ if (DocumentView.Selected().length) {
+ DocumentView.SelectedDocs().forEach(doc => {
doc[DocData].layout_headingColor = color === 'transparent' ? undefined : color;
doc.layout_showTitle = color === 'transparent' ? undefined : StrCast(doc.layout_showTitle, 'title');
});
@@ -93,29 +101,33 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole
Doc.GetProto(Doc.SharingDoc()).headingColor = color === 'transparent' ? undefined : color;
Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'title');
}
+ return undefined;
});
// toggle: Set overlay status of selected document
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
- const selected = SelectionManager.Views.length ? SelectionManager.Views[0] : undefined;
+ const selected = DocumentView.Selected().length ? DocumentView.Selected()[0] : undefined;
if (checkResult) {
if (NumCast(selected?.Document.z) >= 1) return true;
return false;
}
- selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
+ selected ? CollectionFreeFormDocumentView.from(selected)?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
+ return undefined;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) {
- const selected = SelectionManager.Docs.lastElement();
+ const selected = DocumentView.SelectedDocs().lastElement();
// prettier-ignore
const map: Map<'center' |'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
['grid', {
checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false),
- setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
+ setDoc: (doc:Doc) => { doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid; },
}],
['snaplines', {
checkResult: (doc:Doc) => BoolCast(doc?._freeform_snapLines, false),
- setDoc: (doc:Doc, dv:DocumentView) => doc._freeform_snapLines = !doc._freeform_snapLines,
+ setDoc: (doc:Doc) => { doc._freeform_snapLines = !doc._freeform_snapLines; },
}],
['viewAll', {
checkResult: (doc:Doc) => BoolCast(doc?._freeform_fitContentsToBox, false),
@@ -127,12 +139,12 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
}],
['center', {
checkResult: (doc:Doc) => BoolCast(doc?._stacking_alignCenter, false),
- setDoc: (doc:Doc,dv:DocumentView) => doc._stacking_alignCenter = !doc._stacking_alignCenter,
+ setDoc: (doc:Doc) => { doc._stacking_alignCenter = !doc._stacking_alignCenter; },
}],
['clusters', {
waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
checkResult: (doc:Doc) => BoolCast(doc?._freeform_useClusters, false),
- setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_useClusters = !doc._freeform_useClusters,
+ setDoc: (doc:Doc) => { doc._freeform_useClusters = !doc._freeform_useClusters; },
}],
]);
@@ -140,37 +152,39 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
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.layoutDoc, dv));
+ DocumentView.Selected().map(dv => map.get(attr)?.setDoc(dv.layoutDoc, dv));
setTimeout(() => batch.end(), 100);
+ return undefined;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize' | 'alignment', value: any, checkResult?: boolean) {
const editorView = RichTextMenu.Instance?.TextView?.EditorView;
- const selected = SelectionManager.Docs.lastElement();
// prettier-ignore
const map: Map<'font'|'fontColor'|'highlight'|'fontSize'|'alignment', { checkResult: () => any; setDoc: () => void;}> = new Map([
['font', {
checkResult: () => RichTextMenu.Instance?.fontFamily,
- setDoc: () => value && RichTextMenu.Instance?.setFontFamily(value),
+ setDoc: () => value && RichTextMenu.Instance?.setFontField(value, 'fontFamily'),
}],
['highlight', {
checkResult: () => RichTextMenu.Instance?.fontHighlight,
- setDoc: () => value && RichTextMenu.Instance?.setHighlight(value),
+ setDoc: () => value && RichTextMenu.Instance?.setFontField(value, 'fontHighlight'),
}],
['fontColor', {
checkResult: () => RichTextMenu.Instance?.fontColor,
- setDoc: () => value && RichTextMenu.Instance?.setColor(value),
+ setDoc: () => value && RichTextMenu.Instance?.setFontField(value, 'fontColor'),
}],
['alignment', {
checkResult: () => RichTextMenu.Instance?.textAlign,
- setDoc: () => value && editorView?.state ? RichTextMenu.Instance?.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value),
+ setDoc: () => { value && editorView?.state ? RichTextMenu.Instance?.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value); },
}],
['fontSize', {
checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''),
setDoc: () => {
- if (typeof value === 'number') value = value.toString();
- if (value && Number(value).toString() === value) value += 'px';
- RichTextMenu.Instance?.setFontSize(value);
+ let fsize = value;
+ if (typeof fsize === 'number') fsize = fsize.toString();
+ if (fsize && Number(fsize).toString() === fsize) fsize += 'px';
+ RichTextMenu.Instance?.setFontField(fsize, 'fontSize');
},
}],
]);
@@ -179,63 +193,55 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
return map.get(attr)?.checkResult();
}
map.get(attr)?.setDoc?.();
+ return undefined;
});
type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal';
type attrfuncs = [attrname, { checkResult: () => boolean; toggle?: () => any }];
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) {
const textView = RichTextMenu.Instance?.TextView;
const editorView = textView?.EditorView;
// prettier-ignore
const alignments:attrfuncs[] = (['left','right','center','vcent'] as ("left"|"center"|"right"|"vcent")[]).map((where) =>
- [ where, { checkResult: () =>(editorView ? (where === 'vcent' ? RichTextMenu.Instance?.textVcenter ?? false:
- (RichTextMenu.Instance?.textAlign === where)):
- where === 'vcent' ? BoolCast(Doc.UserDoc()._layout_centered):
- (Doc.UserDoc().textAlign ===where) ? true:false),
- toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle(editorView, editorView.dispatch):
- RichTextMenu.Instance?.align(editorView, editorView.dispatch, where)):
+ [ where, { checkResult: () => editorView ? (where === 'vcent' ? RichTextMenu.Instance?.textVcenter ?? false:
+ (RichTextMenu.Instance?.textAlign === where)):
+ where === 'vcent' ? BoolCast(Doc.UserDoc()._layout_centered):
+ (Doc.UserDoc().textAlign === where),
+ toggle: () => { editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle():
+ RichTextMenu.Instance?.align(editorView, editorView.dispatch, where)):
where === 'vcent' ? Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered:
- (Doc.UserDoc().textAlign = where))}]); // prettier-ignore
+ (Doc.UserDoc().textAlign = where); }
+ }]); // prettier-ignore
// prettier-ignore
const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list =>
[ list, { checkResult: () => (editorView ? RichTextMenu.Instance?.listStyle === list:false),
toggle: () => editorView?.state && RichTextMenu.Instance?.changeListType(list) }]);
// prettier-ignore
const attrs:attrfuncs[] = [
- ['dictation', { checkResult: () => textView?._recordingDictation ? true:false,
- toggle: () => textView && runInAction(() => (textView._recordingDictation = !textView._recordingDictation)) }],
+ ['dictation', { checkResult: () => !!textView?._recordingDictation,
+ toggle: () => textView && runInAction(() => { textView._recordingDictation = !textView._recordingDictation;} ) }],
['elide', { checkResult: () => false,
toggle: () => editorView ? RichTextMenu.Instance?.elideSelection(): 0}],
['noAutoLink',{ checkResult: () => ((editorView && RichTextMenu.Instance?.noAutoLink) ?? false),
toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}],
- ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance?.bold??false : (Doc.UserDoc().fontWeight === 'bold') ? true:false),
- toggle: editorView ? RichTextMenu.Instance?.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}],
- ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance?.italics ?? false : (Doc.UserDoc().fontStyle === 'italics') ? true:false),
- toggle: editorView ? RichTextMenu.Instance?.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}],
- ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance?.underline ?? false: (Doc.UserDoc().textDecoration === 'underline') ? true:false),
- toggle: editorView ? RichTextMenu.Instance?.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]]
+ ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance?.bold??false : (Doc.UserDoc().fontWeight === 'bold')),
+ toggle: editorView ? RichTextMenu.Instance?.toggleBold : () => { Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold'; }}],
+ ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance?.italics ?? false : (Doc.UserDoc().fontStyle === 'italics')),
+ toggle: editorView ? RichTextMenu.Instance?.toggleItalics : () => { Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics'; }}],
+ ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance?.underline ?? false: (Doc.UserDoc().textDecoration === 'underline')),
+ toggle: editorView ? RichTextMenu.Instance?.toggleUnderline : () => { Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline'; } }]]
const map = new Map(attrs.concat(alignments).concat(listings));
if (checkResult) {
return map.get(charStyle)?.checkResult();
}
undoable(() => map.get(charStyle)?.toggle?.(), 'toggle ' + charStyle)();
+ return undefined;
});
-export function checkInksToGroup() {
- if (Doc.ActiveTool === InkTool.Write) {
- CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
- // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
- // find all inkDocs in ffView.unprocessedDocs that are within 200 pixels of each other
- const inksToGroup = ffView.unprocessedDocs.filter(inkDoc => {
- // console.log(inkDoc.x, inkDoc.y);
- });
- });
- }
-}
-
-export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
+export function createInkGroup(/* inksToGroup?: Doc[], isSubGroup?: boolean */) {
// TODO nda - if document being added to is a inkGrouping then we can just add to that group
if (Doc.ActiveTool === InkTool.Write) {
CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
@@ -301,26 +307,24 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
}
-function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, checkResult?: boolean) {
+function setActiveTool(tool: InkTool | Gestures, keepPrim: boolean, checkResult?: boolean) {
// InkTranscription.Instance?.createInkGroup();
if (checkResult) {
return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool
- ? GestureOverlay.Instance?.KeepPrimitiveMode || ![GestureUtils.Gestures.Circle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Rectangle].includes(tool as GestureUtils.Gestures)
- ? true
- : true
+ ? GestureOverlay.Instance?.KeepPrimitiveMode || ![Gestures.Circle, Gestures.Line, Gestures.Rectangle].includes(tool as Gestures)
: false;
}
runInAction(() => {
if (GestureOverlay.Instance) {
GestureOverlay.Instance.KeepPrimitiveMode = keepPrim;
}
- if (Object.values(GestureUtils.Gestures).includes(tool as any)) {
+ if (Object.values(Gestures).includes(tool as any)) {
if (GestureOverlay.Instance.InkShape === tool && !keepPrim) {
Doc.ActiveTool = InkTool.None;
GestureOverlay.Instance.InkShape = undefined;
} else {
Doc.ActiveTool = InkTool.Pen;
- GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures;
+ GestureOverlay.Instance.InkShape = tool as Gestures;
}
} else if (tool) {
// pen or eraser
@@ -334,38 +338,40 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean,
Doc.ActiveTool = InkTool.None;
}
});
+ return undefined;
}
ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode');
// toggle: Set overlay status of selected document
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) {
- const selected = SelectionManager.Docs.lastElement() ?? Doc.UserDoc();
+ const selected = DocumentView.SelectedDocs().lastElement() ?? Doc.UserDoc();
// prettier-ignore
const map: Map<'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
['inkMask', {
checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())),
- setInk: (doc: Doc) => (doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask),
+ setInk: (doc: Doc) => { doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask; },
setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()),
}],
['labels', {
checkResult: () => ((selected?._stroke_showLabel ? BoolCast(selected[DocData].stroke_showLabel) : ActiveInkHideTextLabels())),
- setInk: (doc: Doc) => (doc[DocData].stroke_showLabel = !doc.stroke_showLabel),
+ setInk: (doc: Doc) => { doc[DocData].stroke_showLabel = !doc.stroke_showLabel; },
setMode: () => selected?.type !== DocumentType.INK && SetActiveInkHideTextLabels(!ActiveInkHideTextLabels()),
}],
['fillColor', {
checkResult: () => (selected?._layout_isSvg ? StrCast(selected[DocData].fillColor) : ActiveFillColor() ?? "transparent"),
- setInk: (doc: Doc) => (doc[DocData].fillColor = StrCast(value)),
+ setInk: (doc: Doc) => { doc[DocData].fillColor = StrCast(value); },
setMode: () => SetActiveFillColor(StrCast(value)),
}],
[ 'strokeWidth', {
checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width) : ActiveInkWidth()),
- setInk: (doc: Doc) => (doc[DocData].stroke_width = NumCast(value)),
+ setInk: (doc: Doc) => { doc[DocData].stroke_width = NumCast(value); },
setMode: () => { SetActiveInkWidth(value.toString()); selected?.type === DocumentType.INK && setActiveTool( GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
}],
['strokeColor', {
checkResult: () => (selected?._layout_isSvg? StrCast(selected[DocData].color) : ActiveInkColor()),
- setInk: (doc: Doc) => (doc[DocData].color = String(value)),
+ setInk: (doc: Doc) => { doc[DocData].color = String(value); },
setMode: () => { SetActiveInkColor(StrCast(value)); selected?.type === DocumentType.INK && setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
}],
]);
@@ -374,84 +380,112 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fil
return map.get(option)?.checkResult();
}
map.get(option)?.setMode();
- SelectionManager.Docs.filter(doc => doc._layout_isSvg).map(doc => map.get(option)?.setInk(doc));
+ DocumentView.SelectedDocs()
+ .filter(doc => doc._layout_isSvg)
+ .map(doc => map.get(option)?.setInk(doc));
+ return undefined;
+});
+
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) {
+ if (readOnly) {
+ return DocumentView.Selected().some(dv => dv.Document.keepZWhenDragged);
+ }
+ DocumentView.Selected().forEach(dv => {
+ dv.Document.keepZWhenDragged = !dv.Document.keepZWhenDragged;
+ });
+ return undefined;
});
/** WEB
* webSetURL
- **/
+ * */
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) {
- const selected = SelectionManager.Views.lastElement();
+ const selected = DocumentView.Selected().lastElement();
if (selected?.Document.type === DocumentType.WEB) {
if (checkResult) {
return StrCast(selected.Document.data, Cast(selected.Document.data, WebField, null)?.url?.href);
}
selected.ComponentView?.setData?.(url);
- //selected.Document.data = new WebField(url);
}
+ return '';
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function webForward(checkResult?: boolean) {
- const selected = SelectionManager.Views.lastElement()?.ComponentView as WebBox;
+ const selected = DocumentView.Selected().lastElement()?.ComponentView as WebBox;
if (checkResult) {
return selected?.forward(checkResult) ? undefined : 'lightGray';
}
selected?.forward();
+ return undefined;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function webBack() {
- const selected = SelectionManager.Views.lastElement()?.ComponentView as WebBox;
+ const selected = DocumentView.Selected().lastElement()?.ComponentView as WebBox;
selected?.back();
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function videoSnapshot() {
- const selected = SelectionManager.Views.lastElement()?.ComponentView as VideoBox;
+ const selected = DocumentView.Selected().lastElement()?.ComponentView as VideoBox;
selected?.Snapshot();
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function imageSetPixelSize() {
- const selected = SelectionManager.Views.lastElement()?.ComponentView as ImageBox;
+ const selected = DocumentView.Selected().lastElement()?.ComponentView as ImageBox;
selected?.setNativeSize();
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function imageRotate90() {
- const selected = SelectionManager.Views.lastElement()?.ComponentView as ImageBox;
+ const selected = DocumentView.Selected().lastElement()?.ComponentView as ImageBox;
selected?.rotate();
});
/** Schema
* toggleSchemaPreview
- **/
+ * */
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) {
- const selected = SelectionManager.Docs.lastElement();
+ const selected = DocumentView.SelectedDocs().lastElement();
if (checkResult && selected) {
const result: boolean = NumCast(selected.schema_previewWidth) > 0;
if (result) return Colors.MEDIUM_BLUE;
- else return 'transparent';
- } else if (selected) {
+ return 'transparent';
+ }
+ if (selected) {
if (NumCast(selected.schema_previewWidth) > 0) {
selected.schema_previewWidth = 0;
} else {
selected.schema_previewWidth = 200;
}
}
+ return '';
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) {
- const selected = SelectionManager.Docs.lastElement();
+ const selected = DocumentView.SelectedDocs().lastElement();
if (checkResult && selected) {
return NumCast(selected._schema_singleLine) > 0 ? Colors.MEDIUM_BLUE : 'transparent';
}
if (selected) {
selected._schema_singleLine = !selected._schema_singleLine;
}
+ return undefined;
});
/** STACK
* groupBy
*/
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) {
- SelectionManager.Docs.map(doc => (doc._text_fontFamily = key));
+ DocumentView.SelectedDocs().forEach(doc => { doc._text_fontFamily = key; }); // prettier-ignore
const editorView = RichTextMenu.Instance?.TextView?.EditorView;
if (checkResult) {
return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc())?.fontFamily);
}
- if (editorView) RichTextMenu.Instance?.setFontFamily(key);
+ if (editorView) RichTextMenu.Instance?.setFontField(key, 'fontFamily');
else Doc.UserDoc().fontFamily = key;
+ return undefined;
});
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 60def5d45..cd735318e 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -1,14 +1,16 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/require-default-props */
+import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
+import * as React from 'react';
import { Doc, StrListCast } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { Cast, DocCast } from '../../../fields/Types';
-import { LinkManager } from '../../util/LinkManager';
+import { DocumentType } from '../../documents/DocumentTypes';
import { DocumentView } from '../nodes/DocumentView';
import './LinkMenu.scss';
import { LinkMenuItem } from './LinkMenuItem';
-import * as React from 'react';
-import { DocumentType } from '../../documents/DocumentTypes';
interface LinkMenuGroupProps {
sourceDoc: Doc;
@@ -22,25 +24,24 @@ interface LinkMenuGroupProps {
@observer
export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
private _menuRef = React.createRef<HTMLDivElement>();
+ @observable _collapsed = false;
getBackgroundColor = (): string | undefined => {
- const link_relationshipList = StrListCast(Doc.UserDoc().link_relationshipList);
+ const linkRelationshipList = StrListCast(Doc.UserDoc().link_relationshipList);
const linkColorList = StrListCast(Doc.UserDoc().link_ColorList);
let color: string | undefined;
// if this link's relationship property is not default "link", set its color
- if (link_relationshipList) {
- const relationshipIndex = link_relationshipList.indexOf(this.props.groupType);
+ if (linkRelationshipList) {
+ const relationshipIndex = linkRelationshipList.indexOf(this.props.groupType);
const RGBcolor: string = linkColorList[relationshipIndex];
if (RGBcolor) {
- //set opacity to 0.25 by modifiying the rgb string
+ // set opacity to 0.25 by modifiying the rgb string
color = RGBcolor.slice(0, RGBcolor.length - 1) + ', 0.25)';
}
}
return color;
};
- @observable _collapsed = false;
-
render() {
const set = new Set<Doc>(this.props.group);
const groupItems = Array.from(set.keys()).map(linkDoc => {
@@ -57,8 +58,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
? this.props.docView._props.LayoutTemplateString?.includes('link_anchor_1')
? DocCast(linkDoc.link_anchor_2)
: DocCast(linkDoc.link_anchor_1)
- : LinkManager.getOppositeAnchor(linkDoc, sourceDoc) ||
- LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.link_anchor_2, Doc, null)?.annotationOn === sourceDoc ? Cast(linkDoc.link_anchor_2, Doc, null) : Cast(linkDoc.link_anchor_1, Doc, null));
+ : Doc.getOppositeAnchor(linkDoc, sourceDoc) || Doc.getOppositeAnchor(linkDoc, Cast(linkDoc.link_anchor_2, Doc, null)?.annotationOn === sourceDoc ? Cast(linkDoc.link_anchor_2, Doc, null) : Cast(linkDoc.link_anchor_1, Doc, null));
return !destDoc || !sourceDoc ? null : (
<LinkMenuItem
key={linkDoc[Id]}
@@ -76,7 +76,12 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
return (
<div className="linkMenu-group" ref={this._menuRef}>
- <div className="linkMenu-group-name" onClick={action(() => (this._collapsed = !this._collapsed))} style={{ background: this.getBackgroundColor() }}>
+ <div
+ className="linkMenu-group-name"
+ onClick={action(() => {
+ this._collapsed = !this._collapsed;
+ })}
+ style={{ background: this.getBackgroundColor() }}>
<p className={this.props.groupType === '*' || this.props.groupType === '' ? '' : 'expand-one'}> {this.props.groupType}:</p>
</div>
{this._collapsed ? null : <div className="linkMenu-group-wrapper">{groupItems}</div>}
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index a2c9d10b6..9ce04ffac 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,24 +1,25 @@
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc } from '../../../fields/Doc';
import { Cast, DocCast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
import { DocumentType } from '../../documents/DocumentTypes';
-import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../util/DragManager';
-import { LinkFollower } from '../../util/LinkFollower';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { LinkManager } from '../../util/LinkManager';
-import { SelectionManager } from '../../util/SelectionManager';
-import { SettingsManager } from '../../util/SettingsManager';
+import { SnappingManager } from '../../util/SnappingManager';
import { undoBatch } from '../../util/UndoManager';
import { ObservableReactComponent } from '../ObservableReactComponent';
-import { DocumentView, DocumentViewInternal, OpenWhere } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
import { LinkInfo } from '../nodes/LinkDocPreview';
+import { OpenWhere } from '../nodes/OpenWhere';
import './LinkMenuItem.scss';
interface LinkMenuItemProps {
@@ -34,14 +35,14 @@ interface LinkMenuItemProps {
// drag links and drop link targets (embedding them if needed)
export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: DocumentView, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) {
- const draggedDocs = (specificLinks ? specificLinks : LinkManager.Links(sourceDoc)).map(link => LinkManager.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
+ const draggedDocs = (specificLinks || LinkManager.Links(sourceDoc)).map(link => Doc.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
if (draggedDocs.length) {
const moddrag: Doc[] = [];
- for (const draggedDoc of draggedDocs) {
+ draggedDocs.forEach(async draggedDoc => {
const doc = await Cast(draggedDoc.annotationOn, Doc);
if (doc) moddrag.push(doc);
- }
+ });
const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
dragData.canEmbed = true;
@@ -77,7 +78,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
onIconDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, returnFalse, returnFalse, () => {
- const ancestor = DocumentManager.LinkCommonAncestor(this._props.linkDoc);
+ const ancestor = DocumentView.linkCommonAncestor(this._props.linkDoc);
if (!ancestor?.ComponentView?.removeDocument?.(this._props.linkDoc)) {
ancestor?.ComponentView?.addDocument?.(this._props.linkDoc);
}
@@ -88,10 +89,10 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
setupMoveUpEvents(
this,
e,
- e => {
+ moveEv => {
const dragData = new DragManager.DocumentDragData([this._props.linkDoc], dropActionType.embed);
dragData.dropPropertiesToRemove = ['hidden'];
- DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y);
+ DragManager.StartDocumentDrag([this._editRef.current!], dragData, moveEv.x, moveEv.y);
return true;
},
emptyFunction,
@@ -101,12 +102,16 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
Doc.ActivePresentation = trail;
DocumentViewInternal.addDocTabFunc(trail, OpenWhere.replaceRight);
} else {
- SelectionManager.SelectView(this._props.docView, false);
+ DocumentView.SelectView(this._props.docView, false);
LinkManager.Instance.currentLink = this._props.linkDoc === LinkManager.Instance.currentLink ? undefined : this._props.linkDoc;
LinkManager.Instance.currentLinkAnchor = LinkManager.Instance.currentLink ? this.sourceAnchor : undefined;
- if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
- setTimeout(action(() => (SettingsManager.Instance.propertiesWidth = 250)));
+ if ((SnappingManager.PropertiesWidth ?? 0) < 100) {
+ setTimeout(
+ action(() => {
+ SnappingManager.SetPropertiesWidth(250);
+ })
+ );
}
}
})
@@ -117,11 +122,13 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
setupMoveUpEvents(
this,
e,
- e => {
- const eleClone: any = this._drag.current!.cloneNode(true);
- eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`;
- StartLinkTargetsDrag(eleClone, this._props.docView, e.x, e.y, this._props.sourceDoc, [this._props.linkDoc]);
- this._props.clearLinkEditor?.();
+ moveEv => {
+ const eleClone: any = this._drag.current?.cloneNode(true);
+ if (eleClone) {
+ eleClone.style.transform = `translate(${moveEv.x}px, ${moveEv.y}px)`;
+ StartLinkTargetsDrag(eleClone, this._props.docView, moveEv.x, moveEv.y, this._props.sourceDoc, [this._props.linkDoc]);
+ this._props.clearLinkEditor?.();
+ }
return true;
},
emptyFunction,
@@ -138,17 +145,17 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
: undefined;
if (focusDoc) this._props.docView._props.focus(focusDoc, { instant: true });
- LinkFollower.FollowLink(this._props.linkDoc, this._props.sourceDoc, false);
+ DocumentView.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(() => Doc.DeleteLink?.(this._props.linkDoc))));
@observable _hover = false;
docView = () => this._props.docView;
render() {
- const destinationIcon = Doc.toIcon(this._props.destinationDoc) as any as IconProp;
+ const destinationIcon = Doc.toIcon(this._props.destinationDoc);
const title = StrCast(this._props.destinationDoc.title).length > 18 ? StrCast(this._props.destinationDoc.title).substr(0, 14) + '...' : this._props.destinationDoc.title;
@@ -164,25 +171,29 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
return (
<div
className="linkMenu-item"
- onPointerEnter={action(e => (this._hover = true))}
- onPointerLeave={action(e => (this._hover = false))}
+ onPointerEnter={action(() => {
+ this._hover = true;
+ })}
+ onPointerLeave={action(() => {
+ this._hover = false;
+ })}
style={{
fontSize: this._hover ? 'larger' : undefined,
fontWeight: this._hover ? 'bold' : undefined,
- background: LinkManager.Instance.currentLink === this._props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+ background: LinkManager.Instance.currentLink === this._props.linkDoc ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
}}>
<div className="linkMenu-item-content expand-two">
<div
ref={this._drag}
- className="linkMenu-name" //title="drag to view target. click to customize."
+ className="linkMenu-name" // title="drag to view target. click to customize."
onPointerDown={this.onLinkButtonDown}>
<div className="linkMenu-item-buttons">
- <Tooltip disableInteractive={true} title={<div className="dash-tooltip">Edit Link</div>}>
+ <Tooltip disableInteractive title={<div className="dash-tooltip">Edit Link</div>}>
<div className="linkMenu-icon-wrapper" ref={this._editRef} onPointerDown={this.onEdit} onClick={e => e.stopPropagation()}>
<FontAwesomeIcon className="linkMenu-icon" icon="edit" size="sm" />
</div>
</Tooltip>
- <Tooltip disableInteractive={true} title={<div className="dash-tooltip">Show/Hide Link</div>}>
+ <Tooltip disableInteractive title={<div className="dash-tooltip">Show/Hide Link</div>}>
<div className="linkMenu-icon-wrapper" onPointerDown={this.onIconDown}>
<FontAwesomeIcon className="linkMenu-icon" icon={destinationIcon} size="sm" />
</div>
@@ -211,7 +222,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
</p>
) : null}
<div className="linkMenu-title-wrapper">
- <Tooltip disableInteractive={true} title={<div className="dash-tooltip">Follow Link</div>}>
+ <Tooltip disableInteractive title={<div className="dash-tooltip">Follow Link</div>}>
<p className="linkMenu-destination-title">
{this._props.linkDoc.linksToAnnotation && Cast(this._props.destinationDoc.data, WebField)?.url.href === this._props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}
</p>
@@ -221,7 +232,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
</div>
<div className="linkMenu-item-buttons">
- <Tooltip disableInteractive={true} title={<div className="dash-tooltip">Delete Link</div>}>
+ <Tooltip disableInteractive title={<div className="dash-tooltip">Delete Link</div>}>
<div className="linkMenu-deleteButton" onPointerDown={this.deleteLink} onClick={e => e.stopPropagation()}>
<FontAwesomeIcon className="fa-icon" icon="trash" size="sm" />
</div>
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index c9e3c203d..9fb1c0fdc 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -1,14 +1,12 @@
-import { action, observable } from 'mobx';
+/* eslint-disable react/require-default-props */
import { observer } from 'mobx-react';
-import { EditorView } from 'prosemirror-view';
import * as React from 'react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc } from '../../../fields/Doc';
import { Transform } from '../../util/Transform';
-import { undoBatch } from '../../util/UndoManager';
import { DefaultStyleProvider } from '../StyleProvider';
-import { OpenWhere, returnEmptyDocViewList } from '../nodes/DocumentView';
-import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import { returnEmptyDocViewList } from '../nodes/DocumentView';
import { SearchBox } from '../search/SearchBox';
import './LinkPopup.scss';
@@ -28,16 +26,6 @@ interface LinkPopupProps {
@observer
export class LinkPopup extends React.Component<LinkPopupProps> {
- @observable private linkURL: string = '';
- @observable public view?: EditorView = undefined;
-
- // TODO: should check for valid URL
- @undoBatch
- makeLinkToURL = (target: string, lcoation: string) => ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, OpenWhere.addRight, target, target);
-
- @action
- onLinkChange = (e: React.ChangeEvent<HTMLInputElement>) => (this.linkURL = e.target.value);
-
getPWidth = () => 500;
getPHeight = () => 500;
@@ -64,7 +52,7 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
docViewPath={returnEmptyDocViewList}
linkFrom={linkDoc}
linkCreateAnchor={this.props.linkCreateAnchor}
- linkSearch={true}
+ linkSearch
linkCreated={this.props.linkCreated}
fieldKey="data"
isSelected={returnTrue}
diff --git a/src/client/views/linking/LinkRelationshipSearch.tsx b/src/client/views/linking/LinkRelationshipSearch.tsx
deleted file mode 100644
index 0902d53b2..000000000
--- a/src/client/views/linking/LinkRelationshipSearch.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import './LinkEditor.scss';
-
-interface link_relationshipSearchProps {
- results: string[] | undefined;
- display: string;
- //callback fn to set rel + hide dropdown upon setting
- handleRelationshipSearchChange: (result: string) => void;
- toggleSearch: () => void;
-}
-@observer
-export class link_relationshipSearch extends React.Component<link_relationshipSearchProps> {
- handleResultClick = (e: React.MouseEvent) => {
- const relationship = (e.target as HTMLParagraphElement).textContent;
- if (relationship) {
- this.props.handleRelationshipSearchChange(relationship);
- }
- };
-
- handleMouseEnter = () => {
- this.props.toggleSearch();
- };
-
- handleMouseLeave = () => {
- this.props.toggleSearch();
- };
-
- /**
- * Render an empty div to increase the height of LinkEditor to accommodate 2+ results
- */
- emptyDiv = () => {
- if (this.props.results && this.props.results.length > 2 && this.props.display === 'block') {
- return <div style={{ height: '50px' }} />;
- }
- };
-
- render() {
- return (
- <div className="linkEditor-relationship-dropdown-container">
- <div className="linkEditor-relationship-dropdown" style={{ display: this.props.display }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
- {
- // return a dropdown of relationship results if there exist results
- this.props.results ? (
- this.props.results.map(result => {
- return (
- <p key={result} onClick={this.handleResultClick}>
- {result}
- </p>
- );
- })
- ) : (
- <p>No matching relationships</p>
- )
- }
- </div>
-
- {/*Render an empty div to increase the height of LinkEditor to accommodate 2+ results */}
- {this.emptyDiv()}
- </div>
- );
- }
-}
diff --git a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
index bce2b296f..e48e993cf 100644
--- a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
+++ b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
@@ -2,10 +2,9 @@ import { action } from 'mobx';
import * as React from 'react';
import { Doc } from '../../../../fields/Doc';
import { InkTool } from '../../../../fields/InkField';
-import { SelectionManager } from '../../../util/SelectionManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { CollectionDockingView } from '../../collections/CollectionDockingView';
-import { OpenWhereMod } from '../../nodes/DocumentView';
+import { OpenWhereMod } from '../../nodes/OpenWhere';
import { NewLightboxView } from '../NewLightboxView';
import './ButtonMenu.scss';
import { IButtonMenu } from './utils';
@@ -26,7 +25,7 @@ export const ButtonMenu = (props: IButtonMenu) => {
onClick={e => {
e.stopPropagation();
CollectionDockingView.AddSplit(NewLightboxView.LightboxDoc || NewLightboxView.LightboxDoc!, OpenWhereMod.none);
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
NewLightboxView.SetNewLightboxDoc(undefined);
}}></div>
<div
diff --git a/src/client/views/newlightbox/ExploreView/index.ts b/src/client/views/newlightbox/ExploreView/index.ts
index bf94eedcd..f2ebf511f 100644
--- a/src/client/views/newlightbox/ExploreView/index.ts
+++ b/src/client/views/newlightbox/ExploreView/index.ts
@@ -1 +1 @@
-export * from './ExploreView' \ No newline at end of file
+export * from './ExploreView';
diff --git a/src/client/views/newlightbox/ExploreView/utils.ts b/src/client/views/newlightbox/ExploreView/utils.ts
index 7d9cf226d..2d1bd75a9 100644
--- a/src/client/views/newlightbox/ExploreView/utils.ts
+++ b/src/client/views/newlightbox/ExploreView/utils.ts
@@ -1,20 +1,21 @@
-import { IRecommendation } from "../components";
+import { IRecommendation } from '../components';
export interface IExploreView {
- recs?: IRecommendation[],
- bounds?: IBounds
+ recs?: IRecommendation[];
+ // eslint-disable-next-line no-use-before-define
+ bounds?: IBounds;
}
export const emptyBounds = {
max_x: 0,
max_y: 0,
min_x: 0,
- min_y: 0
-}
+ min_y: 0,
+};
export interface IBounds {
- max_x: number,
- max_y: number,
- min_x: number,
- min_y: number
-} \ No newline at end of file
+ max_x: number;
+ max_y: number;
+ min_x: number;
+ min_y: number;
+}
diff --git a/src/client/views/newlightbox/Header/LightboxHeader.tsx b/src/client/views/newlightbox/Header/LightboxHeader.tsx
index 76efe0185..51bfaa4e5 100644
--- a/src/client/views/newlightbox/Header/LightboxHeader.tsx
+++ b/src/client/views/newlightbox/Header/LightboxHeader.tsx
@@ -12,13 +12,13 @@ import { getType } from '../utils';
import './LightboxHeader.scss';
import { INewLightboxHeader } from './utils';
-export const NewLightboxHeader = (props: INewLightboxHeader) => {
+export function NewLightboxHeader(props: INewLightboxHeader) {
const { height = 100, width } = props;
const [doc, setDoc] = React.useState<Doc | undefined>(LightboxView.LightboxDoc);
const [editing, setEditing] = React.useState<boolean>(false);
const [title, setTitle] = React.useState<JSX.Element | null>(null);
React.useEffect(() => {
- let lbDoc = LightboxView.LightboxDoc;
+ const lbDoc = LightboxView.LightboxDoc;
setDoc(lbDoc);
if (lbDoc) {
setTitle(
@@ -37,34 +37,23 @@ export const NewLightboxHeader = (props: INewLightboxHeader) => {
const [saved, setSaved] = React.useState<boolean>(false);
if (!doc) return null;
- else
- return (
- <div className={`newLightboxHeader-container`} onPointerDown={e => e.stopPropagation()} style={{ minHeight: height, height: height, width: width }}>
- <div className={`title-container`}>
- <div className={`lb-label`}>Title</div>
- {title}
- </div>
- <div className={`type-container`}>
- <div className={`lb-label`}>Type</div>
- <div className={`type`}>{getType(StrCast(doc.type))}</div>
- </div>
- <div style={{ gridColumn: 2, gridRow: 1, height: '100%', display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}>
- <IconButton size={Size.XSMALL} onClick={() => setSaved(!saved)} color={Colors.DARK_GRAY} icon={saved ? <BsBookmarkFill /> : <BsBookmark />} />
- <IconButton size={Size.XSMALL} onClick={() => setSaved(!saved)} color={Colors.DARK_GRAY} icon={saved ? <BsBookmarkFill /> : <BsBookmark />} />
- </div>
- <div style={{ gridColumn: 2, gridRow: 2, height: '100%', display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}>
- <Button
- onClick={() => {
- console.log(NewLightboxView.ExploreMode);
- NewLightboxView.SetExploreMode(!NewLightboxView.ExploreMode);
- }}
- size={Size.XSMALL}
- color={Colors.DARK_GRAY}
- type={Type.SEC}
- text={'t-SNE 2D Embeddings'}
- icon={<MdTravelExplore />}
- />
- </div>
+ return (
+ <div className="newLightboxHeader-container" onPointerDown={e => e.stopPropagation()} style={{ minHeight: height, height: height, width: width }}>
+ <div className="title-container">
+ <div className="lb-label">Title</div>
+ {title}
</div>
- );
-};
+ <div className="type-container">
+ <div className="lb-label">Type</div>
+ <div className="type">{getType(StrCast(doc.type))}</div>
+ </div>
+ <div style={{ gridColumn: 2, gridRow: 1, height: '100%', display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}>
+ <IconButton size={Size.XSMALL} onClick={() => setSaved(!saved)} color={Colors.DARK_GRAY} icon={saved ? <BsBookmarkFill /> : <BsBookmark />} />
+ <IconButton size={Size.XSMALL} onClick={() => setSaved(!saved)} color={Colors.DARK_GRAY} icon={saved ? <BsBookmarkFill /> : <BsBookmark />} />
+ </div>
+ <div style={{ gridColumn: 2, gridRow: 2, height: '100%', display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}>
+ <Button onClick={() => NewLightboxView.SetExploreMode(!NewLightboxView.ExploreMode)} size={Size.XSMALL} color={Colors.DARK_GRAY} type={Type.SEC} text="t-SNE 2D Embeddings" icon={<MdTravelExplore />} />
+ </div>
+ </div>
+ );
+}
diff --git a/src/client/views/newlightbox/Header/index.ts b/src/client/views/newlightbox/Header/index.ts
index 090677c16..88a533585 100644
--- a/src/client/views/newlightbox/Header/index.ts
+++ b/src/client/views/newlightbox/Header/index.ts
@@ -1 +1 @@
-export * from './LightboxHeader' \ No newline at end of file
+export * from './LightboxHeader';
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
index 12b9870ca..558ce7e38 100644
--- a/src/client/views/newlightbox/NewLightboxView.tsx
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -1,23 +1,21 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../../Utils';
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
+import { CreateLinkToActiveAudio, Doc, DocListCast, Opt } from '../../../fields/Doc';
import { InkTool } from '../../../fields/InkField';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { DocUtils } from '../../documents/Documents';
-import { DocumentManager } from '../../util/DocumentManager';
-import { LinkManager } from '../../util/LinkManager';
-import { SelectionManager } from '../../util/SelectionManager';
+import { Cast, NumCast, StrCast, toList } from '../../../fields/Types';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { GestureOverlay } from '../GestureOverlay';
import { LightboxView } from '../LightboxView';
import { DefaultStyleProvider } from '../StyleProvider';
-import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
-import { TabDocView } from '../collections/TabDocView';
-import { DocumentView, OpenWhere } from '../nodes/DocumentView';
+import { DocumentView } from '../nodes/DocumentView';
+import { OpenWhere } from '../nodes/OpenWhere';
import { ExploreView } from './ExploreView';
import { IBounds, emptyBounds } from './ExploreView/utils';
import { NewLightboxHeader } from './Header';
@@ -25,11 +23,11 @@ import './NewLightboxView.scss';
import { RecommendationList } from './RecommendationList';
import { IRecommendation } from './components';
-enum LightboxStatus {
- RECOMMENDATIONS = 'recommendations',
- ANNOTATIONS = 'annotations',
- NONE = 'none',
-}
+// enum LightboxStatus {
+// RECOMMENDATIONS = 'recommendations',
+// ANNOTATIONS = 'annotations',
+// NONE = 'none',
+// }
interface LightboxViewProps {
PanelWidth: number;
@@ -46,10 +44,6 @@ type LightboxSavedState = {
};
@observer
export class NewLightboxView extends React.Component<LightboxViewProps> {
- @computed public static get LightboxDoc() {
- return this._doc;
- }
- private static LightboxDocTemplate = () => NewLightboxView._layoutTemplate;
@observable private static _layoutTemplate: Opt<Doc> = undefined;
@observable private static _layoutTemplateString: Opt<string> = undefined;
@observable private static _doc: Opt<Doc> = undefined;
@@ -62,59 +56,39 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
// keywords
@observable private static _keywords: string[] = [];
- @action public static SetKeywords(kw: string[]) {
- this._keywords = kw;
- }
- @computed public static get Keywords() {
- return this._keywords;
- }
-
- // query
@observable private static _query: string = '';
- @action public static SetQuery(query: string) {
- this._query = query;
- }
- @computed public static get Query() {
- return this._query;
- }
-
- // keywords
@observable private static _recs: IRecommendation[] = [];
- @action public static SetRecs(recs: IRecommendation[]) {
- this._recs = recs;
- }
- @computed public static get Recs() {
- return this._recs;
- }
-
- // bounds
@observable private static _bounds: IBounds = emptyBounds;
- @action public static SetBounds(bounds: IBounds) {
- this._bounds = bounds;
- }
- @computed public static get Bounds() {
- return this._bounds;
- }
-
- // explore
@observable private static _explore: Opt<boolean> = false;
- @action public static SetExploreMode(status: Opt<boolean>) {
- this._explore = status;
- }
- @computed public static get ExploreMode() {
- return this._explore;
- }
-
- // newLightbox sidebar status
@observable private static _sidebarStatus: Opt<string> = '';
- @action public static SetSidebarStatus(sidebarStatus: Opt<string>) {
- this._sidebarStatus = sidebarStatus;
+ static path: { doc: Opt<Doc>; target: Opt<Doc>; history: Opt<{ doc: Doc; target?: Doc }[]>; future: Opt<Doc[]>; saved: Opt<LightboxSavedState> }[] = [];
+ private static LightboxDocTemplate = () => NewLightboxView._layoutTemplate;
+ public static GetSavedState(doc: Doc) {
+ return this.LightboxDoc === doc && this._savedState ? this._savedState : undefined;
}
- @computed public static get SidebarStatus() {
- return this._sidebarStatus;
+ // adds a cookie to the newLightbox view - the cookie becomes part of a filter which will display any documents whose cookie metadata field matches this cookie
+ @action
+ public static SetCookie(cookie: string) {
+ if (this.LightboxDoc && cookie) {
+ this._docFilters = (f => (this._docFilters ? [this._docFilters.push(f) as any, this._docFilters][1] : [f]))(`cookies:${cookie}:provide`);
+ }
}
+ public static AddDocTab = (docsIn: Doc | Doc[], location: OpenWhere, layoutTemplate?: Doc | string) => {
+ DocumentView.DeselectAll();
+ const doc = toList(docsIn).lastElement();
+ return (
+ doc &&
+ NewLightboxView.SetNewLightboxDoc(
+ doc,
+ undefined,
+ [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...(NewLightboxView._future ?? [])].sort(
+ (a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)
+ ),
+ layoutTemplate
+ )
+ );
+ };
- static path: { doc: Opt<Doc>; target: Opt<Doc>; history: Opt<{ doc: Doc; target?: Doc }[]>; future: Opt<Doc[]>; saved: Opt<LightboxSavedState> }[] = [];
@action public static SetNewLightboxDoc(doc: Opt<Doc>, target?: Doc, future?: Doc[], layoutTemplate?: Doc | string) {
if (this.LightboxDoc && this.LightboxDoc !== doc && this._savedState) {
if (this._savedState.panX !== undefined) this.LightboxDoc._freeform_panX = this._savedState.panX;
@@ -129,10 +103,10 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
Doc.ActiveTool = InkTool.None;
SnappingManager.SetExploreMode(false);
} else {
- const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement();
+ const l = CreateLinkToActiveAudio(() => doc).lastElement();
l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
- CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.());
- //TabDocView.PinDoc(doc, { hidePresBox: true });
+ DocumentView.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.());
+ // DocumentView.PinDoc(doc, { hidePresBox: true });
this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]);
if (doc !== LightboxView.LightboxDoc) {
this._savedState = {
@@ -151,7 +125,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
...future
.slice()
.sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow))
- .sort((a, b) => LinkManager.Links(a).length - LinkManager.Links(b).length),
+ .sort((a, b) => Doc.Links(a).length - Doc.Links(b).length),
];
}
this._doc = doc;
@@ -166,85 +140,33 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
public static IsNewLightboxDocView(path: DocumentView[]) {
return (path ?? []).includes(this._docView!);
}
- @computed get leftBorder() {
- return Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]);
- }
- @computed get topBorder() {
- return Math.min(this.props.PanelHeight / 4, this.props.maxBorder[1]);
- }
- newLightboxWidth = () => this.props.PanelWidth - 420;
- newLightboxHeight = () => this.props.PanelHeight - 140;
- newLightboxScreenToLocal = () => new Transform(-this.leftBorder, -this.topBorder, 1);
- navBtn = (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => {
- return (
- <div
- className="newLightboxView-navBtn-frame"
- style={{
- display: display(),
- left,
- width: bottom !== undefined ? undefined : Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]),
- bottom,
- }}>
- <div className="newLightboxView-navBtn" title={color} style={{ top, color: color ? 'red' : 'white', background: color ? 'white' : undefined }} onClick={click}>
- <div style={{ height: 10 }}>{color}</div>
- <FontAwesomeIcon icon={icon as any} size="3x" />
- </div>
- </div>
- );
- };
- public static GetSavedState(doc: Doc) {
- return this.LightboxDoc === doc && this._savedState ? this._savedState : undefined;
- }
-
- // adds a cookie to the newLightbox view - the cookie becomes part of a filter which will display any documents whose cookie metadata field matches this cookie
- @action
- public static SetCookie(cookie: string) {
- if (this.LightboxDoc && cookie) {
- this._docFilters = (f => (this._docFilters ? [this._docFilters.push(f) as any, this._docFilters][1] : [f]))(`cookies:${cookie}:provide`);
- }
- }
- public static AddDocTab = (doc: Doc, location: OpenWhere, layoutTemplate?: Doc | string) => {
- SelectionManager.DeselectAll();
- return NewLightboxView.SetNewLightboxDoc(
- doc,
- undefined,
- [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...(NewLightboxView._future ?? [])].sort(
- (a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)
- ),
- layoutTemplate
- );
- };
- docFilters = () => NewLightboxView._docFilters || [];
- addDocTab = NewLightboxView.AddDocTab;
@action public static Next() {
const doc = NewLightboxView._doc!;
const target = (NewLightboxView._docTarget = this._future?.pop());
- const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target);
+ const targetDocView = target && DocumentView.getLightboxDocumentView(target);
if (targetDocView && target) {
- const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement();
+ const l = CreateLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement();
l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
- DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
+ DocumentView.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
if (NewLightboxView._history?.lastElement().target !== target) NewLightboxView._history?.push({ doc, target });
- } else {
- if (!target && NewLightboxView.path.length) {
- const saved = NewLightboxView._savedState;
- if (LightboxView.LightboxDoc && saved) {
- LightboxView.LightboxDoc._freeform_panX = saved.panX;
- LightboxView.LightboxDoc._freeform_panY = saved.panY;
- LightboxView.LightboxDoc._freeform_scale = saved.scale;
- LightboxView.LightboxDoc._layout_scrollTop = saved.scrollTop;
- }
- const pop = NewLightboxView.path.pop();
- if (pop) {
- NewLightboxView._doc = pop.doc;
- NewLightboxView._docTarget = pop.target;
- NewLightboxView._future = pop.future;
- NewLightboxView._history = pop.history;
- NewLightboxView._savedState = pop.saved;
- }
- } else {
- NewLightboxView.SetNewLightboxDoc(target);
+ } else if (!target && NewLightboxView.path.length) {
+ const saved = NewLightboxView._savedState;
+ if (LightboxView.LightboxDoc && saved) {
+ LightboxView.LightboxDoc._freeform_panX = saved.panX;
+ LightboxView.LightboxDoc._freeform_panY = saved.panY;
+ LightboxView.LightboxDoc._freeform_scale = saved.scale;
+ LightboxView.LightboxDoc._layout_scrollTop = saved.scrollTop;
+ }
+ const pop = NewLightboxView.path.pop();
+ if (pop) {
+ NewLightboxView._doc = pop.doc;
+ NewLightboxView._docTarget = pop.target;
+ NewLightboxView._future = pop.future;
+ NewLightboxView._history = pop.history;
+ NewLightboxView._savedState = pop.saved;
}
+ } else {
+ NewLightboxView.SetNewLightboxDoc(target);
}
}
@@ -255,15 +177,127 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
return;
}
const { doc, target } = NewLightboxView._history?.lastElement();
- const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc);
+ const docView = DocumentView.getLightboxDocumentView(target || doc);
if (docView) {
NewLightboxView._docTarget = target;
- target && DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
+ target && DocumentView.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
} else {
NewLightboxView.SetNewLightboxDoc(doc, target);
}
if (NewLightboxView._future?.lastElement() !== previous.target || previous.doc) NewLightboxView._future?.push(previous.target || previous.doc);
}
+
+ @action public static SetKeywords(kw: string[]) {
+ this._keywords = kw;
+ }
+ @computed public static get Keywords() {
+ return this._keywords;
+ }
+ @computed public static get LightboxDoc() {
+ return this._doc;
+ }
+
+ // query
+ @action public static SetQuery(query: string) {
+ this._query = query;
+ }
+ @computed public static get Query() {
+ return this._query;
+ }
+
+ // keywords
+ @action public static SetRecs(recs: IRecommendation[]) {
+ this._recs = recs;
+ }
+ @computed public static get Recs() {
+ return this._recs;
+ }
+
+ // bounds
+ @action public static SetBounds(bounds: IBounds) {
+ this._bounds = bounds;
+ }
+ @computed public static get Bounds() {
+ return this._bounds;
+ }
+ // newLightbox sidebar status
+ @action public static SetSidebarStatus(sidebarStatus: Opt<string>) {
+ this._sidebarStatus = sidebarStatus;
+ }
+
+ // explore
+ @action public static SetExploreMode(status: Opt<boolean>) {
+ this._explore = status;
+ }
+
+ addDocTab = NewLightboxView.AddDocTab;
+
+ @computed public static get ExploreMode() {
+ return this._explore;
+ }
+
+ @computed public static get SidebarStatus() {
+ return this._sidebarStatus;
+ }
+ @computed get leftBorder() {
+ return Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]);
+ }
+ @computed get topBorder() {
+ return Math.min(this.props.PanelHeight / 4, this.props.maxBorder[1]);
+ }
+
+ @computed
+ get documentView() {
+ if (!LightboxView.LightboxDoc) return null;
+ return (
+ <GestureOverlay isActive>
+ <DocumentView
+ ref={action((r: DocumentView | null) => {
+ NewLightboxView._docView = r !== null ? r : undefined;
+ })}
+ Document={LightboxView.LightboxDoc}
+ PanelWidth={this.newLightboxWidth}
+ PanelHeight={this.newLightboxHeight}
+ LayoutTemplate={NewLightboxView.LightboxDocTemplate}
+ isDocumentActive={returnTrue} // without this being true, sidebar annotations need to be activated before text can be selected.
+ isContentActive={returnTrue}
+ styleProvider={DefaultStyleProvider}
+ ScreenToLocalTransform={this.newLightboxScreenToLocal}
+ renderDepth={0}
+ containerViewPath={returnEmptyDoclist}
+ childFilters={this.docFilters}
+ childFiltersByRanges={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ addDocument={undefined}
+ removeDocument={undefined}
+ whenChildContentsActiveChanged={emptyFunction}
+ addDocTab={this.addDocTab}
+ pinToPres={DocumentView.PinDoc}
+ focus={emptyFunction}
+ />
+ </GestureOverlay>
+ );
+ }
+ newLightboxWidth = () => this.props.PanelWidth - 420;
+ newLightboxHeight = () => this.props.PanelHeight - 140;
+ newLightboxScreenToLocal = () => new Transform(-this.leftBorder, -this.topBorder, 1);
+ navBtn = (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => (
+ <div
+ className="newLightboxView-navBtn-frame"
+ style={{
+ display: display(),
+ left,
+ width: bottom !== undefined ? undefined : Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]),
+ bottom,
+ }}>
+ <div className="newLightboxView-navBtn" title={color} style={{ top, color: color ? 'red' : 'white', background: color ? 'white' : undefined }} onClick={click}>
+ <div style={{ height: 10 }}>{color}</div>
+ <FontAwesomeIcon icon={icon as any} size="3x" />
+ </div>
+ </div>
+ );
+
+ docFilters = () => NewLightboxView._docFilters || [];
@action
stepInto = () => {
NewLightboxView.path.push({
@@ -277,52 +311,19 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
if (coll) {
const fieldKey = Doc.LayoutFieldKey(coll);
const contents = [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + '_annotations'])];
- const links = LinkManager.Links(coll)
- .map(link => LinkManager.getOppositeAnchor(link, coll))
+ const links = Doc.Links(coll)
+ .map(link => Doc.getOppositeAnchor(link, coll))
.filter(doc => doc)
.map(doc => doc!);
NewLightboxView.SetNewLightboxDoc(coll, undefined, contents.length ? contents : links);
}
};
- @computed
- get documentView() {
- if (!LightboxView.LightboxDoc) return null;
- else
- return (
- <GestureOverlay isActive={true}>
- <DocumentView
- ref={action((r: DocumentView | null) => (NewLightboxView._docView = r !== null ? r : undefined))}
- Document={LightboxView.LightboxDoc}
- PanelWidth={this.newLightboxWidth}
- PanelHeight={this.newLightboxHeight}
- LayoutTemplate={NewLightboxView.LightboxDocTemplate}
- isDocumentActive={returnTrue} // without this being true, sidebar annotations need to be activated before text can be selected.
- isContentActive={returnTrue}
- styleProvider={DefaultStyleProvider}
- ScreenToLocalTransform={this.newLightboxScreenToLocal}
- renderDepth={0}
- containerViewPath={returnEmptyDoclist}
- childFilters={this.docFilters}
- childFiltersByRanges={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- addDocument={undefined}
- removeDocument={undefined}
- whenChildContentsActiveChanged={emptyFunction}
- addDocTab={this.addDocTab}
- pinToPres={TabDocView.PinDoc}
- onBrowseClickScript={DocumentView.exploreMode}
- focus={emptyFunction}
- />
- </GestureOverlay>
- );
- }
-
future = () => NewLightboxView._future;
render() {
- let newLightboxHeaderHeight = 100;
- let downx = 0,
- downy = 0;
+ const newLightboxHeaderHeight = 100;
+ let downx = 0;
+ let downy = 0;
return !LightboxView.LightboxDoc ? null : (
<div
className="newLightboxView-frame"
@@ -335,7 +336,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
NewLightboxView.SetNewLightboxDoc(undefined);
}
}}>
- <div className={`app-document`} style={{ gridTemplateColumns: `calc(100% - 400px) 400px` }}>
+ <div className="app-document" style={{ gridTemplateColumns: `calc(100% - 400px) 400px` }}>
<div
className="newLightboxView-contents"
style={{
@@ -350,7 +351,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
{this.documentView}
</div>
) : (
- <div className={`explore`}>
+ <div className="explore">
<ExploreView recs={NewLightboxView.Recs} bounds={NewLightboxView.Bounds} />
</div>
)}
@@ -363,6 +364,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
}
interface NewLightboxTourBtnProps {
navBtn: (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => JSX.Element;
+ // eslint-disable-next-line react/no-unused-prop-types
future: () => Opt<Doc[]>;
stepInto: () => void;
}
@@ -374,7 +376,7 @@ export class NewLightboxTourBtn extends React.Component<NewLightboxTourBtnProps>
0,
0,
'chevron-down',
- () => (LightboxView.LightboxDoc /*&& this.props.future()?.length*/ ? '' : 'none'),
+ () => (LightboxView.LightboxDoc /* && this.props.future()?.length */ ? '' : 'none'),
e => {
e.stopPropagation();
this.props.stepInto();
diff --git a/src/client/views/newlightbox/RecommendationList/index.ts b/src/client/views/newlightbox/RecommendationList/index.ts
index f4555c1f2..226eafb7e 100644
--- a/src/client/views/newlightbox/RecommendationList/index.ts
+++ b/src/client/views/newlightbox/RecommendationList/index.ts
@@ -1 +1 @@
-export * from './RecommendationList' \ No newline at end of file
+export * from './RecommendationList';
diff --git a/src/client/views/newlightbox/RecommendationList/utils.ts b/src/client/views/newlightbox/RecommendationList/utils.ts
index cdfff3258..fbf8ac19f 100644
--- a/src/client/views/newlightbox/RecommendationList/utils.ts
+++ b/src/client/views/newlightbox/RecommendationList/utils.ts
@@ -1,9 +1,8 @@
-import { IRecommendation } from "../components";
+import { IRecommendation } from '../components';
export interface IRecommendationList {
- loading?: boolean,
- keywords?: string[],
- recs?: IRecommendation[]
- getRecs?: any
+ loading?: boolean;
+ keywords?: string[];
+ recs?: IRecommendation[];
+ getRecs?: any;
}
-
diff --git a/src/client/views/newlightbox/components/EditableText/index.ts b/src/client/views/newlightbox/components/EditableText/index.ts
index e3367b175..444881a74 100644
--- a/src/client/views/newlightbox/components/EditableText/index.ts
+++ b/src/client/views/newlightbox/components/EditableText/index.ts
@@ -1 +1 @@
-export * from './EditableText'
+export * from './EditableText';
diff --git a/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx
index 23027808f..375408d01 100644
--- a/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx
+++ b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx
@@ -1,12 +1,12 @@
import * as React from 'react';
-import { IRecommendation } from './utils';
-import './Recommendation.scss';
-import { getType } from '../../utils';
import { FaEyeSlash } from 'react-icons/fa';
-import { NewLightboxView } from '../../NewLightboxView';
-import { DocumentManager } from '../../../../util/DocumentManager';
import { Doc } from '../../../../../fields/Doc';
import { Docs } from '../../../../documents/Documents';
+import { DocumentView } from '../../../nodes/DocumentView';
+import { NewLightboxView } from '../../NewLightboxView';
+import { getType } from '../../utils';
+import './Recommendation.scss';
+import { IRecommendation } from './utils';
export const Recommendation = (props: IRecommendation) => {
const { title, data, type, text, transcript, loading, source, previewUrl, related_concepts, distance, docId } = props;
@@ -17,7 +17,7 @@ export const Recommendation = (props: IRecommendation) => {
onClick={() => {
let doc: Doc | null = null;
if (source == 'Dash' && docId) {
- const docView = DocumentManager.Instance.getDocumentViewsById(docId).lastElement();
+ const docView = DocumentView.getDocumentViewsById(docId).lastElement();
if (docView) {
doc = docView.Document;
}
diff --git a/src/client/views/newlightbox/components/Recommendation/index.ts b/src/client/views/newlightbox/components/Recommendation/index.ts
index 12ebf9d6e..4fce185ec 100644
--- a/src/client/views/newlightbox/components/Recommendation/index.ts
+++ b/src/client/views/newlightbox/components/Recommendation/index.ts
@@ -1,2 +1,2 @@
-export * from './utils'
-export * from './Recommendation' \ No newline at end of file
+export * from './utils';
+export * from './Recommendation';
diff --git a/src/client/views/newlightbox/components/Recommendation/utils.ts b/src/client/views/newlightbox/components/Recommendation/utils.ts
index 796ce0eb0..4a55d394e 100644
--- a/src/client/views/newlightbox/components/Recommendation/utils.ts
+++ b/src/client/views/newlightbox/components/Recommendation/utils.ts
@@ -1,23 +1,23 @@
-import { DocumentType } from "../../../../documents/DocumentTypes"
+import { DocumentType } from '../../../../documents/DocumentTypes';
export interface IRecommendation {
- loading?: boolean
- type?: DocumentType | string,
- data?: string,
- title?: string,
- text?: string,
- source?: string,
- previewUrl?: string,
+ loading?: boolean;
+ type?: DocumentType | string;
+ data?: string;
+ title?: string;
+ text?: string;
+ source?: string;
+ previewUrl?: string;
transcript?: {
- text: string,
- start: number,
- duration: number
- }[],
+ text: string;
+ start: number;
+ duration: number;
+ }[];
embedding?: {
- x: number,
- y: number
- },
- distance?: number,
- related_concepts?: string[],
- docId?: string
-} \ No newline at end of file
+ x: number;
+ y: number;
+ };
+ distance?: number;
+ related_concepts?: string[];
+ docId?: string;
+}
diff --git a/src/client/views/newlightbox/components/SkeletonDoc/index.ts b/src/client/views/newlightbox/components/SkeletonDoc/index.ts
index 396b7272b..98379f404 100644
--- a/src/client/views/newlightbox/components/SkeletonDoc/index.ts
+++ b/src/client/views/newlightbox/components/SkeletonDoc/index.ts
@@ -1 +1 @@
-export * from './SkeletonDoc' \ No newline at end of file
+export * from './SkeletonDoc';
diff --git a/src/client/views/newlightbox/components/Template/index.ts b/src/client/views/newlightbox/components/Template/index.ts
index 36b5f3f46..f5b83ad09 100644
--- a/src/client/views/newlightbox/components/Template/index.ts
+++ b/src/client/views/newlightbox/components/Template/index.ts
@@ -1 +1 @@
-export * from './Template' \ No newline at end of file
+export * from './Template';
diff --git a/src/client/views/newlightbox/components/index.ts b/src/client/views/newlightbox/components/index.ts
index 3f9128690..1c266c5d8 100644
--- a/src/client/views/newlightbox/components/index.ts
+++ b/src/client/views/newlightbox/components/index.ts
@@ -1,3 +1,3 @@
-export * from './Template'
-export * from './Recommendation'
-export * from './SkeletonDoc' \ No newline at end of file
+export * from './Template';
+export * from './Recommendation';
+export * from './SkeletonDoc';
diff --git a/src/client/views/newlightbox/utils.ts b/src/client/views/newlightbox/utils.ts
index c879718e3..a0d6b9f4a 100644
--- a/src/client/views/newlightbox/utils.ts
+++ b/src/client/views/newlightbox/utils.ts
@@ -1,5 +1,5 @@
+/* eslint-disable no-use-before-define */
import { DocumentType } from '../../documents/DocumentTypes';
-import { IRecommendation } from './components';
export interface IDocRequest {
id: string;
@@ -12,7 +12,7 @@ export const fetchRecommendations = async (src: string, query: string, docs?: ID
console.log('[rec] making request');
if (dummy) {
return {
- recommendations: [], //dummyRecs,
+ recommendations: [], // dummyRecs,
keywords: dummyKeywords,
num_recommendations: 4,
max_x: 100,
@@ -79,6 +79,7 @@ export const getType = (type: DocumentType | string) => {
}
};
+/*
const dummyRecs = {
a: {
title: 'Vannevar Bush - American Engineer',
@@ -117,4 +118,6 @@ const dummyRecs = {
},
};
+*/
+
const dummyKeywords = ['user control', 'vannevar bush', 'hypermedia', 'hypertext'];
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index c685ec66f..9deed4de4 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,28 +1,34 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { action, computed, IReactionDisposer, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
import { DateField } from '../../../fields/DateField';
import { Doc } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { ComputedField } from '../../../fields/ScriptField';
import { Cast, DateCast, NumCast } from '../../../fields/Types';
import { AudioField, nullAudio } from '../../../fields/URLField';
-import { emptyFunction, formatTime, returnFalse, setupMoveUpEvents } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
+import { formatTime } from '../../../Utils';
+import { Docs } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DocUtils } from '../../documents/DocUtils';
import { Networking } from '../../Network';
import { DragManager } from '../../util/DragManager';
-import { LinkManager } from '../../util/LinkManager';
import { undoBatch } from '../../util/UndoManager';
import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
+import { DocViewUtils } from '../DocViewUtils';
+import { PinDocView, PinProps } from '../PinFuncs';
import './AudioBox.scss';
-import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
-import { PinProps, PresBox } from './trails';
-import { OpenWhere } from './DocumentView';
+import { DocumentView } from './DocumentView';
+import { FieldView, FieldViewProps } from './FieldView';
+import { OpenWhere } from './OpenWhere';
/**
* AudioBox
@@ -42,7 +48,7 @@ declare class MediaRecorder {
constructor(e: any); // whatever MediaRecorder has
}
-export enum media_state {
+export enum mediaState {
PendingRecording = 'pendingRecording',
Recording = 'recording',
Paused = 'paused',
@@ -94,19 +100,19 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return this._props.PanelHeight() < 50;
} // used to collapse timeline when node is shrunk
@computed get links() {
- return LinkManager.Links(this.dataDoc);
+ return Doc.Links(this.dataDoc);
}
@computed get mediaState() {
- return this.dataDoc.mediaState as media_state;
+ return this.dataDoc.mediaState as mediaState;
+ }
+ set mediaState(value) {
+ this.dataDoc.mediaState = value;
}
@computed get path() {
// returns the path of the audio file
const path = Cast(this.Document[this.fieldKey], AudioField, null)?.url.href || '';
return path === nullAudio ? '' : path;
}
- set mediaState(value) {
- this.dataDoc.mediaState = value;
- }
@computed get timeline() {
return this._stackedTimeline;
@@ -117,17 +123,17 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this._dropDisposer?.();
Object.values(this._disposers).forEach(disposer => disposer?.());
- this.mediaState === media_state.Recording && this.stopRecording();
+ this.mediaState === mediaState.Recording && this.stopRecording();
}
@action
componentDidMount() {
this._props.setContentViewBox?.(this);
if (this.path) {
- this.mediaState = media_state.Paused;
+ this.mediaState = mediaState.Paused;
this.setPlayheadTime(NumCast(this.layoutDoc.clipStart));
} else {
- this.mediaState = undefined as any as media_state;
+ this.mediaState = undefined as any as mediaState;
}
}
@@ -149,24 +155,24 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this.Document,
this.dataDoc,
this.annotationKey,
- this._ele?.currentTime || Cast(this.Document._layout_currentTimecode, 'number', null) || (this.mediaState === media_state.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined),
+ this._ele?.currentTime || Cast(this.Document._layout_currentTimecode, 'number', null) || (this.mediaState === mediaState.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined),
undefined,
undefined,
addAsAnnotation
) || 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);
+ PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true } }, this.Document);
return anchor;
};
// updates timecode and shows it in timeline, follows links at time
@action
timecodeChanged = () => {
- if (this.mediaState !== media_state.Recording && this._ele) {
+ if (this.mediaState !== mediaState.Recording && this._ele) {
this.links
.map(l => this.getLinkData(l))
- .forEach(({ la1, la2, linkTime }) => {
+ .forEach(({ la1, linkTime }) => {
if (linkTime > NumCast(this.layoutDoc._layout_currentTimecode) && linkTime < this._ele!.currentTime) {
Doc.linkFollowHighlight(la1);
}
@@ -180,7 +186,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@action
playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false) => {
clearTimeout(this._play); // abort any previous clip ending
- if (Number.isNaN(this._ele?.duration)) {
+ if (isNaN(this._ele?.duration ?? Number.NaN)) {
// audio element isn't loaded yet... wait 1/2 second and try again
setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
} else if (this.timeline && this._ele && AudioBox.Enabled) {
@@ -191,7 +197,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
if (seekTimeInSeconds >= 0 && this.timeline.trimStart <= end && seekTimeInSeconds <= this.timeline.trimEnd) {
this._ele.currentTime = start;
this._ele.play();
- this.mediaState = media_state.Playing;
+ this.mediaState = mediaState.Playing;
this.addCurrentlyPlaying();
this._play = setTimeout(
() => {
@@ -213,9 +219,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@action
removeCurrentlyPlaying = () => {
const docView = this.DocumentView?.();
- if (CollectionStackedTimeline.CurrentlyPlaying && docView) {
- const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView);
- index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
+ if (DocumentView.CurrentlyPlaying && docView) {
+ const index = DocumentView.CurrentlyPlaying.indexOf(docView);
+ index !== -1 && DocumentView.CurrentlyPlaying.splice(index, 1);
}
};
@@ -223,17 +229,17 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@action
addCurrentlyPlaying = () => {
const docView = this.DocumentView?.();
- if (!CollectionStackedTimeline.CurrentlyPlaying) {
- CollectionStackedTimeline.CurrentlyPlaying = [];
+ if (!DocumentView.CurrentlyPlaying) {
+ DocumentView.CurrentlyPlaying = [];
}
- if (docView && CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView) === -1) {
- CollectionStackedTimeline.CurrentlyPlaying.push(docView);
+ if (docView && DocumentView.CurrentlyPlaying.indexOf(docView) === -1) {
+ DocumentView.CurrentlyPlaying.push(docView);
}
};
// update the recording time
updateRecordTime = () => {
- if (this.mediaState === media_state.Recording) {
+ if (this.mediaState === mediaState.Recording) {
setTimeout(this.updateRecordTime, 30);
if (!this._paused) {
this.layoutDoc._layout_currentTimecode = (new Date().getTime() - this._recordStart - this._pausedTime) / 1000;
@@ -246,7 +252,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this._stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this._recorder = new MediaRecorder(this._stream);
this.dataDoc[this.fieldKey + '_recordingStart'] = new DateField();
- DocUtils.ActiveRecordings.push(this);
+ DocViewUtils.ActiveRecordings.push(this);
this._recorder.ondataavailable = async (e: any) => {
const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
if (!(result instanceof Error)) {
@@ -254,7 +260,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
};
this._recordStart = new Date().getTime();
- runInAction(() => (this.mediaState = media_state.Recording));
+ runInAction(() => {
+ this.mediaState = mediaState.Recording;
+ });
setTimeout(this.updateRecordTime);
this._recorder.start();
setTimeout(this.stopRecording, 60 * 60 * 1000); // stop after an hour
@@ -269,34 +277,34 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const now = new Date().getTime();
this._paused && (this._pausedTime += now - this._pauseStart);
this.dataDoc[this.fieldKey + '_duration'] = (now - this._recordStart - this._pausedTime) / 1000;
- this.mediaState = media_state.Paused;
+ this.mediaState = mediaState.Paused;
this._stream?.getAudioTracks()[0].stop();
- const ind = DocUtils.ActiveRecordings.indexOf(this);
- ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
+ const ind = DocViewUtils.ActiveRecordings.indexOf(this);
+ ind !== -1 && DocViewUtils.ActiveRecordings.splice(ind, 1);
}
};
// context menu
- specificContextMenu = (e: React.MouseEvent): void => {
+ specificContextMenu = (): void => {
const funcs: ContextMenuProps[] = [];
funcs.push({
description: (this.layoutDoc.hideAnchors ? "Don't hide" : 'Hide') + ' anchors',
- event: e => (this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors),
+ event: () => { this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors; }, // prettier-ignore
icon: 'expand-arrows-alt',
});
funcs.push({
description: (this.layoutDoc.dontAutoFollowLinks ? '' : "Don't") + ' follow links when encountered',
- event: e => (this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks),
+ event: () => { this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks}, // prettier-ignore
icon: 'expand-arrows-alt',
});
funcs.push({
description: (this.layoutDoc.dontAutoPlayFollowedLinks ? '' : "Don't") + ' play when link is selected',
- event: e => (this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks),
+ event: () => { this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks; }, // prettier-ignore
icon: 'expand-arrows-alt',
});
funcs.push({
description: (this.layoutDoc.autoPlayAnchors ? "Don't auto" : 'Auto') + ' play anchors onClick',
- event: e => (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors),
+ event: () => { this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors; }, // prettier-ignore
icon: 'expand-arrows-alt',
});
ContextMenu.Instance?.addItem({
@@ -342,9 +350,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
};
- IsPlaying = () => this.mediaState === media_state.Playing;
+ IsPlaying = () => this.mediaState === mediaState.Playing;
TogglePause = () => {
- if (this.mediaState === media_state.Paused) this.Play();
+ if (this.mediaState === mediaState.Paused) this.Play();
else this.pause();
};
// pause playback without removing from the playback list to allow user to play it again.
@@ -352,7 +360,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
pause = () => {
if (this._ele) {
this._ele.pause();
- this.mediaState = media_state.Paused;
+ this.mediaState = mediaState.Paused;
// if paused in the middle of playback, prevents restart on next play
if (!this._finished) clearTimeout(this._play);
@@ -434,7 +442,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
// plays link
- playLink = (link: Doc, options: FocusViewOptions) => {
+ playLink = (link: Doc /* , options: FocusViewOptions */) => {
if (link.annotationOn === this.Document) {
if (!this.layoutDoc.dontAutoPlayFollowedLinks) {
this.playFrom(this.timeline?.anchorStart(link) || 0, this.timeline?.anchorEnd(link));
@@ -460,13 +468,17 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
@action
- timelineWhenChildContentsActiveChanged = (isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
+ timelineWhenChildContentsActiveChanged = (isActive: boolean) => {
+ this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
+ };
timelineScreenToLocal = () => this.ScreenToLocalBoxXf().translate(0, -AudioBox.topControlsHeight);
- setPlayheadTime = (time: number) => (this._ele!.currentTime /*= this.layoutDoc._layout_currentTimecode*/ = time);
+ setPlayheadTime = (time: number) => {
+ this._ele!.currentTime /* = this.layoutDoc._layout_currentTimecode */ = time;
+ };
- playing = () => this.mediaState === media_state.Playing;
+ playing = () => this.mediaState === mediaState.Playing;
isActiveChild = () => this._isAnyChildContentActive;
@@ -497,7 +509,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
e,
returnFalse,
returnFalse,
- action(e => {
+ action(() => {
if (this.timeline?.IsTrimming !== TrimScope.None) {
this.timeline?.CancelTrimming();
} else {
@@ -523,7 +535,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
e,
returnFalse,
returnFalse,
- action((e: PointerEvent, doubleTap?: boolean) => {
+ action((moveEv: PointerEvent, doubleTap?: boolean) => {
if (doubleTap) {
this.startTrim(TrimScope.All);
} else if (this.timeline) {
@@ -563,14 +575,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
setupTimelineDrop = (r: HTMLDivElement | null) => {
if (r && this.timeline) {
this._dropDisposer?.();
- this._dropDisposer = DragManager.MakeDropTarget(
- r,
- (e, de) => {
- const [xp, yp] = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y);
- de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData, xp);
- },
- this.layoutDoc
- );
+ this._dropDisposer = DragManager.MakeDropTarget(r, (e, de) => de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData), this.layoutDoc);
}
};
@@ -581,7 +586,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
<div className="audiobox-dictation" onPointerDown={this.onFile}>
<FontAwesomeIcon size="2x" icon="file-alt" />
</div>
- {[media_state.Recording, media_state.Playing].includes(this.mediaState) ? (
+ {[mediaState.Recording, mediaState.Playing].includes(this.mediaState) ? (
<div className="recording-controls" onClick={e => e.stopPropagation()}>
<div className="record-button" onPointerDown={this.Record}>
<FontAwesomeIcon size="2x" icon="stop" />
@@ -614,31 +619,29 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
<div className="controls-left">
<div
className="audiobox-button"
- title={this.mediaState === media_state.Paused ? 'play' : 'pause'}
+ title={this.mediaState === mediaState.Paused ? 'play' : 'pause'}
onPointerDown={
- this.mediaState === media_state.Paused
+ this.mediaState === mediaState.Paused
? this.Play
: e => {
e.stopPropagation();
this.Pause();
}
}>
- <FontAwesomeIcon icon={this.mediaState === media_state.Paused ? 'play' : 'pause'} size={'1x'} />
+ <FontAwesomeIcon icon={this.mediaState === mediaState.Paused ? 'play' : 'pause'} size="1x" />
</div>
{!this.miniPlayer && (
<>
<Tooltip title={<>trim audio</>}>
<div className="audiobox-button" onPointerDown={this.onClipPointerDown}>
- <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'check' : 'cut'} size={'1x'} />
+ <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'check' : 'cut'} size="1x" />
</div>
</Tooltip>
- {this.timeline?.IsTrimming == TrimScope.None && !NumCast(this.layoutDoc.clipStart) && NumCast(this.layoutDoc.clipEnd) === this.rawDuration ? (
- <></>
- ) : (
- <Tooltip title={<>{this.timeline?.IsTrimming !== TrimScope.None ? 'Cancel trimming' : 'Edit original timeline'}</>}>
+ {this.timeline?.IsTrimming === TrimScope.None && !NumCast(this.layoutDoc.clipStart) && NumCast(this.layoutDoc.clipEnd) === this.rawDuration ? null : (
+ <Tooltip title={this.timeline?.IsTrimming !== TrimScope.None ? 'Cancel trimming' : 'Edit original timeline'}>
<div className="audiobox-button" onPointerDown={this.onResetPointerDown}>
- <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'cancel' : 'arrows-left-right'} size={'1x'} />
+ <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'cancel' : 'arrows-left-right'} size="1x" />
</div>
</Tooltip>
)}
@@ -705,9 +708,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@computed get renderTimeline() {
return (
<CollectionStackedTimeline
- ref={action((r: CollectionStackedTimeline | null) => (this._stackedTimeline = r))}
+ ref={action((r: CollectionStackedTimeline | null) => {
+ this._stackedTimeline = r;
+ })}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
- CollectionFreeFormDocumentView={undefined}
dataFieldKey={this.fieldKey}
fieldKey={this.annotationKey}
dictationKey={this.fieldKey + '_dictation'}
@@ -738,10 +743,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// returns the html audio element
@computed get audio() {
return (
+ // eslint-disable-next-line jsx-a11y/media-has-caption
<audio
ref={this.setRef}
className={`audiobox-control${this._props.isContentActive() ? '-interactive' : ''}`}
- onLoadedData={action(e => this._ele?.duration && this._ele?.duration !== Infinity && (this.dataDoc[this.fieldKey + '_duration'] = this._ele.duration))}>
+ onLoadedData={action(() => {
+ this._ele?.duration && this._ele?.duration !== Infinity && (this.dataDoc[this.fieldKey + '_duration'] = this._ele.duration);
+ })}>
<source src={this.path} type="audio/mpeg" />
Not supported.
</audio>
@@ -756,3 +764,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
);
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.AUDIO, {
+ layout: { view: AudioBox, dataField: 'data' },
+ options: { acl: '', _height: 100, _layout_fitWidth: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true, systemIcon: 'BsFillVolumeUpFill' },
+});
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 0d0a7c623..685a5aca4 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,23 +1,24 @@
-import { action, makeObservable, observable, trace } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { OmitKeys, numberRange } from '../../../Utils';
+import { OmitKeys } from '../../../ClientUtils';
+import { numberRange } from '../../../Utils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { TransitionTimer } from '../../../fields/DocSymbols';
+import { InkField } from '../../../fields/InkField';
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 { DocumentManager } from '../../util/DocumentManager';
+import { DragManager } from '../../util/DragManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { SelectionManager } from '../../util/SelectionManager';
import { DocComponent } from '../DocComponent';
-import { StyleProp } from '../StyleProvider';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { StyleProp } from '../StyleProp';
import './CollectionFreeFormDocumentView.scss';
-import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
+import { DocumentView, DocumentViewProps } from './DocumentView';
import { FieldViewProps } from './FieldView';
-import { TransitionTimer } from '../../../fields/DocSymbols';
+import { OpenWhere } from './OpenWhere';
/// Ugh, typescript has no run-time way of iterating through the keys of an interface. so we need
/// manaully keep this list of keys in synch wih the fields of the freeFormProps interface
@@ -37,16 +38,18 @@ interface freeFormProps {
highlight?: boolean;
transition?: string;
}
+
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
RenderCutoffProvider: (doc: Doc) => boolean;
- CollectionFreeFormView: CollectionFreeFormView;
+ isAnyChildContentActive: () => boolean;
+ parent: any;
}
@observer
export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & freeFormProps>() {
get displayName() { // this makes mobx trace() statements more descriptive
return 'CollectionFreeFormDocumentView(' + this.Document.title + ')';
} // prettier-ignore
- public static CollectionFreeFormDocViewClassName = 'collectionFreeFormDocumentView-container';
+ public static CollectionFreeFormDocViewClassName = DragManager.dragClassName;
public static animFields: { key: string; val?: number }[] = [
{ key: 'x' },
{ key: 'y' },
@@ -62,6 +65,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
]; // fields that are configured to be animatable using animation frames
public static animStringFields = ['backgroundColor', 'color', 'fillColor']; // fields that are configured to be animatable using animation frames
public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames
+ public static from(dv?: DocumentView): CollectionFreeFormDocumentView | undefined {
+ return dv?._props.parent instanceof CollectionFreeFormDocumentView ? dv._props.parent : undefined;
+ }
constructor(props: CollectionFreeFormDocumentViewProps & freeFormProps) {
super(props);
@@ -96,7 +102,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
if (this.props.transition && !this.Document[TransitionTimer]) {
const num = Number(this.props.transition.match(/([0-9.]+)s/)?.[1]) * 1000 || Number(this.props.transition.match(/([0-9.]+)ms/)?.[1]);
this.Document[TransitionTimer] = setTimeout(
- action(() => (this.Document[TransitionTimer] = this.Transition = undefined)),
+ action(() => {
+ this.Document[TransitionTimer] = this.Transition = undefined;
+ }),
num
);
}
@@ -104,10 +112,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<CollectionFreeFormDocumentViewProps & freeFormProps>>) {
super.componentDidUpdate(prevProps);
- this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
+ this.WrapperKeys.forEach(
+ action(keys => {
+ (this as any)[keys.upper] = (this.props as any)[keys.lower];
+ })
+ );
}
- CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
// this way, downstream code only invalidates when it uses a specific prop, not when any prop changes
DataTransition = () => this.Transition || StrCast(this.Document.dataTransition); // prettier-ignore
RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
@@ -120,6 +131,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
case StyleProp.Opacity: return this.Opacity; // only change the opacity for this specific document, not its children
case StyleProp.BackgroundColor: return this.BackgroundColor;
case StyleProp.Color: return this.Color;
+ default:
} // prettier-ignore
}
return this._props.styleProvider?.(doc, props, property);
@@ -128,7 +140,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
public static getValues(doc: Doc, time: number, fillIn: boolean = true) {
return CollectionFreeFormDocumentView.animFields.reduce(
(p, val) => {
- p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number);
+ p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce(
+ (prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev),
+ undefined as any as number
+ );
return p;
},
{} as { [val: string]: Opt<number> }
@@ -138,7 +153,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
public static getStringValues(doc: Doc, time: number) {
return CollectionFreeFormDocumentView.animStringFields.reduce(
(p, val) => {
- p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string);
+ p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev), undefined as any as string);
return p;
},
{} as { [val: string]: Opt<string> }
@@ -149,7 +164,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
const timecode = Math.round(time);
Object.keys(vals).forEach(val => {
const findexed = Cast(d[`${val}_indexed`], listSpec('string'), []).slice();
- findexed[timecode] = vals[val] as any as string;
+ findexed[timecode] = vals[val] || '';
d[`${val}_indexed`] = new List<string>(findexed);
});
}
@@ -158,7 +173,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
const timecode = Math.round(time);
Object.keys(vals).forEach(val => {
const findexed = Cast(d[`${val}_indexed`], listSpec('number'), []).slice();
- findexed[timecode] = vals[val] as any as number;
+ findexed[timecode] = vals[val] || 0;
d[`${val}_indexed`] = new List<number>(findexed);
});
}
@@ -168,25 +183,50 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
const currentFrame = Cast(doc._currentFrame, 'number', null);
if (currentFrame === undefined) {
doc._currentFrame = 0;
- CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
+ this.setupKeyframes(childDocs, 0);
}
- CollectionFreeFormView.updateKeyframe(undefined, [...childDocs, doc], currentFrame || 0);
+ this.updateKeyframe(undefined, [...childDocs, doc], currentFrame || 0);
doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
}
}
+ public static updateKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], time: number) {
+ const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, timer, undefined, true);
+ const timecode = Math.round(time);
+ docs.forEach(doc => {
+ this.animFields.forEach(val => {
+ const findexed = Cast(doc[`${val.key}_indexed`], listSpec('number'), null);
+ findexed?.length <= timecode + 1 && findexed.push(undefined as any as number);
+ });
+ this.animStringFields.forEach(val => {
+ const findexed = Cast(doc[`${val}_indexed`], listSpec('string'), null);
+ findexed?.length <= timecode + 1 && findexed.push(undefined as any as string);
+ });
+ this.animDataFields(doc).forEach(val => {
+ const findexed = Cast(doc[`${val}_indexed`], listSpec(InkField), null);
+ findexed?.length <= timecode + 1 && findexed.push(undefined as any);
+ });
+ });
+ return newTimer;
+ }
public static setupKeyframes(docs: Doc[], currTimecode: number, makeAppear: boolean = false) {
docs.forEach(doc => {
if (doc.appearFrame === undefined) doc.appearFrame = currTimecode;
- if (!doc['opacity_indexed']) {
+ if (!doc.opacity_indexed) {
// opacity is unlike other fields because it's value should not be undefined before it appears to enable it to fade-in
- doc['opacity_indexed'] = new List<number>(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)));
+ doc.opacity_indexed = new List<number>(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)));
}
- CollectionFreeFormDocumentView.animFields.forEach(val => (doc[val.key] = ComputedField.MakeInterpolatedNumber(val.key, 'activeFrame', doc, currTimecode, val.val)));
- CollectionFreeFormDocumentView.animStringFields.forEach(val => (doc[val] = ComputedField.MakeInterpolatedString(val, 'activeFrame', doc, currTimecode)));
- CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => (doc[val] = ComputedField.MakeInterpolatedDataField(val, 'activeFrame', doc, currTimecode)));
+ CollectionFreeFormDocumentView.animFields.forEach(val => {
+ doc[val.key] = ComputedField.MakeInterpolatedNumber(val.key, 'activeFrame', doc, currTimecode, val.val);
+ });
+ CollectionFreeFormDocumentView.animStringFields.forEach(val => {
+ doc[val] = ComputedField.MakeInterpolatedString(val, 'activeFrame', doc, currTimecode);
+ });
+ CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => {
+ doc[val] = ComputedField.MakeInterpolatedDataField(val, 'activeFrame', doc, currTimecode);
+ });
const targetDoc = doc; // data fields, like rtf 'text' exist on the data doc, so
- //doc !== targetDoc && (targetDoc.embedContainer = doc.embedContainer); // the computed fields don't see the layout doc -- need to copy the embedContainer to the data doc (HACK!!!) and set the activeFrame on the data doc (HACK!!!)
+ // doc !== targetDoc && (targetDoc.embedContainer = doc.embedContainer); // the computed fields don't see the layout doc -- need to copy the embedContainer to the data doc (HACK!!!) and set the activeFrame on the data doc (HACK!!!)
targetDoc.activeFrame = ComputedField.MakeFunction('this.embedContainer?._currentFrame||0');
targetDoc.dataTransition = 'inherit';
});
@@ -197,22 +237,20 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
const containerDocView = this._props.containerViewPath?.().lastElement();
const screenXf = containerDocView?.screenToContentsTransform();
if (screenXf) {
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
if (topDoc.z) {
const spt = screenXf.inverse().transformPoint(NumCast(topDoc.x), NumCast(topDoc.y));
topDoc.z = 0;
- topDoc.x = spt[0];
- topDoc.y = spt[1];
+ [topDoc.x, topDoc.y] = spt;
this._props.removeDocument?.(topDoc);
this._props.addDocTab(topDoc, OpenWhere.inParentFromScreen);
} else {
const spt = this.screenToLocalTransform().inverse().transformPoint(0, 0);
const fpt = screenXf.transformPoint(spt[0], spt[1]);
topDoc.z = 1;
- topDoc.x = fpt[0];
- topDoc.y = fpt[1];
+ [topDoc.x, topDoc.y] = fpt;
}
- setTimeout(() => SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(topDoc, containerDocView), false), 0);
+ setTimeout(() => DocumentView.SelectView(DocumentView.getDocumentView(topDoc, containerDocView), false), 0);
}
};
@@ -234,11 +272,12 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
// 'inactive' - this is a group child but it is not active
// undefined - this is not activated by a group
isGroupActive = () => {
- if (this.CollectionFreeFormView.isAnyChildContentActive()) return undefined;
+ if (this._props.isAnyChildContentActive()) return undefined;
const backColor = this.BackgroundColor;
const isGroup = this.dataDoc.isGroup && (!backColor || backColor === 'transparent');
return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined;
};
+ localRotation = () => this._props.rotation;
render() {
TraceMobx();
@@ -257,8 +296,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
<div style={{ position: 'absolute', width: this.PanelWidth(), height: this.PanelHeight(), background: 'lightGreen' }} />
) : (
<DocumentView
+ parent={this}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...OmitKeys(this._props,this.WrapperKeys.map(val => val.lower)).omit} // prettier-ignore
DataTransition={this.DataTransition}
+ LocalRotation={this.localRotation}
CollectionFreeFormDocumentView={this.returnThis}
styleProvider={this.styleProvider}
ScreenToLocalTransform={this.screenToLocalTransform}
@@ -271,6 +313,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
);
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function gotoFrame(doc: any, newFrame: any) {
CollectionFreeFormDocumentView.gotoKeyFrame(doc, newFrame);
});
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 9ffdc350d..474d54119 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -2,24 +2,27 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils';
+import { returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
import { RichTextField } from '../../../fields/RichTextField';
-import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types';
-import { DocUtils, Docs } from '../../documents/Documents';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DocCast, NumCast, RTFCast, StrCast, toList } from '../../../fields/Types';
+import { DocUtils } from '../../documents/DocUtils';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
-import { StyleProp } from '../StyleProvider';
+import { PinDocView, PinProps } from '../PinFuncs';
+import { StyleProp } from '../StyleProp';
import './ComparisonBox.scss';
import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
-import { KeyValueBox } from './KeyValueBox';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
-import { PinProps, PresBox } from './trails';
@observer
-export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
+export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(ComparisonBox, fieldKey);
}
@@ -50,13 +53,14 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
@undoBatch
private internalDrop = (e: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
if (dropEvent.complete.docDragData) {
- const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments;
- const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocs, this.Document, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey));
- Doc.SetContainer(droppedDocs.lastElement(), this.dataDoc);
+ const { droppedDocuments } = dropEvent.complete.docDragData;
+ const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocuments, this.Document, (doc: Doc | Doc[]) => this.addDoc(toList(doc).lastElement(), fieldKey));
+ Doc.SetContainer(droppedDocuments.lastElement(), this.dataDoc);
!added && e.preventDefault();
e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
return added;
}
+ return undefined;
};
private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => {
@@ -66,7 +70,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
e,
this.onPointerMove,
emptyFunction,
- action((e, doubleTap) => {
+ action((moveEv, doubleTap) => {
if (doubleTap) {
this._isAnyChildContentActive = true;
if (!this.dataDoc[this.fieldKey + '_1'] && !this.dataDoc[this.fieldKey]) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
@@ -81,7 +85,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
// on click, animate slider movement to the targetWidth
this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth();
setTimeout(
- action(() => (this._animating = '')),
+ action(() => {
+ this._animating = '';
+ }),
200
);
})
@@ -107,8 +113,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
});
if (anchor) {
if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
- /* addAsAnnotation &&*/ this.addDocument(anchor);
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), clippable: true } }, this.Document);
+ /* addAsAnnotation && */ this.addDocument(anchor);
+ PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), clippable: true } }, this.Document);
return anchor;
}
return this.Document;
@@ -135,28 +141,28 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
setupMoveUpEvents(
this,
e,
- e => {
+ moveEv => {
const de = new DragManager.DocumentDragData([DocCast(this.dataDoc[which])], dropActionType.move);
de.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
this.clearDoc(which);
return addDocument(doc);
};
de.canEmbed = true;
- DragManager.StartDocumentDrag([this._closeRef.current!], de, e.clientX, e.clientY);
+ DragManager.StartDocumentDrag([this._closeRef.current!], de, moveEv.clientX, moveEv.clientY);
return true;
},
emptyFunction,
- e => this.clearDoc(which)
+ () => this.clearDoc(which)
);
};
docStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
if (property === StyleProp.PointerEvents) return 'none';
return this._props.styleProvider?.(doc, props, property);
};
- moveDoc1 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true);
- moveDoc2 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true);
- remDoc1 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
- remDoc2 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true);
+ moveDoc1 = (docs: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => toList(docs).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true);
+ moveDoc2 = (docs: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => toList(docs).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true);
+ remDoc1 = (docs: Doc | Doc[]) => toList(docs).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
+ remDoc2 = (docs: Doc | Doc[]) => toList(docs).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true);
/**
* Tests for whether a comparison box slot (ie, before or after) has renderable text content
@@ -164,7 +170,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
* @returns a JSX layout string if a text field is found, othwerise undefined
*/
testForTextFields = (whichSlot: string) => {
- const slotHasText = Doc.Get(this.dataDoc, whichSlot, true) instanceof RichTextField || typeof Doc.Get(this.dataDoc, whichSlot, true) === 'string';
+ const slotData = Doc.Get(this.dataDoc, whichSlot, true);
+ const slotHasText = slotData instanceof RichTextField || typeof slotData === 'string';
const subjectText = RTFCast(this.Document[this.fieldKey])?.Text.trim();
const altText = RTFCast(this.Document[this.fieldKey + '_alternate'])?.Text.trim();
const layoutTemplateString =
@@ -180,9 +187,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
// where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field
// The GPT call will put the "answer" in the second slot of the comparison (eg., text_2)
if (whichSlot.endsWith('2') && !layoutTemplateString?.includes(whichSlot)) {
- var queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ...
- if (queryText && queryText.match(/\(\(.*\)\)/)) {
- KeyValueBox.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt
+ const queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in Doc.setField but it doesn't know about the fieldKey ...
+ if (queryText?.match(/\(\(.*\)\)/)) {
+ Doc.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt
}
}
return layoutTemplateString;
@@ -190,17 +197,15 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
_closeRef = React.createRef<HTMLDivElement>();
render() {
- const clearButton = (which: string) => {
- return (
- <div
- ref={this._closeRef}
- className={`clear-button ${which}`}
- onPointerDown={e => this.closeDown(e, which)} // prevent triggering slider movement in registerSliding
- >
- <FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" />
- </div>
- );
- };
+ const clearButton = (which: string) => (
+ <div
+ ref={this._closeRef}
+ className={`clear-button ${which}`}
+ onPointerDown={e => this.closeDown(e, which)} // prevent triggering slider movement in registerSliding
+ >
+ <FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" />
+ </div>
+ );
/**
* Display the Docs in the before/after fields of the comparison. This also supports a GPT flash card use case
@@ -211,15 +216,16 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
const displayDoc = (whichSlot: string) => {
const whichDoc = DocCast(this.dataDoc[whichSlot]);
const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
- const layoutTemplateString = targetDoc ? '' : this.testForTextFields(whichSlot);
- return targetDoc || layoutTemplateString ? (
+ const layoutString = targetDoc ? '' : this.testForTextFields(whichSlot);
+ return targetDoc || layoutString ? (
<>
<DocumentView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
- ignoreUsePath={layoutTemplateString ? true : undefined}
+ ignoreUsePath={layoutString ? true : undefined}
renderDepth={this.props.renderDepth + 1}
- LayoutTemplateString={layoutTemplateString}
- Document={layoutTemplateString ? this.Document : targetDoc}
+ LayoutTemplateString={layoutString}
+ Document={layoutString ? this.Document : targetDoc}
containerViewPath={this.DocumentView?.().docViewPath}
moveDocument={whichSlot.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
removeDocument={whichSlot.endsWith('1') ? this.remDoc1 : this.remDoc2}
@@ -229,10 +235,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
isDocumentActive={returnFalse}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
styleProvider={this._isAnyChildContentActive ? this._props.styleProvider : this.docStyleProvider}
- hideLinkButton={true}
+ hideLinkButton
pointerEvents={this._isAnyChildContentActive ? undefined : returnNone}
/>
- {layoutTemplateString ? null : clearButton(whichSlot)}
+ {layoutString ? null : clearButton(whichSlot)}
</> // placeholder image if doc is missing
) : (
<div className="placeholder">
@@ -240,13 +246,11 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
</div>
);
};
- const displayBox = (which: string, index: number, cover: number) => {
- return (
- <div className={`${index === 0 ? 'before' : 'after'}Box-cont`} key={which} style={{ width: this._props.PanelWidth() }} onPointerDown={e => this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}>
- {displayDoc(which)}
- </div>
- );
- };
+ const displayBox = (which: string, index: number, cover: number) => (
+ <div className={`${index === 0 ? 'before' : 'after'}Box-cont`} key={which} style={{ width: this._props.PanelWidth() }} onPointerDown={e => this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}>
+ {displayDoc(which)}
+ </div>
+ );
return (
<div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
@@ -269,3 +273,18 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
);
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.COMPARISON, {
+ data: '',
+ layout: { view: ComparisonBox, dataField: 'data' },
+ options: {
+ acl: '',
+ backgroundColor: 'gray',
+ dropAction: dropActionType.move,
+ waitForDoubleClickToClick: 'always',
+ _layout_reflowHorizontal: true,
+ _layout_reflowVertical: true,
+ _layout_nativeDimEditable: true,
+ systemIcon: 'BsLayoutSplit',
+ },
+});
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 60c5fdba2..15187b4e4 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,34 +1,37 @@
+/* eslint-disable react/jsx-props-no-spreading */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Checkbox } from '@mui/material';
import { Colors, Toggle, ToggleType, Type } from 'browndash-components';
import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils';
+import { returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc';
import { InkTool } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
-import { listSpec } from '../../../../fields/Schema';
import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { CsvField } from '../../../../fields/URLField';
import { TraceMobx } from '../../../../fields/util';
-import { DocUtils, Docs } from '../../../documents/Documents';
-import { DocumentManager } from '../../../util/DocumentManager';
+import { DocUtils } from '../../../documents/DocUtils';
+import { DocumentType } from '../../../documents/DocumentTypes';
+import { Docs } from '../../../documents/Documents';
import { UndoManager, undoable } from '../../../util/UndoManager';
+import { ContextMenu } from '../../ContextMenu';
import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
import { MarqueeAnnotator } from '../../MarqueeAnnotator';
+import { PinProps } from '../../PinFuncs';
import { SidebarAnnos } from '../../SidebarAnnos';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { GPTPopup, GPTPopupMode } from '../../pdf/GPTPopup/GPTPopup';
import { DocumentView } from '../DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
-import { PinProps } from '../trails';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { FocusViewOptions } from '../FocusViewOptions';
import './DataVizBox.scss';
import { Histogram } from './components/Histogram';
import { LineChart } from './components/LineChart';
import { PieChart } from './components/PieChart';
import { TableBox } from './components/TableBox';
-import { Checkbox } from '@mui/material';
-import { ContextMenu } from '../../ContextMenu';
export enum DataVizView {
TABLE = 'table',
@@ -64,9 +67,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
setupMoveUpEvents(
this,
e,
- action(e => {
+ action(moveEv => {
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
- this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
+ this._marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]);
return true;
}),
returnFalse,
@@ -97,7 +100,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
// all CSV records in the dataset (that aren't an empty row)
@computed.struct get records() {
- var records = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
+ const records = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
return records?.filter(record => Object.keys(record).some(key => record[key])) ?? [];
}
@@ -112,11 +115,15 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
@computed.struct get axes() {
return StrListCast(this.layoutDoc._dataViz_axes);
}
- selectAxes = (axes: string[]) => (this.layoutDoc._dataViz_axes = new List<string>(axes));
+ selectAxes = (axes: string[]) => {
+ this.layoutDoc._dataViz_axes = new List<string>(axes);
+ };
@computed.struct get titleCol() {
return StrCast(this.layoutDoc._dataViz_titleCol);
}
- selectTitleCol = (titleCol: string) => (this.layoutDoc._dataViz_titleCol = titleCol);
+ selectTitleCol = (titleCol: string) => {
+ this.layoutDoc._dataViz_titleCol = titleCol;
+ };
@action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
restoreView = (data: Doc) => {
@@ -126,7 +133,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
this.layoutDoc.dataViz_histogram_barColors = Field.Copy(data.dataViz_histogram_barColors);
this.layoutDoc.dataViz_histogram_defaultColor = data.dataViz_histogram_defaultColor;
this.layoutDoc.dataViz_pie_sliceColors = Field.Copy(data.dataViz_pie_sliceColors);
- Object.keys(this.layoutDoc).map(key => {
+ Object.keys(this.layoutDoc).forEach(key => {
if (key.startsWith('dataViz_histogram_title') || key.startsWith('dataViz_lineChart_title') || key.startsWith('dataViz_pieChart_title')) {
this.layoutDoc['_' + key] = data[key];
}
@@ -152,7 +159,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
annotationOn: this.Document,
// when we clear selection -> we should have it so chartBox getAnchor returns undefined
// this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker)
- /*put in some options*/
+ /* put in some options */
});
anchor.config_dataViz = this.dataVizView;
anchor.config_dataVizAxes = this.axes.length ? new List<string>(this.axes) : undefined;
@@ -160,24 +167,21 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
anchor.dataViz_histogram_barColors = Field.Copy(this.layoutDoc.dataViz_histogram_barColors);
anchor.dataViz_histogram_defaultColor = this.layoutDoc.dataViz_histogram_defaultColor;
anchor.dataViz_pie_sliceColors = Field.Copy(this.layoutDoc.dataViz_pie_sliceColors);
- Object.keys(this.layoutDoc).map(key => {
+ Object.keys(this.layoutDoc).forEach(key => {
if (key.startsWith('dataViz_histogram_title') || key.startsWith('dataViz_lineChart_title') || key.startsWith('dataViz_pieChart_title')) {
anchor[key] = this.layoutDoc[key];
}
});
this.addDocument(anchor);
- //addAsAnnotation && this.addDocument(anchor);
+ // addAsAnnotation && this.addDocument(anchor);
return anchor;
};
createNoteAnnotation = () => {
- const createFunc = undoable(
- action(() => {
- const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false), ['latitude', 'longitude', '-linkedTo']);
- }),
- 'create note annotation'
- );
+ const createFunc = undoable(() => {
+ this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false), ['latitude', 'longitude', '-linkedTo']);
+ }, 'create note annotation');
if (!this.layoutDoc.layout_showSidebar) {
this.toggleSidebar();
setTimeout(createFunc);
@@ -194,7 +198,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
this.layoutDoc._width = this.layoutDoc._layout_showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
};
@computed get SidebarShown() {
- return this.layoutDoc._layout_showSidebar ? true : false;
+ return !!this.layoutDoc._layout_showSidebar;
}
@computed get sidebarHandle() {
return (
@@ -208,7 +212,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
}}
onPointerDown={this.sidebarBtnDown}>
- <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
</div>
);
}
@@ -220,7 +224,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
setupMoveUpEvents(
this,
e,
- (e, down, delta) =>
+ (moveEv, down, delta) =>
runInAction(() => {
const localDelta = this._props
.ScreenToLocalTransform()
@@ -248,7 +252,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
options.didMove = true;
this.toggleSidebar();
}
- return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ return new Promise<Opt<DocumentView>>(res => {
+ DocumentView.addViewRenderedCb(doc, dv => res(dv));
+ });
};
@computed get sidebarWidthPercent() {
return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
@@ -268,32 +274,32 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
this._disposers.datavis = reaction(
() => {
- if (this.layoutDoc.dataViz_schemaLive == undefined) this.layoutDoc.dataViz_schemaLive = true;
+ if (this.layoutDoc.dataViz_schemaLive === undefined) this.layoutDoc.dataViz_schemaLive = true;
const getFrom = DocCast(this.layoutDoc.dataViz_asSchema);
- const keys = Cast(getFrom?.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text');
- if (!keys) return;
- const children = DocListCast(getFrom[Doc.LayoutFieldKey(getFrom)]);
- var current: { [key: string]: string }[] = [];
+ if (!getFrom?.schema_columnKeys) return undefined;
+ const keys = StrListCast(getFrom?.schema_columnKeys).filter(key => key !== 'text');
+ const children = DocListCast(getFrom?.[Doc.LayoutFieldKey(getFrom)]);
+ const current: { [key: string]: string }[] = [];
children
.filter(child => child)
.forEach(child => {
const row: { [key: string]: string } = {};
keys.forEach(key => {
- var cell = child[key];
- if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
+ let cell = child[key];
+ if (cell && (cell as string)) cell = cell.toString().replace(/,/g, '');
row[key] = StrCast(cell);
});
current.push(row);
});
if (!this.layoutDoc._dataViz_schemaOG) {
// makes a copy of the original table for the "live" toggle
- let csvRows = [];
+ const csvRows = [];
csvRows.push(keys.join(','));
for (let i = 0; i < children.length - 1; i++) {
- let eachRow = [];
+ const eachRow = [];
for (let j = 0; j < keys.length; j++) {
- var cell = children[i][keys[j]];
- if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
+ let cell = children[i][keys[j]];
+ if (cell && (cell as string)) cell = cell.toString().replace(/,/g, '');
eachRow.push(cell);
}
csvRows.push(eachRow);
@@ -307,19 +313,19 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
}
const ogDoc = this.layoutDoc._dataViz_schemaOG as Doc;
const ogHref = CsvCast(ogDoc[this.fieldKey]) ? CsvCast(ogDoc[this.fieldKey]).url.href : undefined;
- const href = CsvCast(this.Document[this.fieldKey]).url.href;
+ const { href } = CsvCast(this.Document[this.fieldKey]).url;
if (ogHref && !DataVizBox.datasetSchemaOG.has(href)) {
// sets original dataset to the var
const lastRow = current.pop();
DataVizBox.datasetSchemaOG.set(href, current);
current.push(lastRow!);
- fetch('/csvData?uri=' + ogHref).then(res => res.json().then(action(res => !res.errno && DataVizBox.datasetSchemaOG.set(href, res))));
+ fetch('/csvData?uri=' + ogHref).then(res => res.json().then(action(jsonRes => !jsonRes.errno && DataVizBox.datasetSchemaOG.set(href, jsonRes))));
}
return current;
},
current => {
if (current) {
- const href = CsvCast(this.Document[this.fieldKey]).url.href;
+ const { href } = CsvCast(this.Document[this.fieldKey]).url;
if (this.layoutDoc.dataViz_schemaLive) DataVizBox.dataset.set(href, current);
else DataVizBox.dataset.set(href, DataVizBox.datasetSchemaOG.get(href)!);
}
@@ -331,8 +337,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
fetchData = () => {
if (!this.Document.dataViz_asSchema) {
DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests
- fetch('/csvData?uri=' + this.dataUrl?.url.href) //
- .then(res => res.json().then(action(res => !res.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, res))));
+ fetch('/csvData?uri=' + (this.dataUrl?.url.href ?? '')) //
+ .then(res => res.json().then(action(jsonRes => !jsonRes.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, jsonRes))));
}
};
@@ -345,7 +351,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
records: this.records,
axes: this.axes,
titleCol: this.titleCol,
- //width: this.SidebarShown? this._props.PanelWidth()*.9/1.2: this._props.PanelWidth() * 0.9,
+ // width: this.SidebarShown? this._props.PanelWidth()*.9/1.2: this._props.PanelWidth() * 0.9,
height: (this._props.PanelHeight() / scale - 32) /* height of 'change view' button */ * 0.9,
width: ((this._props.PanelWidth() - this.sidebarWidth()) / scale) * 0.9,
margin: { top: 10, right: 25, bottom: 75, left: 45 },
@@ -353,11 +359,13 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
if (!this.records.length) return 'no data/visualization';
switch (this.dataVizView) {
case DataVizView.TABLE: return <TableBox {...sharedProps} docView={this.DocumentView} selectAxes={this.selectAxes} selectTitleCol={this.selectTitleCol}/>;
- case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} vizBox={this} />;
- case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
- case DataVizView.PIECHART: return <PieChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)}
+ case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} vizBox={this} />;
+ case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} />;
+ case DataVizView.PIECHART: return <PieChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}}
margin={{ top: 10, right: 15, bottom: 15, left: 15 }} />;
+ default:
} // prettier-ignore
+ return null;
}
@action
@@ -369,10 +377,13 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
this._marqueeing = [e.clientX, e.clientY];
const target = e.target as any;
if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) {
+ /* empty */
} else {
// if textLayer is hit, then we select text instead of using a marquee so clear out the marquee.
setTimeout(
- action(() => (this._marqueeing = undefined)),
+ action(() => {
+ this._marqueeing = undefined;
+ }),
100
); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
@@ -402,34 +413,44 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
@action
changeLiveSchemaCheckbox = () => {
- this.layoutDoc.dataViz_schemaLive = !this.layoutDoc.dataViz_schemaLive
- }
+ this.layoutDoc.dataViz_schemaLive = !this.layoutDoc.dataViz_schemaLive;
+ };
- specificContextMenu = (e: React.MouseEvent): void => {
+ specificContextMenu = (): void => {
const cm = ContextMenu.Instance;
const options = cm.findByDescription('Options...');
const optionItems = options && 'subitems' in options ? options.subitems : [];
optionItems.push({ description: `Analyze with AI`, event: () => this.askGPT(), icon: 'lightbulb' });
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
- }
-
-
+ };
askGPT = action(async () => {
GPTPopup.Instance.setSidebarId('data_sidebar');
GPTPopup.Instance.addDoc = this.sidebarAddDocument;
- GPTPopup.Instance.setDataJson("");
+ GPTPopup.Instance.setDataJson('');
GPTPopup.Instance.setMode(GPTPopupMode.DATA);
- let data = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
- let input = JSON.stringify(data);
+ const data = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
+ const input = JSON.stringify(data);
GPTPopup.Instance.setDataJson(input);
GPTPopup.Instance.generateDataAnalysis();
});
render() {
const scale = this._props.NativeDimScaling?.() || 1;
+ const toggleBtn = (name: string, type: DataVizView) => (
+ <Toggle
+ text={name}
+ toggleType={ToggleType.BUTTON}
+ type={Type.SEC}
+ color="black"
+ onClick={() => {
+ this.layoutDoc._dataViz = type;
+ }}
+ toggleStatus={this.layoutDoc._dataViz === type}
+ />
+ );
return !this.records.length ? (
// displays how to get data into the DataVizBox if its empty
- <div className="start-message">To create a DataViz box, either import / drag a CSV file into your canvas or copy a data table and use the command 'ctrl + p' to bring the data table to your canvas.</div>
+ <div className="start-message">To create a DataViz box, either import / drag a CSV file into your canvas or copy a data table and use the command (ctrl + p) to bring the data table to your canvas.</div>
) : (
<div
className="dataViz-box"
@@ -445,19 +466,19 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
onWheel={e => e.stopPropagation()}
ref={this._mainCont}>
<div className="datatype-button">
- <Toggle text={' TABLE '} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.TABLE)} toggleStatus={this.layoutDoc._dataViz === DataVizView.TABLE} />
- <Toggle text={'LINECHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.LINECHART)} toggleStatus={this.layoutDoc._dataViz === DataVizView.LINECHART} />
- <Toggle text={'HISTOGRAM'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.HISTOGRAM)} toggleStatus={this.layoutDoc._dataViz === DataVizView.HISTOGRAM} />
- <Toggle text={'PIE CHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.PIECHART)} toggleStatus={this.layoutDoc._dataViz == -DataVizView.PIECHART} />
+ {toggleBtn(' TABLE ', DataVizView.TABLE)}
+ {toggleBtn('LINECHART', DataVizView.LINECHART)}
+ {toggleBtn('HISTOGRAM', DataVizView.HISTOGRAM)}
+ {toggleBtn('PIE CHART', DataVizView.PIECHART)}
</div>
- {(this.layoutDoc && this.layoutDoc.dataViz_asSchema)?(
- <div className={'displaySchemaLive'}>
- <div className={'liveSchema-checkBox'} style={{ width: this._props.width }}>
- <Checkbox color="primary" onChange={this.changeLiveSchemaCheckbox} checked={this.layoutDoc.dataViz_schemaLive as boolean} />
- Display Live Updates to Canvas
+ {this.layoutDoc && this.layoutDoc.dataViz_asSchema ? (
+ <div className="displaySchemaLive">
+ <div className="liveSchema-checkBox" style={{ width: this._props.width }}>
+ <Checkbox color="primary" onChange={this.changeLiveSchemaCheckbox} checked={this.layoutDoc.dataViz_schemaLive as boolean} />
+ Display Live Updates to Canvas
+ </div>
</div>
- </div>
) : null}
{this.renderVizView}
@@ -470,7 +491,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
Document={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
- usePanelWidth={true}
+ usePanelWidth
showSidebar={this.SidebarShown}
nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
@@ -504,3 +525,18 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
);
}
}
+Docs.Prototypes.TemplateMap.set(DocumentType.DATAVIZ, {
+ layout: { view: DataVizBox, dataField: 'data' },
+ options: {
+ acl: '',
+ dataViz_title: '',
+ dataViz_line: '',
+ dataViz_pie: '',
+ dataViz_histogram: '',
+ dataViz: 'table',
+ _layout_fitWidth: true,
+ _layout_reflowHorizontal: true,
+ _layout_reflowVertical: true,
+ _layout_nativeDimEditable: true,
+ },
+});
diff --git a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
index 24023077f..60bc8df18 100644
--- a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
+++ b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
@@ -1,9 +1,12 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/alt-text */
import { IconButton } from 'browndash-components';
import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { CgClose } from 'react-icons/cg';
-import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils';
+import { ClientUtils, setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
import { Doc } from '../../../../fields/Doc';
import { StrCast } from '../../../../fields/Types';
import { DragManager } from '../../../util/DragManager';
@@ -14,56 +17,52 @@ interface SchemaCSVPopUpProps {}
@observer
export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
+ // eslint-disable-next-line no-use-before-define
static Instance: SchemaCSVPopUp;
- @observable
- public dataVizDoc: Doc | undefined = undefined;
+ @observable public dataVizDoc: Doc | undefined = undefined;
+ @observable public view: DocumentView | undefined = undefined;
+ @observable public target: Doc | undefined = undefined;
+ @observable public visible: boolean = false;
+
+ constructor(props: SchemaCSVPopUpProps) {
+ super(props);
+ makeObservable(this);
+ SchemaCSVPopUp.Instance = this;
+ }
+
@action
public setDataVizDoc = (doc: Doc) => {
this.dataVizDoc = doc;
};
- @observable
- public view: DocumentView | undefined = undefined;
@action
public setView = (docView: DocumentView) => {
this.view = docView;
};
- @observable
- public target: Doc | undefined = undefined;
@action
public setTarget = (doc: Doc) => {
this.target = doc;
};
- @observable
- public visible: boolean = false;
@action
public setVisible = (vis: boolean) => {
this.visible = vis;
};
- constructor(props: SchemaCSVPopUpProps) {
- super(props);
- makeObservable(this);
- SchemaCSVPopUp.Instance = this;
- }
-
- dataBox = () => {
- return (
- <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
- {this.heading('Schema Table as Data Visualization Doc')}
- <div className="image-content-wrapper">
- <div className="img-wrapper">
- <div className="img-container" onPointerDown={e => this.drag(e)}>
- <img width={150} height={150} src={'/assets/dataVizBox.png'} />
- </div>
+ dataBox = () => (
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
+ {this.heading('Schema Table as Data Visualization Doc')}
+ <div className="image-content-wrapper">
+ <div className="img-wrapper">
+ <div className="img-container" onPointerDown={e => this.drag(e)}>
+ <img width={150} height={150} src="/assets/dataVizBox.png" />
</div>
</div>
</div>
- );
- };
+ </div>
+ );
heading = (headingText: string) => (
<div className="summary-heading">
@@ -78,24 +77,22 @@ export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
setupMoveUpEvents(
{},
e,
- e => {
+ moveEv => {
const sourceAnchorCreator = () => this.dataVizDoc!;
- const targetCreator = (annotationOn: Doc | undefined) => {
+ const targetCreator = () => {
const embedding = Doc.MakeEmbedding(this.dataVizDoc!);
return embedding;
};
- if (this.view && sourceAnchorCreator && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
- DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this.view, sourceAnchorCreator, targetCreator), downX, downY, {
- dragComplete: e => {
- this.setVisible(false);
- },
+ if (this.view && sourceAnchorCreator && !ClientUtils.isClick(moveEv.clientX, moveEv.clientY, downX, downY, Date.now())) {
+ DragManager.StartAnchorAnnoDrag(moveEv.target instanceof HTMLElement ? [moveEv.target] : [], new DragManager.AnchorAnnoDragData(this.view, sourceAnchorCreator, targetCreator), downX, downY, {
+ dragComplete: () => this.setVisible(false),
});
return true;
}
return false;
},
emptyFunction,
- action(e => {})
+ action(() => {})
);
};
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index 6672603f3..79b3e9541 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -12,7 +12,7 @@ import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
import { Docs } from '../../../../documents/Documents';
import { undoable } from '../../../../util/UndoManager';
import { ObservableReactComponent } from '../../../ObservableReactComponent';
-import { PinProps, PresBox } from '../../trails';
+import { PinProps, PinDocView } from '../../../PinFuncs';
import { scaleCreatorNumerical, yAxisCreator } from '../utils/D3Utils';
import './Chart.scss';
@@ -63,14 +63,13 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
@computed get _histogramData() {
if (this._props.axes.length < 1) return [];
if (this._props.axes.length < 2) {
- var ax0 = this._props.axes[0];
- if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])){
+ const ax0 = this._props.axes[0];
+ if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])) {
this.numericalXData = true;
}
return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]] }));
}
- var ax0 = this._props.axes[0];
- var ax1 = this._props.axes[1];
+ const [ax0, ax1] = this._props.axes;
if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])) {
this.numericalXData = true;
}
@@ -81,11 +80,11 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
}
@computed get defaultGraphTitle() {
- var ax0 = this._props.axes[0];
- var ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
+ const [ax0, ax1] = this._props.axes;
if (this._props.axes.length < 2 || !ax1 || !/\d/.test(this._props.records[0][ax1]) || !this.numericalYData) {
return ax0 + ' Histogram';
- } else return ax0 + ' by ' + ax1 + ' Histogram';
+ }
+ return ax0 + ' by ' + ax1 + ' Histogram';
}
@computed get parentViz() {
@@ -111,14 +110,13 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
);
}
- @action
- restoreView = (data: Doc) => {};
+ restoreView = () => {};
// create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc)
getAnchor = (pinProps?: PinProps) => {
const anchor = Docs.Create.ConfigDocument({
title: 'histogram doc selection' + this._currSelected,
});
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
+ PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
return anchor;
};
@@ -132,36 +130,36 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
// cleans data by converting numerical data to numbers and taking out empty cells
data = (dataSet: any) => {
- var validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
+ const validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key])));
const field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined;
return !field
? []
: validData.map((d: { [x: string]: any }) =>
!this.numericalXData //
? d[field]
- : +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')
+ : +d[field!].replace(/\$/g, '').replace(/%/g, '').replace(/</g, '')
);
};
// outlines the bar selected / hovered over
highlightSelectedBar = (changeSelectedVariables: boolean, svg: any, eachRectWidth: any, pointerX: any, xAxisTitle: any, yAxisTitle: any, histDataSet: any) => {
- var sameAsCurrent: boolean;
- var barCounter = -1;
+ let sameAsCurrent: boolean;
+ let barCounter = -1;
const selected = svg.selectAll('.histogram-bar').filter((d: any) => {
barCounter++; // uses the order of bars and width of each bar to find which one the pointer is over
if (barCounter * eachRectWidth <= pointerX && pointerX <= (barCounter + 1) * eachRectWidth) {
- var showSelected = this.numericalYData
- ? this._histogramData.filter((data: { [x: string]: any }) => StrCast(data[xAxisTitle]).replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') == d[0])[0]
- : histDataSet.filter((data: { [x: string]: any }) => data[xAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') == d[0])[0];
+ let showSelected = this.numericalYData
+ ? this._histogramData.filter((data: { [x: string]: any }) => StrCast(data[xAxisTitle]).replace(/$/g, '').replace(/%/g, '').replace(/</g, '') === d[0])[0]
+ : histDataSet.filter((data: { [x: string]: any }) => data[xAxisTitle].replace(/$/g, '').replace(/%/g, '').replace(/</g, '') === d[0])[0];
if (this.numericalXData) {
// calculating frequency
- if (d[0] && d[1] && d[0] != d[1]) {
+ if (d[0] && d[1] && d[0] !== d[1]) {
showSelected = { [xAxisTitle]: d3.min(d) + ' to ' + d3.max(d), frequency: d.length };
} else if (!this.numericalYData) showSelected = { [xAxisTitle]: showSelected[xAxisTitle], frequency: d.length };
}
if (changeSelectedVariables) {
// for when a bar is selected - not just hovered over
- sameAsCurrent = this._currSelected ? showSelected[xAxisTitle] == this._currSelected![xAxisTitle] && showSelected[yAxisTitle] == this._currSelected![yAxisTitle] : false;
+ sameAsCurrent = this._currSelected ? showSelected[xAxisTitle] === this._currSelected![xAxisTitle] && showSelected[yAxisTitle] === this._currSelected![yAxisTitle] : false;
this._currSelected = sameAsCurrent ? undefined : showSelected;
this.selectedData = sameAsCurrent ? undefined : d;
} else this.hoverOverData = d;
@@ -184,16 +182,16 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
const xAxisTitle = Object.keys(dataSet[0])[0];
const yAxisTitle = this.numericalYData ? Object.keys(dataSet[0])[1] : 'frequency';
const uniqueArr: unknown[] = [...new Set(data)];
- var numBins = this.numericalXData && Number.isInteger(data[0]) ? this.rangeVals.xMax! - this.rangeVals.xMin! : uniqueArr.length;
- var translateXAxis = !this.numericalXData || numBins < this.maxBins ? width / (numBins + 1) / 2 : 0;
+ let numBins = this.numericalXData && Number.isInteger(data[0]) ? this.rangeVals.xMax! - this.rangeVals.xMin! : uniqueArr.length;
+ let translateXAxis = !this.numericalXData || numBins < this.maxBins ? width / (numBins + 1) / 2 : 0;
if (numBins > this.maxBins) numBins = this.maxBins;
const startingPoint = this.numericalXData ? this.rangeVals.xMin! : 0;
const endingPoint = this.numericalXData ? this.rangeVals.xMax! : numBins;
// converts data into Objects
- var histDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
+ let histDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key])));
if (!this.numericalXData) {
- var histStringDataSet: { [x: string]: unknown }[] = [];
+ const histStringDataSet: { [x: string]: unknown }[] = [];
if (this.numericalYData) {
for (let i = 0; i < dataSet.length; i++) {
histStringDataSet.push({ [yAxisTitle]: dataSet[i][yAxisTitle], [xAxisTitle]: dataSet[i][xAxisTitle] });
@@ -203,15 +201,15 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
histStringDataSet.push({ [yAxisTitle]: 0, [xAxisTitle]: uniqueArr[i] });
}
for (let i = 0; i < data.length; i++) {
- let barData = histStringDataSet.filter(each => each[xAxisTitle] == data[i]);
- histStringDataSet.filter(each => each[xAxisTitle] == data[i])[0][yAxisTitle] = Number(barData[0][yAxisTitle]) + 1;
+ const barData = histStringDataSet.filter(each => each[xAxisTitle] === data[i]);
+ histStringDataSet.filter(each => each[xAxisTitle] === data[i])[0][yAxisTitle] = Number(barData[0][yAxisTitle]) + 1;
}
}
histDataSet = histStringDataSet;
}
// initial graph and binning data for histogram
- var svg = (this._histogramSvg = d3
+ const svg = (this._histogramSvg = d3
.select(this._histogramRef.current)
.append('svg')
.attr('class', 'graph')
@@ -219,23 +217,21 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
.attr('height', height + this._props.margin.top + this._props.margin.bottom)
.append('g')
.attr('transform', 'translate(' + this._props.margin.left + ',' + this._props.margin.top + ')'));
- var x = d3
+ let x = d3
.scaleLinear()
.domain(this.numericalXData ? [startingPoint!, endingPoint!] : [0, numBins])
.range([0, width]);
- var histogram = d3
+ const histogram = d3
.histogram()
- .value(function (d) {
- return d;
- })
+ .value(d => d)
.domain([startingPoint!, endingPoint!])
.thresholds(x.ticks(numBins));
- var bins = histogram(data);
- var eachRectWidth = width / bins.length;
- var graphStartingPoint = bins[0].x1 && bins[1] ? bins[0].x1! - (bins[1].x1! - bins[1].x0!) : 0;
+ const bins = histogram(data);
+ let eachRectWidth = width / bins.length;
+ const graphStartingPoint = bins[0].x1 && bins[1] ? bins[0].x1! - (bins[1].x1! - bins[1].x0!) : 0;
bins[0].x0 = graphStartingPoint;
x = x.domain([graphStartingPoint, endingPoint]).range([0, Number.isInteger(this.rangeVals.xMin!) ? width - eachRectWidth : width]);
- var xAxis;
+ let xAxis;
// more calculations based on bins
// x-axis
@@ -244,9 +240,9 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
// uniqueArr.sort()
histDataSet.sort();
for (let i = 0; i < data.length; i++) {
- var index = 0;
+ let index = 0;
for (let j = 0; j < uniqueArr.length; j++) {
- if (uniqueArr[j] == data[i]) {
+ if (uniqueArr[j] === data[i]) {
index = j;
}
}
@@ -254,7 +250,9 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
}
bins.pop();
eachRectWidth = width / bins.length;
- bins.forEach(d => (d.x0 = d.x0!));
+ bins.forEach(d => {
+ d.x0 = d.x0!;
+ });
xAxis = d3
.axisBottom(x)
.ticks(bins.length > 1 ? bins.length - 1 : 1)
@@ -264,12 +262,12 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
x.domain([0, bins.length - 1]);
translateXAxis = eachRectWidth / 2;
} else {
- var allSame = true;
- for (var i = 0; i < bins.length; i++) {
+ let allSame = true;
+ for (let i = 0; i < bins.length; i++) {
if (bins[i] && bins[i][0]) {
- var compare = bins[i][0];
+ const compare = bins[i][0];
for (let j = 1; j < bins[i].length; j++) {
- if (bins[i][j] != compare) allSame = false;
+ if (bins[i][j] !== compare) allSame = false;
}
}
}
@@ -278,8 +276,8 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
eachRectWidth = width / bins.length;
} else {
eachRectWidth = width / (bins.length + 1);
- var tickDiff = bins.length >= 2 ? bins[bins.length - 2].x1! - bins[bins.length - 2].x0! : 0;
- var curDomain = x.domain();
+ const tickDiff = bins.length >= 2 ? bins[bins.length - 2].x1! - bins[bins.length - 2].x0! : 0;
+ const curDomain = x.domain();
x.domain([curDomain[0], curDomain[0] + tickDiff * bins.length]);
}
@@ -287,16 +285,13 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
x.range([0, width - eachRectWidth]);
}
// y-axis
- const maxFrequency = this.numericalYData
- ? d3.max(histDataSet, function (d: any) {
- return d[yAxisTitle] ? Number(d[yAxisTitle]!.replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')) : 0;
- })
- : d3.max(bins, function (d) {
- return d.length;
- });
- var y = d3.scaleLinear().range([height, 0]);
+ const maxFrequency = this.numericalYData ?
+ d3.max(histDataSet, (d: any) => (d[yAxisTitle] ? Number(d[yAxisTitle]!.replace(/\$/g, '')
+ .replace(/%/g, '').replace(/</g, '')) : 0)) :
+ d3.max(bins, d => d.length); // prettier-ignore
+ const y = d3.scaleLinear().range([height, 0]);
y.domain([0, +maxFrequency!]);
- var yAxis = d3.axisLeft(y).ticks(maxFrequency!);
+ const yAxis = d3.axisLeft(y).ticks(maxFrequency!);
if (this.numericalYData) {
const yScale = scaleCreatorNumerical(0, Number(maxFrequency), height, 0);
yAxisCreator(svg.append('g'), width, yScale);
@@ -311,18 +306,17 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
const onPointClick = action((e: any) => this.highlightSelectedBar(true, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet));
const onHover = action((e: any) => {
this.highlightSelectedBar(false, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet);
+ // eslint-disable-next-line no-use-before-define
updateHighlights();
});
- const mouseOut = action((e: any) => {
+ const mouseOut = action(() => {
this.hoverOverData = undefined;
+ // eslint-disable-next-line no-use-before-define
updateHighlights();
});
const updateHighlights = () => {
- const hoverOverBar = this.hoverOverData;
- const selectedData = this.selectedData;
- svg.selectAll('rect').attr('class', function (d: any) {
- return (hoverOverBar && hoverOverBar[0] == d[0]) || (selectedData && selectedData[0] == d[0]) ? 'histogram-bar hover' : 'histogram-bar';
- });
+ const { hoverOverData: hoverOverBar, selectedData } = this;
+ svg.selectAll('rect').attr('class', (d: any) => ((hoverOverBar && hoverOverBar[0] === d[0]) || (selectedData && selectedData[0] === d[0]) ? 'histogram-bar hover' : 'histogram-bar'));
};
svg.on('click', onPointClick).on('mouseover', onHover).on('mouseout', mouseOut);
@@ -332,7 +326,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
.style('text-anchor', 'middle')
.text(xAxisTitle);
svg.append('text')
- .attr('transform', 'rotate(-90)' + ' ' + 'translate( 0, ' + -10 + ')')
+ .attr('transform', 'rotate(-90) translate( 0, ' + -10 + ')')
.attr('x', -(height / 2))
.attr('y', -20)
.style('text-anchor', 'middle')
@@ -340,7 +334,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
d3.format('.0f');
// draw bars
- var selected = this.selectedData;
+ const selected = this.selectedData;
svg.selectAll('rect')
.data(bins)
.enter()
@@ -348,49 +342,34 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
.attr(
'transform',
this.numericalYData
- ? function (d) {
- const eachData = histDataSet.filter((data: { [x: string]: number }) => {
- return data[xAxisTitle] == d[0];
- });
- const length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
+ ? d => {
+ const eachData = histDataSet.filter((hData: { [x: string]: number }) => hData[xAxisTitle] === d[0]);
+ const length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/%/g, '').replace(/</g, '') : 0;
return 'translate(' + x(d.x0!) + ',' + y(length) + ')';
}
- : function (d) {
- return 'translate(' + x(d.x0!) + ',' + y(d.length) + ')';
- }
+ : d => 'translate(' + x(d.x0!) + ',' + y(d.length) + ')'
)
.attr(
'height',
this.numericalYData
- ? function (d) {
- const eachData = histDataSet.filter((data: { [x: string]: number }) => {
- return data[xAxisTitle] == d[0];
- });
- const length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
+ ? d => {
+ const eachData = histDataSet.filter((hData: { [x: string]: number }) => hData[xAxisTitle] === d[0]);
+ const length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/%/g, '').replace(/</g, '') : 0;
return height - y(length);
}
- : function (d) {
- return height - y(d.length);
- }
+ : d => height - y(d.length)
)
.attr('width', eachRectWidth)
- .attr(
- 'class',
- selected
- ? function (d) {
- return selected && selected[0] === d[0] ? 'histogram-bar hover' : 'histogram-bar';
- }
- : function (d) {
- return 'histogram-bar';
- }
- )
+ .attr('class', selected ? d => (selected && selected[0] === d[0] ? 'histogram-bar hover' : 'histogram-bar') : () => 'histogram-bar')
.attr('fill', d => {
- var barColor;
+ let barColor;
const barColors = StrListCast(this._props.layoutDoc.dataViz_histogram_barColors).map(each => each.split('::'));
barColors.forEach(each => {
- if (d[0] && d[0].toString() && each[0] == d[0].toString()) barColor = each[1];
+ // eslint-disable-next-line prefer-destructuring
+ if (d[0] && d[0].toString() && each[0] === d[0].toString()) barColor = each[1];
else {
const range = StrCast(each[0]).split(' to ');
+ // eslint-disable-next-line prefer-destructuring
if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1];
}
});
@@ -400,7 +379,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
@action changeSelectedColor = (color: string) => {
this.curBarSelected.attr('fill', color);
- const barName = StrCast(this._currSelected[this._props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+ const barName = StrCast(this._currSelected[this._props.axes[0]].replace(/\$/g, '').replace(/%/g, '').replace(/</g, ''));
const barColors = Cast(this._props.layoutDoc.dataViz_histogram_barColors, listSpec('string'), null);
barColors.forEach(each => each.split('::')[0] === barName && barColors.splice(barColors.indexOf(each), 1));
@@ -409,22 +388,24 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
@action eraseSelectedColor = () => {
this.curBarSelected.attr('fill', this._props.layoutDoc.dataViz_histogram_defaultColor);
- const barName = StrCast(this._currSelected[this._props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+ const barName = StrCast(this._currSelected[this._props.axes[0]].replace(/\$/g, '').replace(/%/g, '').replace(/</g, ''));
const barColors = Cast(this._props.layoutDoc.dataViz_histogram_barColors, listSpec('string'), null);
barColors.forEach(each => each.split('::')[0] === barName && barColors.splice(barColors.indexOf(each), 1));
};
updateBarColors = () => {
- var svg = this._histogramSvg;
+ const svg = this._histogramSvg;
if (svg)
svg.selectAll('rect').attr('fill', (d: any) => {
- var barColor;
+ let barColor;
const barColors = StrListCast(this._props.layoutDoc.dataViz_histogram_barColors).map(each => each.split('::'));
barColors.forEach(each => {
- if (d[0] && d[0].toString() && each[0] == d[0].toString()) barColor = each[1];
+ // eslint-disable-next-line prefer-destructuring
+ if (d[0] && d[0].toString() && each[0] === d[0].toString()) barColor = each[1];
else {
const range = StrCast(each[0]).split(' to ');
+ // eslint-disable-next-line prefer-destructuring
if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1];
}
});
@@ -435,39 +416,41 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
render() {
this.updateBarColors();
this._histogramData;
- var curSelectedBarName = '';
- var titleAccessor: any = 'dataViz_histogram_title';
- if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
- else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
+ let curSelectedBarName = '';
+ let titleAccessor: any = 'dataViz_histogram_title';
+ if (this._props.axes.length === 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor += this._props.axes[0];
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
if (!this._props.layoutDoc.dataViz_histogram_defaultColor) this._props.layoutDoc.dataViz_histogram_defaultColor = '#69b3a2';
if (!this._props.layoutDoc.dataViz_histogram_barColors) this._props.layoutDoc.dataViz_histogram_barColors = new List<string>();
- var selected = 'none';
+ let selected = 'none';
if (this._currSelected) {
- curSelectedBarName = StrCast(this._currSelected![this._props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+ curSelectedBarName = StrCast(this._currSelected![this._props.axes[0]].replace(/\$/g, '').replace(/%/g, '').replace(/</g, ''));
selected = '{ ';
- Object.keys(this._currSelected).forEach(key =>
+ Object.keys(this._currSelected).forEach(key => {
key //
? (selected += key + ': ' + this._currSelected[key] + ', ')
- : ''
- );
+ : '';
+ });
selected = selected.substring(0, selected.length - 2) + ' }';
- if (this._props.titleCol!="" && (!this._currSelected["frequency"] || this._currSelected["frequency"]<10)){
- selected+= "\n" + this._props.titleCol + ": "
+ if (this._props.titleCol !== '' && (!this._currSelected.frequency || this._currSelected.frequency < 10)) {
+ selected += '\n' + this._props.titleCol + ': ';
this._tableData.forEach(each => {
- if (this._currSelected[this._props.axes[0]]==each[this._props.axes[0]]) {
- if (this._props.axes[1]){
- if (this._currSelected[this._props.axes[1]]==each[this._props.axes[1]]) selected+= each[this._props.titleCol] + ", ";
- }
- else selected+= each[this._props.titleCol] + ", ";
+ if (this._currSelected[this._props.axes[0]] === each[this._props.axes[0]]) {
+ if (this._props.axes[1]) {
+ if (this._currSelected[this._props.axes[1]] === each[this._props.axes[1]]) selected += each[this._props.titleCol] + ', ';
+ } else selected += each[this._props.titleCol] + ', ';
}
- })
- selected = selected.slice(0,-1).slice(0,-1);
+ });
+ selected = selected.slice(0, -1).slice(0, -1);
}
}
- var selectedBarColor;
- var barColors = StrListCast(this._props.layoutDoc.histogramBarColors).map(each => each.split('::'));
- barColors.forEach(each => each[0] === curSelectedBarName && (selectedBarColor = each[1]));
+ let selectedBarColor;
+ const barColors = StrListCast(this._props.layoutDoc.histogramBarColors).map(each => each.split('::'));
+ barColors.forEach(each => {
+ // eslint-disable-next-line prefer-destructuring
+ each[0] === curSelectedBarName && (selectedBarColor = each[1]);
+ });
if (this._histogramData.length > 0 || !this.parentViz) {
return this._props.axes.length >= 1 ? (
@@ -476,45 +459,51 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
<EditableText
val={StrCast(this._props.layoutDoc[titleAccessor])}
setVal={undoable(
- action(val => (this._props.layoutDoc[titleAccessor] = val as string)),
+ action(val => {
+ this._props.layoutDoc[titleAccessor] = val as string;
+ }),
'Change Graph Title'
)}
- color={'black'}
+ color="black"
size={Size.LARGE}
fillWidth
/>
&nbsp; &nbsp;
<ColorPicker
- tooltip={'Change Default Bar Color'}
+ tooltip="Change Default Bar Color"
type={Type.SEC}
icon={<FaFillDrip />}
selectedColor={StrCast(this._props.layoutDoc.dataViz_histogram_defaultColor)}
- setFinalColor={undoable(color => (this._props.layoutDoc.dataViz_histogram_defaultColor = color), 'Change Default Bar Color')}
- setSelectedColor={undoable(color => (this._props.layoutDoc.dataViz_histogram_defaultColor = color), 'Change Default Bar Color')}
+ setFinalColor={undoable(color => {
+ this._props.layoutDoc.dataViz_histogram_defaultColor = color;
+ }, 'Change Default Bar Color')}
+ setSelectedColor={undoable(color => {
+ this._props.layoutDoc.dataViz_histogram_defaultColor = color;
+ }, 'Change Default Bar Color')}
size={Size.XSMALL}
/>
</div>
<div ref={this._histogramRef} />
- {selected != 'none' ? (
- <div className={'selected-data'}>
+ {selected !== 'none' ? (
+ <div className="selected-data">
Selected: {selected}
&nbsp; &nbsp;
<ColorPicker
- tooltip={'Change Bar Color'}
+ tooltip="Change Bar Color"
type={Type.SEC}
icon={<FaFillDrip />}
- selectedColor={selectedBarColor ? selectedBarColor : this.curBarSelected.attr('fill')}
+ selectedColor={selectedBarColor || this.curBarSelected.attr('fill')}
setFinalColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Bar Color')}
setSelectedColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Bar Color')}
size={Size.XSMALL}
/>
&nbsp;
<IconButton
- icon={<FontAwesomeIcon icon={'eraser'} />}
+ icon={<FontAwesomeIcon icon="eraser" />}
size={Size.XSMALL}
- color={'black'}
+ color="black"
type={Type.SEC}
- tooltip={'Revert to the default bar color'}
+ tooltip="Revert to the default bar color"
onClick={undoable(
action(() => this.eraseSelectedColor()),
'Change Selected Bar Color'
@@ -524,7 +513,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
) : null}
</div>
) : (
- <span className="chart-container"> {'first use table view to select a column to graph'}</span>
+ <span className="chart-container"> first use table view to select a column to graph</span>
);
}
// when it is a brushed table and the incoming table doesn't have any rows selected
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index e093ec648..bc35ab8c8 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -3,18 +3,19 @@ import * as d3 from 'd3';
import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast, NumListCast, StrListCast } from '../../../../../fields/Doc';
+import { Doc, DocListCast, NumListCast } from '../../../../../fields/Doc';
import { List } from '../../../../../fields/List';
import { listSpec } from '../../../../../fields/Schema';
import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
import { Docs } from '../../../../documents/Documents';
-import { DocumentManager } from '../../../../util/DocumentManager';
import { undoable } from '../../../../util/UndoManager';
+import {} from '../../../DocComponent';
import { ObservableReactComponent } from '../../../ObservableReactComponent';
-import { PinProps, PresBox } from '../../trails';
+import { PinProps, PinDocView } from '../../../PinFuncs';
import { DataVizBox } from '../DataVizBox';
import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils';
import './Chart.scss';
+import { DocumentView } from '../../DocumentView';
export interface DataPoint {
x: number;
@@ -62,7 +63,6 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
return !this.parentViz ? this._props.records : this._tableDataIds.map(rowId => this._props.records[rowId]);
}
@computed get _lineChartData() {
- var guids = StrListCast(this._props.layoutDoc.dataViz_rowIds);
if (this._props.axes.length <= 1) return [];
return this._tableData.map(record => ({ x: Number(record[this._props.axes[0]]), y: Number(record[this._props.axes[1]]) })).sort((a, b) => (a.x < b.x ? -1 : 1));
}
@@ -71,7 +71,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
}
@computed get parentViz() {
return DocCast(this._props.Document.dataViz_parentViz);
- // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
+ // return LinkManager.Links(this._props.Document) // out of all links
// .filter(link => {
// return link.link_anchor_1 == this._props.Document.dataViz_parentViz;
// }) // get links where this chart doc is the target of the link
@@ -80,7 +80,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
@computed get incomingHighlited() {
// 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 incomingVizBox = DocumentView.getFirstDocumentView(this.parentViz)?.ComponentView as DataVizBox;
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
}
@@ -102,7 +102,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
);
this._disposers.annos = reaction(
() => DocListCast(this._props.dataDoc[this._props.fieldKey + '_annotations']),
- annotations => {
+ (/* annotations */) => {
// modify how d3 renders so that anything in this annotations list would be potentially highlighted in some way
// could be blue colored to make it look like anchor
// this.drawAnnotations()
@@ -175,9 +175,9 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
getAnchor = (pinProps?: PinProps) => {
const anchor = Docs.Create.ConfigDocument({
//
- title: 'line doc selection' + this._currSelected?.x,
+ title: 'line doc selection' + (this._currSelected?.x ?? ''),
});
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
+ 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;
};
@@ -191,11 +191,12 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
}
@computed get defaultGraphTitle() {
- var ax0 = this._props.axes[0];
- var ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
+ const ax0 = this._props.axes[0];
+ const ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
if (this._props.axes.length < 2 || !/\d/.test(this._props.records[0][ax0]) || !ax1) {
return ax0 + ' Line Chart';
- } else return ax1 + ' by ' + ax0 + ' Line Chart';
+ }
+ return ax1 + ' by ' + ax0 + ' Line Chart';
}
setupTooltip() {
@@ -215,9 +216,11 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
@action
setCurrSelected(x?: number, y?: number) {
// TODO: nda - get rid of svg element in the list?
- if (this._currSelected && this._currSelected.x == x && this._currSelected.y == y) this._currSelected = undefined;
+ if (this._currSelected && this._currSelected.x === x && this._currSelected.y === y) this._currSelected = undefined;
else this._currSelected = x !== undefined && y !== undefined ? { x, y } : undefined;
- this._props.records.forEach(record => record[this._props.axes[0]] === x && record[this._props.axes[1]] === y && (record.selected = true));
+ this._props.records.forEach(record => {
+ record[this._props.axes[0]] === x && record[this._props.axes[1]] === y && (record.selected = true);
+ });
}
drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) {
@@ -241,13 +244,13 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
d3.select(this._lineChartRef.current).select('svg').remove();
d3.select(this._lineChartRef.current).select('.tooltip').remove();
- var { xMin, xMax, yMin, yMax } = rangeVals;
+ let { xMin, xMax, yMin, yMax } = rangeVals;
if (xMin === undefined || xMax === undefined || yMin === undefined || yMax === undefined) {
return;
}
// adding svg
- const margin = this._props.margin;
+ const { margin } = this._props;
const svg = (this._lineChartSvg = d3
.select(this._lineChartRef.current)
.append('svg')
@@ -257,18 +260,19 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`));
- var validSecondData;
- if (this._props.axes.length>2){ // for when there are 2 lines on the chart
- var next = this._tableData.map(record => ({ x: Number(record[this._props.axes[0]]), y: Number(record[this._props.axes[2]]) })).sort((a, b) => (a.x < b.x ? -1 : 1));
+ let validSecondData;
+ if (this._props.axes.length > 2) {
+ // for when there are 2 lines on the chart
+ const next = this._tableData.map(record => ({ x: Number(record[this._props.axes[0]]), y: Number(record[this._props.axes[2]]) })).sort((a, b) => (a.x < b.x ? -1 : 1));
validSecondData = next.filter(d => {
- if (!d.x || Number.isNaN(d.x) || !d.y || Number.isNaN(d.y)) return false;
+ if (!d.x || isNaN(d.x) || !d.y || isNaN(d.y)) return false;
return true;
});
- var secondDataRange = minMaxRange([validSecondData]);
- if (secondDataRange.xMax!>xMax) xMax = secondDataRange.xMax;
- if (secondDataRange.yMax!>yMax) yMax = secondDataRange.yMax;
- if (secondDataRange.xMin!<xMin) xMin = secondDataRange.xMin;
- if (secondDataRange.yMin!<yMin) yMin = secondDataRange.yMin;
+ const secondDataRange = minMaxRange([validSecondData]);
+ if (secondDataRange.xMax! > xMax) xMax = secondDataRange.xMax;
+ if (secondDataRange.yMax! > yMax) yMax = secondDataRange.yMax;
+ if (secondDataRange.xMin! < xMin) xMin = secondDataRange.xMin;
+ if (secondDataRange.yMin! < yMin) yMin = secondDataRange.yMin;
}
// creating the x and y scales
@@ -285,40 +289,34 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
if (validSecondData) {
drawLine(svg.append('path'), validSecondData, lineGen, true);
this.drawDataPoints(validSecondData, 0, xScale, yScale);
- svg.append('path').attr("stroke", "red");
+ svg.append('path').attr('stroke', 'red');
// legend
- var color = d3.scaleOrdinal()
- .range(["black", "blue"])
- .domain([this._props.axes[1], this._props.axes[2]])
- svg.selectAll("mydots")
+ const color: any = d3.scaleOrdinal().range(['black', 'blue']).domain([this._props.axes[1], this._props.axes[2]]);
+ svg.selectAll('mydots')
.data([this._props.axes[1], this._props.axes[2]])
.enter()
- .append("circle")
- .attr("cx", 5)
- .attr("cy", function(d,i){ return -30 + i*15})
- .attr("r", 7)
- .style("fill", function(d){ return color(d)})
- svg.selectAll("mylabels")
+ .append('circle')
+ .attr('cx', 5)
+ .attr('cy', (d, i) => -30 + i * 15)
+ .attr('r', 7)
+ .style('fill', d => color(d));
+ svg.selectAll('mylabels')
.data([this._props.axes[1], this._props.axes[2]])
.enter()
- .append("text")
- .attr("x", 25)
- .attr("y", function(d,i){ return -30 + i*15})
- .style("fill", function(d){ return color(d)})
- .text(function(d){ return d})
- .attr("text-anchor", "left")
- .style("alignment-baseline", "middle")
+ .append('text')
+ .attr('x', 25)
+ .attr('y', (d, i) => -30 + i * 15)
+ .style('fill', d => color(d))
+ .text(d => d)
+ .attr('text-anchor', 'left')
+ .style('alignment-baseline', 'middle');
}
// get valid data points
const data = dataSet[0];
- var validData = data.filter(d => {
- Object.keys(data[0]).map(key => {
- if (!d[key] || Number.isNaN(d[key])) return false;
- });
- return true;
- });
+ const keys = Object.keys(data[0]);
+ const validData = data.filter(d => !keys.some(key => isNaN(d[key])));
// draw the plot line
drawLine(svg.append('path'), validData, lineGen, false);
@@ -345,7 +343,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
const x0 = bisect(data, xScale.invert(xPos - 5)); // shift x by -5 so that you can reach points on the left-side axis
const d0 = data[x0];
// find .circle-d1 with data-x = d0.x and data-y = d0.y
- const selected = svg.selectAll('.datapoint').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y);
+ svg.selectAll('.datapoint').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y);
this.setCurrSelected(d0.x, d0.y);
this.updateTooltip(higlightFocusPt, xScale, d0, yScale, tooltip);
});
@@ -368,7 +366,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
.style('text-anchor', 'middle')
.text(this._props.axes[0]);
svg.append('text')
- .attr('transform', 'rotate(-90)' + ' ' + 'translate( 0, ' + -10 + ')')
+ .attr('transform', 'rotate(-90) translate(0, -10)')
.attr('x', -(height / 2))
.attr('y', -30)
.attr('height', 20)
@@ -394,57 +392,60 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
}
render() {
- var titleAccessor: any = 'dataViz_lineChart_title';
- if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
- else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
+ let titleAccessor: any = 'dataViz_lineChart_title';
+ if (this._props.axes.length === 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor += this._props.axes[0];
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
const selectedPt = this._currSelected ? `{ ${this._props.axes[0]}: ${this._currSelected.x} ${this._props.axes[1]}: ${this._currSelected.y} }` : 'none';
- var selectedTitle = "";
- if (this._currSelected && this._props.titleCol){
- selectedTitle+= "\n" + this._props.titleCol + ": "
+ let selectedTitle = '';
+ if (this._currSelected && this._props.titleCol) {
+ selectedTitle += '\n' + this._props.titleCol + ': ';
this._tableData.forEach(each => {
- var mapThisEntry = false;
- if (this._currSelected.x==each[this._props.axes[0]] && this._currSelected.y==each[this._props.axes[1]]) mapThisEntry = true;
- else if (this._currSelected.y==each[this._props.axes[0]] && this._currSelected.x==each[this._props.axes[1]]) mapThisEntry = true;
- if (mapThisEntry) selectedTitle += each[this._props.titleCol] + ", ";
- })
- selectedTitle = selectedTitle.slice(0,-1).slice(0,-1);
+ let mapThisEntry = false;
+ if (this._currSelected.x === each[this._props.axes[0]] && this._currSelected.y === each[this._props.axes[1]]) mapThisEntry = true;
+ else if (this._currSelected.y === each[this._props.axes[0]] && this._currSelected.x === each[this._props.axes[1]]) mapThisEntry = true;
+ if (mapThisEntry) selectedTitle += each[this._props.titleCol] + ', ';
+ });
+ selectedTitle = selectedTitle.slice(0, -1).slice(0, -1);
}
- if (this._lineChartData.length > 0 || !this.parentViz || this.parentViz.length == 0) {
+ if (this._lineChartData.length > 0 || !this.parentViz || this.parentViz.length === 0) {
return this._props.axes.length >= 2 && /\d/.test(this._props.records[0][this._props.axes[0]]) && /\d/.test(this._props.records[0][this._props.axes[1]]) ? (
<div className="chart-container" style={{ width: this._props.width + this._props.margin.right }}>
<div className="graph-title">
<EditableText
val={StrCast(this._props.layoutDoc[titleAccessor])}
setVal={undoable(
- action(val => (this._props.layoutDoc[titleAccessor] = val as string)),
+ action(val => {
+ this._props.layoutDoc[titleAccessor] = val as string;
+ }),
'Change Graph Title'
)}
- color={'black'}
+ color="black"
size={Size.LARGE}
fillWidth
/>
</div>
<div ref={this._lineChartRef} />
- {selectedPt != 'none' ? (
- <div className={'selected-data'}>
+ {selectedPt !== 'none' ? (
+ <div className="selected-data">
{`Selected: ${selectedPt}`}
{`${selectedTitle}`}
<Button
- onClick={e => {
+ onClick={() => {
this._props.vizBox.sidebarBtnDown;
this._props.vizBox.sidebarAddDocument;
- }}></Button>
+ }}
+ />
</div>
) : null}
</div>
) : (
- <span className="chart-container"> {'first use table view to select two numerical axes to plot'}</span>
- );
- } else
- return (
- // when it is a brushed table and the incoming table doesn't have any rows selected
- <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div>
+ <span className="chart-container"> first use table view to select two numerical axes to plot</span>
);
+ }
+ return (
+ // when it is a brushed table and the incoming table doesn't have any rows selected
+ <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div>
+ );
}
}
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index fc23f47de..ef6d1d412 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -12,7 +12,7 @@ import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
import { Docs } from '../../../../documents/Documents';
import { undoable } from '../../../../util/UndoManager';
import { ObservableReactComponent } from '../../../ObservableReactComponent';
-import { PinProps, PresBox } from '../../trails';
+import { PinProps, PinDocView } from '../../../PinFuncs';
import './Chart.scss';
export interface PieChartProps {
@@ -74,8 +74,8 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
}
@computed get defaultGraphTitle() {
- var ax0 = this._props.axes[0];
- var ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
+ const ax0 = this._props.axes[0];
+ const ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
if (this._props.axes.length < 2 || !/\d/.test(this._props.records[0][ax0]) || !ax1) {
return ax0 + ' Pie Chart';
}
@@ -84,7 +84,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
@computed get parentViz() {
return DocCast(this._props.Document.dataViz_parentViz);
- // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
+ // return LinkManager.Links(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,14 +101,14 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
}
@action
- restoreView = (data: Doc) => {};
+ restoreView = (/* data: Doc */) => {};
// create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc)
getAnchor = (pinProps?: PinProps) => {
const anchor = Docs.Create.ConfigDocument({
//
title: 'piechart doc selection' + this._currSelected,
});
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
+ PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
return anchor;
};
@@ -122,30 +122,30 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
// cleans data by converting numerical data to numbers and taking out empty cells
data = (dataSet: any) => {
- const validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
+ const validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key])));
const field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined;
return !field
? undefined
: validData.map((d: { [x: string]: any }) =>
this.byCategory
? d[field] //
- : +d[field].replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '')
+ : +d[field].replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '')
);
};
// outlines the slice selected / hovered over
highlightSelectedSlice = (changeSelectedVariables: boolean, svg: any, arc: any, radius: any, pointer: any, pieDataSet: any) => {
- var index = -1;
- var sameAsCurrent: boolean;
+ let index = -1;
+ let sameAsCurrent: boolean;
const selected = svg.selectAll('.slice').filter((d: any) => {
index++;
- var p1 = [0, 0]; // center of pie
- var p3 = [arc.centroid(d)[0] * 2, arc.centroid(d)[1] * 2]; // outward peak of arc
- var p2 = [radius * Math.sin(d.startAngle), -radius * Math.cos(d.startAngle)]; // start of arc
- var p4 = [radius * Math.sin(d.endAngle), -radius * Math.cos(d.endAngle)]; // end of arc
+ const p1 = [0, 0]; // center of pie
+ const p3 = [arc.centroid(d)[0] * 2, arc.centroid(d)[1] * 2]; // outward peak of arc
+ const p2 = [radius * Math.sin(d.startAngle), -radius * Math.cos(d.startAngle)]; // start of arc
+ const p4 = [radius * Math.sin(d.endAngle), -radius * Math.cos(d.endAngle)]; // end of arc
// draw an imaginary horizontal line from the pointer to see how many times it crosses a slice edge
- var lineCrossCount = 0;
+ let lineCrossCount = 0;
// if for all 4 lines
if (Math.min(p1[1], p2[1]) <= pointer[1] && pointer[1] <= Math.max(p1[1], p2[1])) {
// within y bounds
@@ -160,13 +160,13 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
if (Math.min(p4[1], p1[1]) <= pointer[1] && pointer[1] <= Math.max(p4[1], p1[1])) {
if (pointer[0] <= ((pointer[1] - p4[1]) * (p1[0] - p4[0])) / (p1[1] - p4[1]) + p4[0]) lineCrossCount++;
}
- if (lineCrossCount % 2 != 0) {
+ if (lineCrossCount % 2 !== 0) {
// inside the slice of it crosses an odd number of edges
- var showSelected = this.byCategory ? pieDataSet[index] : this._pieChartData[index];
+ const showSelected = this.byCategory ? pieDataSet[index] : this._pieChartData[index];
if (changeSelectedVariables) {
// for when a bar is selected - not just hovered over
sameAsCurrent = this._currSelected
- ? showSelected[Object.keys(showSelected)[0]] == this._currSelected![Object.keys(showSelected)[0]] && showSelected[Object.keys(showSelected)[1]] == this._currSelected![Object.keys(showSelected)[1]]
+ ? showSelected[Object.keys(showSelected)[0]] === this._currSelected![Object.keys(showSelected)[0]] && showSelected[Object.keys(showSelected)[1]] === this._currSelected![Object.keys(showSelected)[1]]
: this._currSelected === showSelected;
this._currSelected = sameAsCurrent ? undefined : showSelected;
this.selectedData = sameAsCurrent ? undefined : d;
@@ -186,104 +186,100 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
d3.select(this._piechartRef.current).select('svg').remove();
d3.select(this._piechartRef.current).select('.tooltip').remove();
- var percentField = Object.keys(dataSet[0])[0];
- var descriptionField = Object.keys(dataSet[0])[1]!;
- var radius = Math.min(width, height - this._props.margin.top - this._props.margin.bottom) / 2;
+ let percentField = Object.keys(dataSet[0])[0];
+ let descriptionField = Object.keys(dataSet[0])[1]!;
+ const radius = Math.min(width, height - this._props.margin.top - this._props.margin.bottom) / 2;
// converts data into Objects
- var data = this.data(dataSet);
- var pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
+ let data = this.data(dataSet);
+ let pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key])));
if (this.byCategory) {
- let uniqueCategories = [...new Set(data)];
- var pieStringDataSet: { frequency: number }[] = [];
+ const uniqueCategories = [...new Set(data)];
+ const pieStringDataSet: { frequency: number }[] = [];
for (let i = 0; i < uniqueCategories.length; i++) {
pieStringDataSet.push({ frequency: 0, [percentField]: uniqueCategories[i] });
}
for (let i = 0; i < data.length; i++) {
- let sliceData = pieStringDataSet.filter((each: any) => each[percentField] == data[i]);
- sliceData[0].frequency = sliceData[0].frequency + 1;
+ // eslint-disable-next-line no-loop-func
+ const sliceData = pieStringDataSet.filter((each: any) => each[percentField] === data[i]);
+ sliceData[0].frequency += 1;
}
pieDataSet = pieStringDataSet;
- percentField = Object.keys(pieDataSet[0])[0];
- descriptionField = Object.keys(pieDataSet[0])[1]!;
+ [percentField, descriptionField] = Object.keys(pieDataSet[0]);
data = this.data(pieStringDataSet);
}
- var trackDuplicates: { [key: string]: any } = {};
- data.forEach((eachData: any) => (!trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null));
+ let trackDuplicates: { [key: string]: any } = {};
+ data.forEach((eachData: any) => {
+ !trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null;
+ });
// initial chart
- var svg = (this._piechartSvg = d3
+ const svg = (this._piechartSvg = d3
.select(this._piechartRef.current)
.append('svg')
.attr('class', 'graph')
.attr('width', width + this._props.margin.right + this._props.margin.left)
.attr('height', height + this._props.margin.top + this._props.margin.bottom)
.append('g'));
- let g = svg.append('g').attr('transform', 'translate(' + (width / 2 + this._props.margin.left) + ',' + height / 2 + ')');
- var pie = d3.pie();
- var arc = d3.arc().innerRadius(0).outerRadius(radius);
+ const g = svg.append('g').attr('transform', 'translate(' + (width / 2 + this._props.margin.left) + ',' + height / 2 + ')');
+ const pie = d3.pie();
+ const arc = d3.arc().innerRadius(0).outerRadius(radius);
+ const updateHighlights = () => {
+ const hoverOverSlice = this.hoverOverData;
+ const { selectedData } = this;
+ svg.selectAll('path').attr('class', (d: any) =>
+ (selectedData && d.startAngle === selectedData.startAngle && d.endAngle === selectedData.endAngle) || (hoverOverSlice && d.startAngle === hoverOverSlice.startAngle && d.endAngle === hoverOverSlice.endAngle) ? 'slice hover' : 'slice'
+ );
+ };
// click/hover
const onPointClick = action((e: any) => this.highlightSelectedSlice(true, svg, arc, radius, d3.pointer(e), pieDataSet));
const onHover = action((e: any) => {
this.highlightSelectedSlice(false, svg, arc, radius, d3.pointer(e), pieDataSet);
updateHighlights();
});
- const mouseOut = action((e: any) => {
+ const mouseOut = action(() => {
this.hoverOverData = undefined;
updateHighlights();
});
- const updateHighlights = () => {
- const hoverOverSlice = this.hoverOverData;
- const selectedData = this.selectedData;
- svg.selectAll('path').attr('class', function (d: any) {
- return (selectedData && d.startAngle == selectedData.startAngle && d.endAngle == selectedData.endAngle) || (hoverOverSlice && d.startAngle == hoverOverSlice.startAngle && d.endAngle == hoverOverSlice.endAngle)
- ? 'slice hover'
- : 'slice';
- });
- };
// drawing the slices
- var selected = this.selectedData;
- var arcs = g.selectAll('arc').data(pie(data)).enter().append('g');
+ const selected = this.selectedData;
+ const arcs = g.selectAll('arc').data(pie(data)).enter().append('g');
const possibleDataPointVals: { [x: string]: any }[] = [];
pieDataSet.forEach((each: { [x: string]: any | { valueOf(): number } }) => {
- var dataPointVal: { [x: string]: any } = {};
+ const dataPointVal: { [x: string]: any } = {};
dataPointVal[percentField] = each[percentField];
if (descriptionField) dataPointVal[descriptionField] = each[descriptionField];
try {
- dataPointVal[percentField] = Number(dataPointVal[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, ''));
- } catch (error) {}
+ dataPointVal[percentField] = Number(dataPointVal[percentField].replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, ''));
+ } catch (error) {
+ /* empty */
+ }
possibleDataPointVals.push(dataPointVal);
});
const sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
arcs.append('path')
.attr('fill', (d, i) => {
- var dataPoint;
+ let dataPoint;
const possibleDataPoints = possibleDataPointVals.filter((pval: any) => pval[percentField] === Number(d.data));
- if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0];
+ if (possibleDataPoints.length === 1) [dataPoint] = possibleDataPoints;
else {
dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]];
trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1;
}
- var sliceColor;
+ let sliceColor;
if (dataPoint) {
const sliceTitle = dataPoint[this._props.axes[0]];
- const accessByName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
- sliceColors.forEach(each => each[0] == accessByName && (sliceColor = each[1]));
+ const accessByName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle;
+ sliceColors.forEach(each => {
+ // eslint-disable-next-line prefer-destructuring
+ each[0] === accessByName && (sliceColor = each[1]);
+ });
}
return sliceColor ? StrCast(sliceColor) : d3.schemeSet3[i] ? d3.schemeSet3[i] : d3.schemeSet3[i % d3.schemeSet3.length];
})
- .attr(
- 'class',
- selected
- ? function (d) {
- return selected && d.startAngle == selected.startAngle && d.endAngle == selected.endAngle ? 'slice hover' : 'slice';
- }
- : function (d) {
- return 'slice';
- }
- )
+ .attr('class', selected ? d => (selected && d.startAngle === selected.startAngle && d.endAngle === selected.endAngle ? 'slice hover' : 'slice') : () => 'slice')
// @ts-ignore
.attr('d', arc)
.on('click', onPointClick)
@@ -292,20 +288,22 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
// adding labels
trackDuplicates = {};
- data.forEach((eachData: any) => (!trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null));
+ data.forEach((eachData: any) => {
+ !trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null;
+ });
arcs.size() < 100 &&
arcs
.append('text')
- .attr('transform', function (d) {
- var centroid = arc.centroid(d as unknown as d3.DefaultArcObject);
- var heightOffset = (centroid[1] / radius) * Math.abs(centroid[1]);
+ .attr('transform', d => {
+ const centroid = arc.centroid(d as unknown as d3.DefaultArcObject);
+ const heightOffset = (centroid[1] / radius) * Math.abs(centroid[1]);
return 'translate(' + (centroid[0] + centroid[0] / (radius * 0.02)) + ',' + (centroid[1] + heightOffset) + ')';
})
.attr('text-anchor', 'middle')
- .text(function (d) {
- var dataPoint;
+ .text(d => {
+ let dataPoint;
const possibleDataPoints = possibleDataPointVals.filter((pval: any) => pval[percentField] === Number(d.data));
- if (possibleDataPoints.length == 1) dataPoint = pieDataSet[possibleDataPointVals.indexOf(possibleDataPoints[0])];
+ if (possibleDataPoints.length === 1) dataPoint = pieDataSet[possibleDataPointVals.indexOf(possibleDataPoints[0])];
else {
dataPoint = pieDataSet[possibleDataPointVals.indexOf(possibleDataPoints[trackDuplicates[d.data.toString()]])];
trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1;
@@ -317,11 +315,11 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
@action changeSelectedColor = (color: string) => {
this.curSliceSelected.attr('fill', color);
const sliceTitle = this._currSelected[this._props.axes[0]];
- const sliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
+ const sliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle;
const sliceColors = Cast(this._props.layoutDoc.dataViz_pie_sliceColors, listSpec('string'), null);
- sliceColors.map(each => {
- if (each.split('::')[0] == sliceName) sliceColors.splice(sliceColors.indexOf(each), 1);
+ sliceColors.forEach(each => {
+ if (each.split('::')[0] === sliceName) sliceColors.splice(sliceColors.indexOf(each), 1);
});
sliceColors.push(StrCast(sliceName + '::' + color));
};
@@ -332,39 +330,39 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
};
render() {
- var titleAccessor: any = 'dataViz_pie_title';
- if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
- else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
+ let titleAccessor: any = 'dataViz_pie_title';
+ if (this._props.axes.length === 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor += this._props.axes[0];
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
if (!this._props.layoutDoc.dataViz_pie_sliceColors) this._props.layoutDoc.dataViz_pie_sliceColors = new List<string>();
- var selected: string;
- var curSelectedSliceName = '';
+ let selected: string;
+ let curSelectedSliceName = '';
if (this._currSelected) {
selected = '{ ';
const sliceTitle = this._currSelected[this._props.axes[0]];
- curSelectedSliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
- Object.keys(this._currSelected).map(key => {
- key != '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : '';
+ curSelectedSliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle;
+ Object.keys(this._currSelected).forEach(key => {
+ key !== '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : '';
});
selected = selected.substring(0, selected.length - 2);
selected += ' }';
- if (this._props.titleCol!="" && (!this._currSelected["frequency"] || this._currSelected["frequency"]<10)){
- selected+= "\n" + this._props.titleCol + ": "
+ if (this._props.titleCol !== '' && (!this._currSelected.frequency || this._currSelected.frequency < 10)) {
+ selected += '\n' + this._props.titleCol + ': ';
this._tableData.forEach(each => {
- if (this._currSelected[this._props.axes[0]]==each[this._props.axes[0]]) {
- if (this._props.axes[1]){
- if (this._currSelected[this._props.axes[1]]==each[this._props.axes[1]]) selected+= each[this._props.titleCol] + ", ";
- }
- else selected+= each[this._props.titleCol] + ", ";
+ if (this._currSelected[this._props.axes[0]] === each[this._props.axes[0]]) {
+ if (this._props.axes[1]) {
+ if (this._currSelected[this._props.axes[1]] === each[this._props.axes[1]]) selected += each[this._props.titleCol] + ', ';
+ } else selected += each[this._props.titleCol] + ', ';
}
- })
- selected = selected.slice(0,-1).slice(0,-1);
+ });
+ selected = selected.slice(0, -1).slice(0, -1);
}
} else selected = 'none';
- var selectedSliceColor;
- var sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
+ let selectedSliceColor;
+ const sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
sliceColors.forEach(each => {
- if (each[0] == curSelectedSliceName!) selectedSliceColor = each[1];
+ // eslint-disable-next-line prefer-destructuring
+ if (each[0] === curSelectedSliceName!) selectedSliceColor = each[1];
});
if (this._pieChartData.length > 0 || !this.parentViz) {
@@ -374,30 +372,32 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
<EditableText
val={StrCast(this._props.layoutDoc[titleAccessor])}
setVal={undoable(
- action(val => (this._props.layoutDoc[titleAccessor] = val as string)),
+ action(val => {
+ this._props.layoutDoc[titleAccessor] = val as string;
+ }),
'Change Graph Title'
)}
- color={'black'}
+ color="black"
size={Size.LARGE}
fillWidth
/>
</div>
{this._props.axes.length === 1 && /\d/.test(this._props.records[0][this._props.axes[0]]) ? (
- <div className={'asHistogram-checkBox'} style={{ width: this._props.width }}>
+ <div className="asHistogram-checkBox" style={{ width: this._props.width }}>
<Checkbox color="primary" onChange={this.changeHistogramCheckBox} checked={this._props.layoutDoc.dataViz_pie_asHistogram as boolean} />
Organize data as histogram
</div>
) : null}
<div ref={this._piechartRef} />
- {selected != 'none' ? (
- <div className={'selected-data'}>
+ {selected !== 'none' ? (
+ <div className="selected-data">
Selected: {selected}
&nbsp; &nbsp;
<ColorPicker
- tooltip={'Change Slice Color'}
+ tooltip="Change Slice Color"
type={Type.SEC}
icon={<FaFillDrip />}
- selectedColor={selectedSliceColor ? selectedSliceColor : this.curSliceSelected.attr('fill')}
+ selectedColor={selectedSliceColor || this.curSliceSelected.attr('fill')}
setFinalColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Slice Color')}
setSelectedColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Slice Color')}
size={Size.XSMALL}
@@ -406,12 +406,12 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
) : null}
</div>
) : (
- <span className="chart-container"> {'first use table view to select a column to graph'}</span>
- );
- } else
- return (
- // when it is a brushed table and the incoming table doesn't have any rows selected
- <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div>
+ <span className="chart-container"> first use table view to select a column to graph</span>
);
+ }
+ return (
+ // when it is a brushed table and the incoming table doesn't have any rows selected
+ <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div>
+ );
}
}
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 67e1c67bd..5cd77e274 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -1,19 +1,25 @@
+/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { Button, Type } from 'browndash-components';
import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../../Utils';
+import { ClientUtils, setupMoveUpEvents } from '../../../../../ClientUtils';
+import { emptyFunction } from '../../../../../Utils';
import { Doc, Field, NumListCast } from '../../../../../fields/Doc';
+import { DocData } from '../../../../../fields/DocSymbols';
import { List } from '../../../../../fields/List';
import { listSpec } from '../../../../../fields/Schema';
import { Cast, DocCast } from '../../../../../fields/Types';
import { DragManager } from '../../../../util/DragManager';
+import { undoable } from '../../../../util/UndoManager';
import { ObservableReactComponent } from '../../../ObservableReactComponent';
import { DocumentView } from '../../DocumentView';
import { DataVizView } from '../DataVizBox';
import './Chart.scss';
-import { undoable } from '../../../../util/UndoManager';
+
const { DATA_VIZ_TABLE_ROW_HEIGHT } = require('../../../global/globalCssVariables.module.scss'); // prettier-ignore
+
interface TableBoxProps {
Document: Doc;
layoutDoc: Doc;
@@ -77,7 +83,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
}
@computed get columns() {
- return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header != '' && header != undefined) : [];
+ return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header !== '' && header !== undefined) : [];
}
// updates the 'dataViz_selectedRows' and 'dataViz_highlightedRows' fields to no longer include rows that aren't in the table
@@ -114,15 +120,13 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
else highlited?.push(rowId);
if (!selected?.includes(rowId)) selected?.push(rowId);
- } else {
+ } else if (selected?.includes(rowId)) {
// selecting a row
- if (selected?.includes(rowId)) {
- if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
- selected.splice(selected.indexOf(rowId), 1);
- } else selected?.push(rowId);
- }
+ if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
+ selected.splice(selected.indexOf(rowId), 1);
+ } else selected?.push(rowId);
e.stopPropagation();
- this.hasRowsToFilter = selected.length > 0 ? true : false;
+ this.hasRowsToFilter = selected.length > 0;
};
columnPointerDown = (e: React.PointerEvent, col: string) => {
@@ -131,7 +135,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
setupMoveUpEvents(
{},
e,
- e => {
+ moveEv => {
// dragging off a column to create a brushed DataVizBox
const sourceAnchorCreator = () => this._props.docView?.()!.Document!;
const targetCreator = (annotationOn: Doc | undefined) => {
@@ -145,15 +149,13 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
embedding.pieSliceColors = Field.Copy(this._props.layoutDoc.pieSliceColors);
return embedding;
};
- if (this._props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
- DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this._props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
- dragComplete: e => {
- if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
- e.linkDocument.link_displayLine = true;
- e.linkDocument.link_matchEmbeddings = true;
- e.linkDocument.link_displayArrow = true;
- // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this._props.Document;
- // e.annoDragData.linkSourceDoc.followLinkZoom = false;
+ if (this._props.docView?.() && !ClientUtils.isClick(moveEv.clientX, moveEv.clientY, downX, downY, Date.now())) {
+ DragManager.StartAnchorAnnoDrag(moveEv.target instanceof HTMLElement ? [moveEv.target] : [], new DragManager.AnchorAnnoDragData(this._props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
+ dragComplete: completeEv => {
+ if (!completeEv.aborted && completeEv.annoDragData && completeEv.annoDragData.linkSourceDoc && completeEv.annoDragData.dropDocument && completeEv.linkDocument) {
+ completeEv.linkDocument[DocData].link_matchEmbeddings = true;
+ completeEv.linkDocument[DocData].stroke_startMarker = true;
+ this._props.docView?.()?._props.addDocument?.(completeEv.linkDocument);
}
},
});
@@ -162,10 +164,10 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
return false;
},
emptyFunction,
- action(e => {
- if (e.shiftKey || this.settingTitle) {
+ action(moveEv => {
+ if (moveEv.shiftKey || this.settingTitle) {
if (this.settingTitle) this.settingTitle = false;
- if (this._props.titleCol == col) this._props.titleCol = '';
+ if (this._props.titleCol === col) this._props.titleCol = '';
else this._props.titleCol = col;
this._props.selectTitleCol(this._props.titleCol);
} else {
@@ -183,16 +185,16 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
* These functions handle the filtering popup for when the "filter" button is pressed to select rows
*/
filter = undoable((e: any) => {
- var start: any;
- var end: any;
- if (this.filteringType == 'Range') {
+ let start: any;
+ let end: any;
+ if (this.filteringType === 'Range') {
start = (this.filteringVal[0] as Number) ? Number(this.filteringVal[0]) : this.filteringVal[0];
end = (this.filteringVal[1] as Number) ? Number(this.filteringVal[1]) : this.filteringVal[0];
}
this._tableDataIds.forEach(rowID => {
- if (this.filteringType == 'Value') {
- if (this._props.records[rowID][this.filteringColumn] == this.filteringVal[0]) {
+ if (this.filteringType === 'Value') {
+ if (this._props.records[rowID][this.filteringColumn] === this.filteringVal[0]) {
if (!NumListCast(this._props.layoutDoc.dataViz_selectedRows).includes(rowID)) {
this.tableRowClick(e, rowID);
}
@@ -229,12 +231,12 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
this.filteringVal[1] = e.target.value;
});
@computed get renderFiltering() {
- if (this.filteringColumn === '') this.filteringColumn = this.columns[0];
+ if (this.filteringColumn === '') [this.filteringColumn] = this.columns;
return (
<div className="tableBox-filterPopup" style={{ right: this._props.width * 0.05 }}>
<div className="tableBox-filterPopup-selectColumn">
Column:
- <select className="tableBox-filterPopup-selectColumn-each" value={this.filteringColumn != '' ? this.filteringColumn : this.columns[0]} onChange={e => this.setFilterColumn(e)}>
+ <select className="tableBox-filterPopup-selectColumn-each" value={this.filteringColumn !== '' ? this.filteringColumn : this.columns[0]} onChange={e => this.setFilterColumn(e)}>
{this.columns.map(column => (
<option className="" key={column} value={column}>
{' '}
@@ -245,17 +247,17 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
</div>
<div className="tableBox-filterPopup-setValue">
<select className="tableBox-filterPopup-setValue-each" value={this.filteringType} onChange={e => this.setFilterType(e)}>
- <option className="" key={'Value'} value={'Value'}>
+ <option className="" key="Value" value="Value">
{' '}
{'Value'}{' '}
</option>
- <option className="" key={'Range'} value={'Range'}>
+ <option className="" key="Range" value="Range">
{' '}
{'Range'}{' '}
</option>
</select>
:
- {this.filteringType == 'Value' ? (
+ {this.filteringType === 'Value' ? (
<input
className="tableBox-filterPopup-setValue-input"
defaultValue=""
@@ -301,7 +303,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
)}
</div>
<div className="tableBox-filterPopup-setFilter">
- <Button onClick={action(e => this.filter(e))} text="Set Filter" type={Type.SEC} color={'black'} />
+ <Button onClick={action(e => this.filter(e))} text="Set Filter" type={Type.SEC} color="black" />
</div>
</div>
);
@@ -322,11 +324,25 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
}}>
<div className="tableBox-selectButtons">
<div className="tableBox-selectTitle">
- <Button onClick={action(() => (this.settingTitle = !this.settingTitle))} text="Select Title Column" type={Type.SEC} color={'black'} />
+ <Button
+ onClick={action(() => {
+ this.settingTitle = !this.settingTitle;
+ })}
+ text="Select Title Column"
+ type={Type.SEC}
+ color="black"
+ />
</div>
<div className="tableBox-filtering">
{this.filtering ? this.renderFiltering : null}
- <Button onClick={action(() => (this.filtering = !this.filtering))} text="Filter" type={Type.SEC} color={'black'} />
+ <Button
+ onClick={action(() => {
+ this.filtering = !this.filtering;
+ })}
+ text="Filter"
+ type={Type.SEC}
+ color="black"
+ />
<div className="tableBox-filterAll">
{this.hasRowsToFilter ? (
<Button
@@ -336,7 +352,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
})}
text="Deselect All"
type={Type.SEC}
- color={'black'}
+ color="black"
tooltip="Select rows to be displayed in any DataViz boxes dragged off of this one."
/>
) : (
@@ -347,7 +363,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
})}
text="Select All"
type={Type.SEC}
- color={'black'}
+ color="black"
tooltip="Select rows to be displayed in any DataViz boxes dragged off of this one."
/>
)}
@@ -391,7 +407,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
? 'darkgreen'
: this._props.axes.length > 2 && this._props.axes.lastElement() === col
? 'darkred'
- : this._props.axes.lastElement() === col || (this._props.axes.length > 2 && this._props.axes[1] == col)
+ : this._props.axes.lastElement() === col || (this._props.axes.length > 2 && this._props.axes[1] === col)
? 'darkblue'
: undefined,
background: this.settingTitle
@@ -400,7 +416,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
? '#E3fbdb'
: this._props.axes.length > 2 && this._props.axes.lastElement() === col
? '#Fbdbdb'
- : this._props.axes.lastElement() === col || (this._props.axes.length > 2 && this._props.axes[1] == col)
+ : this._props.axes.lastElement() === col || (this._props.axes.length > 2 && this._props.axes[1] === col)
? '#c6ebf7'
: undefined,
fontWeight: 'bolder',
@@ -424,11 +440,11 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
background: NumListCast(this._props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this._props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '',
}}>
{this.columns.map(col => {
- var colSelected = false;
- if (this._props.axes.length > 2) colSelected = this._props.axes[0] == col || this._props.axes[1] == col || this._props.axes[2] == col;
- else if (this._props.axes.length > 1) colSelected = this._props.axes[0] == col || this._props.axes[1] == col;
- else if (this._props.axes.length > 0) colSelected = this._props.axes[0] == col;
- if (this._props.titleCol == col) colSelected = true;
+ let colSelected = false;
+ if (this._props.axes.length > 2) colSelected = this._props.axes[0] === col || this._props.axes[1] === col || this._props.axes[2] === col;
+ else if (this._props.axes.length > 1) colSelected = this._props.axes[0] === col || this._props.axes[1] === col;
+ else if (this._props.axes.length > 0) colSelected = this._props.axes[0] === col;
+ if (this._props.titleCol === col) colSelected = true;
return (
<td key={this.columns.indexOf(col)} style={{ border: colSelected ? '3px solid black' : '1px solid black', fontWeight: colSelected ? 'bolder' : 'normal' }}>
<div className="tableBox-cell">{this._props.records[rowId][col]}</div>
@@ -443,10 +459,10 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
</div>
</div>
);
- } else
- return (
- // when it is a brushed table and the incoming table doesn't have any rows selected
- <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div>
- );
+ }
+ return (
+ // when it is a brushed table and the incoming table doesn't have any rows selected
+ <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div>
+ );
}
}
diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
index 336935d23..be05c3529 100644
--- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
+++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
@@ -5,11 +5,11 @@ import { DataPoint } from '../components/LineChart';
export const minMaxRange = (dataPts: DataPoint[][]) => {
// find the max and min of all the data points
- const yMin = d3.min(dataPts, d => d3.min(d, d => Number(d.y)));
- const yMax = d3.max(dataPts, d => d3.max(d, d => Number(d.y)));
+ const yMin = d3.min(dataPts, d => d3.min(d, m => Number(m.y)));
+ const yMax = d3.max(dataPts, d => d3.max(d, m => Number(m.y)));
- const xMin = d3.min(dataPts, d => d3.min(d, d => Number(d.x)));
- const xMax = d3.max(dataPts, d => d3.max(d, d => Number(d.x)));
+ const xMin = d3.min(dataPts, d => d3.min(d, m => Number(m.x)));
+ const xMax = d3.max(dataPts, d => d3.max(d, m => Number(m.x)));
return { xMin, xMax, yMin, yMax };
};
@@ -20,18 +20,15 @@ export const scaleCreatorCategorical = (labels: string[], range: number[]) => {
return scale;
};
-export const scaleCreatorNumerical = (domA: number, domB: number, rangeA: number, rangeB: number) => {
- return d3.scaleLinear().domain([domA, domB]).range([rangeA, rangeB]);
-};
+export const scaleCreatorNumerical = (domA: number, domB: number, rangeA: number, rangeB: number) => d3.scaleLinear().domain([domA, domB]).range([rangeA, rangeB]);
-export const createLineGenerator = (xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) => {
+export const createLineGenerator = (xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) =>
// TODO: nda - look into the different types of curves
- return d3
+ d3
.line<DataPoint>()
.x(d => xScale(d.x))
.y(d => yScale(d.y))
.curve(d3.curveMonotoneX);
-};
export const xAxisCreator = (g: d3.Selection<SVGGElement, unknown, null, undefined>, height: number, xScale: d3.ScaleLinear<number, number, never>) => {
g.attr('class', 'x-axis').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale).tickSize(15));
@@ -48,7 +45,7 @@ export const xGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, he
d3
.axisBottom(scale)
.tickSize(-height)
- .tickFormat((a, b) => '')
+ .tickFormat((/* a, b */) => '')
);
};
@@ -57,10 +54,16 @@ export const yGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, wi
d3
.axisLeft(scale)
.tickSize(-width)
- .tickFormat((a, b) => '')
+ .tickFormat((/* a, b */) => '')
);
};
export const drawLine = (p: d3.Selection<SVGPathElement, unknown, null, undefined>, dataPts: DataPoint[], lineGen: d3.Line<DataPoint>, extra: boolean) => {
- p.datum(dataPts).attr('fill', 'none').attr('stroke', 'rgba(53, 162, 235, 0.5)').attr('stroke-width', 2).attr('stroke', extra? 'blue' : 'black').attr('class', 'line').attr('d', lineGen);
+ p.datum(dataPts)
+ .attr('fill', 'none')
+ .attr('stroke', 'rgba(53, 162, 235, 0.5)')
+ .attr('stroke-width', 2)
+ .attr('stroke', extra ? 'blue' : 'black')
+ .attr('class', 'line')
+ .attr('d', lineGen);
};
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index e729e2fa2..ec9db8480 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,9 +1,11 @@
+/* eslint-disable react/require-default-props */
import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import JsxParser from 'react-jsx-parser';
import * as XRegExp from 'xregexp';
-import { OmitKeys, Without, emptyPath } from '../../../Utils';
+import { OmitKeys } from '../../../ClientUtils';
+import { Without, emptyPath } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
import { AclPrivate, DocData } from '../../../fields/DocSymbols';
import { ScriptField } from '../../../fields/ScriptField';
@@ -17,10 +19,7 @@ import { CollectionView } from '../collections/CollectionView';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { CollectionSchemaView } from '../collections/collectionSchema/CollectionSchemaView';
import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox';
-import { PresElementBox } from '../nodes/trails/PresElementBox';
import { SearchBox } from '../search/SearchBox';
-import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo';
-import { YoutubeBox } from './../../apis/youtube/YoutubeBox';
import { AudioBox } from './AudioBox';
import { ComparisonBox } from './ComparisonBox';
import { DataVizBox } from './DataVizBox/DataVizBox';
@@ -32,7 +31,6 @@ import { FunctionPlotBox } from './FunctionPlotBox';
import { ImageBox } from './ImageBox';
import { KeyValueBox } from './KeyValueBox';
import { LabelBox } from './LabelBox';
-import { LinkAnchorBox } from './LinkAnchorBox';
import { LinkBox } from './LinkBox';
import { LoadingBox } from './LoadingBox';
import { MapBox } from './MapBox/MapBox';
@@ -47,6 +45,7 @@ import { WebBox } from './WebBox';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { ImportElementBox } from './importBox/ImportElementBox';
import { PresBox } from './trails/PresBox';
+import { PresElementBox } from './trails/PresElementBox';
type BindingProps = Without<FieldViewProps, 'fieldKey'>;
export interface JsxBindings {
@@ -71,8 +70,8 @@ interface HTMLtagProps {
children?: JSX.Element[];
}
-//"<HTMLdiv borderRadius='100px' onClick={this.bannerColor=this.bannerColor==='red'?'green':'red'} overflow='hidden' position='absolute' width='100%' height='100%' transform='rotate({2*this.x+this.y}deg)'> <ImageBox {...props} fieldKey={'data'}/> <HTMLspan width='200px' top='0' height='35px' textAlign='center' paddingTop='10px' transform='translate(-40px, 45px) rotate(-45deg)' position='absolute' color='{this.bannerColor===`green`?`light`:`dark`}blue' backgroundColor='{this.bannerColor===`green`?`dark`:`light`}blue'> {this.title}</HTMLspan></HTMLdiv>"
-//"<HTMLdiv borderRadius='100px' overflow='hidden' position='absolute' width='100%' height='100%'
+// "<HTMLdiv borderRadius='100px' onClick={this.bannerColor=this.bannerColor==='red'?'green':'red'} overflow='hidden' position='absolute' width='100%' height='100%' transform='rotate({2*this.x+this.y}deg)'> <ImageBox {...props} fieldKey={'data'}/> <HTMLspan width='200px' top='0' height='35px' textAlign='center' paddingTop='10px' transform='translate(-40px, 45px) rotate(-45deg)' position='absolute' color='{this.bannerColor===`green`?`light`:`dark`}blue' backgroundColor='{this.bannerColor===`green`?`dark`:`light`}blue'> {this.title}</HTMLspan></HTMLdiv>"
+// "<HTMLdiv borderRadius='100px' overflow='hidden' position='absolute' width='100%' height='100%'
// transform='rotate({2*this.x+this.y}deg)'
// onClick = { this.bannerColor = this.bannerColor === 'red' ? 'green' : 'red' } >
// <ImageBox {...props} fieldKey={'data'}/>
@@ -85,22 +84,21 @@ interface HTMLtagProps {
// </HTMLdiv>"
@observer
export class HTMLtag extends React.Component<HTMLtagProps> {
- click = (e: React.MouseEvent) => {
+ click = () => {
const clickScript = (this.props as any).onClick as Opt<ScriptField>;
- clickScript?.script.run({ this: this.props.Document, self: this.props.Document, scale: this.props.scaling });
+ clickScript?.script.run({ this: 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.Document, value: (e.target as any).textContent });
+ onInputScript?.script.run({ this: this.props.Document, value: (e.target as any).textContent });
};
render() {
const style: { [key: string]: any } = {};
const divKeys = OmitKeys(this.props, ['children', 'dragStarting', 'dragEnding', 'htmltag', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit;
- const replacer = (match: any, expr: string, offset: any, string: any) => {
+ const replacer = (match: any, expr: string) =>
// bcz: this executes a script to convert a property expression string: { script } into a value
- return (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.props.Document, this: this.props.Document, scale: this.props.scaling }).result as string) || '';
- };
- Object.keys(divKeys).map((prop: string) => {
+ (ScriptField.MakeFunction(expr, { this: Doc.name, scale: 'number' })?.script.run({ this: this.props.Document, scale: this.props.scaling }).result as string) || '';
+ Object.keys(divKeys).forEach((prop: string) => {
const p = (this.props as any)[prop] as string;
style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer);
});
@@ -122,7 +120,6 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
super(props);
makeObservable(this);
}
-
@computed get layout(): string {
TraceMobx();
if (this._props.LayoutTemplateString) return this._props.LayoutTemplateString;
@@ -158,7 +155,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
'dontCenter',
'DataTransition',
'contextMenuItems',
- //'onClick', // don't need to omit this since it will be set
+ // 'onClick', // don't need to omit this since it will be set
'onDoubleClickScript',
'onPointerDownScript',
'onPointerUpScript',
@@ -187,21 +184,16 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
let layoutFrame = this.layout;
// replace code content with a script >{content}< as in <HTMLdiv>{this.title}</HTMLdiv>
- const replacer = (match: any, prefix: string, expr: string, postfix: string, offset: any, string: any) => {
- return prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this._props.Document }).result as string) || '') + postfix;
- };
+ const replacer = (match: any, prefix: string, expr: string, postfix: string) => prefix + ((ScriptField.MakeFunction(expr, { this: Doc.name })?.script.run({ this: this._props.Document }).result as string) || '') + postfix;
layoutFrame = layoutFrame.replace(/(>[^{]*)[^=]\{([^.'][^<}]+)\}([^}]*<)/g, replacer);
// replace HTML<tag> with corresponding HTML tag as in: <HTMLdiv> becomes <HTMLtag Document={props.Document} htmltag='div'>
- const replacer2 = (match: any, p1: string, offset: any, string: any) => {
- return `<HTMLtag Document={props.Document} scaling='${this._props.NativeDimScaling?.() || 1}' htmltag='${p1}'`;
- };
+ const replacer2 = (match: any, p1: string) => `<HTMLtag Document={props.Document} scaling='${this._props.NativeDimScaling?.() || 1}' htmltag='${p1}'`;
layoutFrame = layoutFrame.replace(/<HTML([a-zA-Z0-9_-]+)/g, replacer2);
// replace /HTML<tag> with </HTMLdiv> as in: </HTMLdiv> becomes </HTMLtag>
- const replacer3 = (match: any, p1: string, offset: any, string: any) => {
- return `</HTMLtag`;
- };
+ const replacer3 = (/* match: any, p1: string, offset: any, string: any */) => `</HTMLtag`;
+
layoutFrame = layoutFrame.replace(/<\/HTML([a-zA-Z0-9_-]+)/g, replacer3);
// add onClick function to props
@@ -211,7 +203,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
const code = XRegExp.matchRecursive(splits[1], '{', '}', '', { valueNames: ['between', 'left', 'match', 'right', 'between'] });
layoutFrame = splits[0] + ` ${func}={props.${func}} ` + splits[1].substring(code[1].end + 1);
const script = code[1].value.replace(/^‘/, '').replace(/’$/, ''); // ‘’ are not valid quotes in javascript so get rid of them -- they may be present to make it easier to write complex scripts - see headerTemplate in currentUserUtils.ts
- return ScriptField.MakeScript(script, { this: Doc.name, self: Doc.name, scale: 'number', value: 'string' });
+ return ScriptField.MakeScript(script, { this: Doc.name, scale: 'number', value: 'string' });
}
return undefined;
// add input function to props
@@ -250,12 +242,9 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
AudioBox,
RecordingBox,
PresBox,
- YoutubeBox,
PresElementBox,
SearchBox,
FunctionPlotBox,
- DashWebRTCVideo,
- LinkAnchorBox,
InkingStroke,
LinkBox,
ScriptingBox,
@@ -272,7 +261,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
}}
bindings={bindings}
jsx={layoutFrame}
- showWarnings={true}
+ showWarnings
onError={(test: any) => {
console.log('DocumentContentsView:' + test, bindings, layoutFrame);
}}
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index 4a22766cc..23d934edb 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -3,12 +3,11 @@ import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { factory } from 'typescript';
-import { Field } from '../../../fields/Doc';
+import { FieldType } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
-import { DocumentManager } from '../../util/DocumentManager';
+import { StrCast } from '../../../fields/Types';
import { Transformer, ts } from '../../util/Scripting';
-import { SettingsManager } from '../../util/SettingsManager';
-import { LightboxView } from '../LightboxView';
+import { SnappingManager } from '../../util/SnappingManager';
import { ObservableReactComponent } from '../ObservableReactComponent';
import { DocumentView } from './DocumentView';
@@ -24,26 +23,23 @@ export class DocumentIcon extends ObservableReactComponent<DocumentIconProps> {
makeObservable(this);
}
- static get DocViews() {
- return LightboxView.LightboxDoc ? DocumentManager.Instance.DocumentViews.filter(v => LightboxView.Contains(v)) : DocumentManager.Instance.DocumentViews;
- }
render() {
- const view = this._props.view;
- const { left, top, right, bottom } = view.getBounds || { left: 0, top: 0, right: 0, bottom: 0 };
+ const { view } = this._props;
+ const { left, top, right } = view.getBounds || { left: 0, top: 0, right: 0, bottom: 0 };
return (
<div
className="documentIcon-outerDiv"
- onPointerEnter={action(e => (this._hovered = true))}
- onPointerLeave={action(e => (this._hovered = false))}
+ onPointerEnter={action(() => { this._hovered = true; })} // prettier-ignore
+ onPointerLeave={action(() => { this._hovered = false; })} // prettier-ignore
style={{
pointerEvents: 'all',
opacity: this._hovered ? 0.3 : 1,
position: 'absolute',
- background: SettingsManager.userBackgroundColor,
+ background: SnappingManager.userBackgroundColor,
transform: `translate(${(left + right) / 2}px, ${top}px)`,
}}>
- <Tooltip title={<>{this._props.view.Document.title}</>}>
+ <Tooltip title={<div>{StrCast(this._props.view.Document?.title)}</div>}>
<p>d{this._props.index}</p>
</Tooltip>
</div>
@@ -56,40 +52,40 @@ export class DocumentIconContainer extends React.Component {
public static getTransformer(): Transformer {
const usedDocuments = new Set<number>();
return {
- transformer: context => {
- return root => {
- function visit(node: ts.Node) {
- node = ts.visitEachChild(node, visit, context);
+ transformer: context => root => {
+ function visit(nodeIn: ts.Node) {
+ const node = ts.visitEachChild(nodeIn, visit, context);
- if (ts.isIdentifier(node)) {
- const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
- const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
- const isntParameter = !ts.isParameter(node.parent);
- if (isntPropAccess && isntPropAssign && isntParameter && !(node.text in globalThis)) {
- const match = node.text.match(/d([0-9]+)/);
- if (match) {
- const m = parseInt(match[1]);
- const doc = DocumentIcon.DocViews[m].Document;
- usedDocuments.add(m);
- return factory.createIdentifier(`idToDoc("${doc[Id]}")`);
- }
+ if (ts.isIdentifier(node)) {
+ const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
+ const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
+ const isntParameter = !ts.isParameter(node.parent);
+ if (isntPropAccess && isntPropAssign && isntParameter && !(node.text in globalThis)) {
+ const match = node.text.match(/d([0-9]+)/);
+ if (match) {
+ const m = parseInt(match[1]);
+ const doc = DocumentView.allViews()[m].Document;
+ usedDocuments.add(m);
+ return factory.createIdentifier(`idToDoc("${doc[Id]}")`);
}
}
-
- return node;
}
- return ts.visitNode(root, visit);
- };
+
+ return node;
+ }
+ return ts.visitNode(root, visit);
},
getVars() {
- const docs = DocumentIcon.DocViews;
- const capturedVariables: { [name: string]: Field } = {};
- usedDocuments.forEach(index => (capturedVariables[`d${index}`] = docs.length > index ? docs[index].Document : `d${index}`));
+ const docs = DocumentView.allViews();
+ const capturedVariables: { [name: string]: FieldType } = {};
+ usedDocuments.forEach(index => {
+ capturedVariables[`d${index}`] = docs.length > index ? docs[index].Document : `d${index}`;
+ });
return capturedVariables;
},
};
}
render() {
- return DocumentIcon.DocViews.map((dv, i) => <DocumentIcon key={i} index={i} view={dv} />);
+ return DocumentView.allViews().map((dv, i) => <DocumentIcon key={dv.DocUniqueId} index={i} view={dv} />);
}
}
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 2a68d2bf6..0c5156339 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -1,23 +1,23 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { StopEvent, emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { StopEvent, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc } from '../../../fields/Doc';
-import { StrCast } from '../../../fields/Types';
-import { DocUtils } from '../../documents/Documents';
+import { DocUtils } from '../../documents/DocUtils';
import { DragManager } from '../../util/DragManager';
-import { Hypothesis } from '../../util/HypothesisUtils';
import { LinkManager } from '../../util/LinkManager';
import { UndoManager, undoBatch } from '../../util/UndoManager';
import { ObservableReactComponent } from '../ObservableReactComponent';
+import { PinProps } from '../PinFuncs';
import './DocumentLinksButton.scss';
import { DocumentView } from './DocumentView';
import { LinkDescriptionPopup } from './LinkDescriptionPopup';
import { TaskCompletionBox } from './TaskCompletedBox';
-import { PinProps } from './trails';
-import { DocData } from '../../../fields/DocSymbols';
interface DocumentLinksButtonProps {
View: DocumentView;
@@ -25,20 +25,22 @@ interface DocumentLinksButtonProps {
AlwaysOn?: boolean;
InMenu?: boolean;
OnHover?: boolean;
- StartLink?: boolean; //whether the link HAS been started (i.e. now needs to be completed)
+ StartLink?: boolean; // whether the link HAS been started (i.e. now needs to be completed)
ShowCount?: boolean;
scaling?: () => number; // how uch doc is scaled so that link buttons can invert it
hideCount?: () => boolean;
}
export class DocButtonState {
- @observable public StartLink: Doc | undefined = undefined; //origin's Doc, if defined
+ @observable public StartLink: Doc | undefined = undefined; // origin's Doc, if defined
@observable public StartLinkView: DocumentView | undefined = undefined;
@observable public AnnotationId: string | undefined = undefined;
@observable public AnnotationUri: string | undefined = undefined;
@observable public LinkEditorDocView: DocumentView | undefined = undefined;
+ // eslint-disable-next-line no-use-before-define
public static _instance: DocButtonState | undefined;
public static get Instance() {
+ // eslint-disable-next-line no-return-assign
return DocButtonState._instance ?? (DocButtonState._instance = new DocButtonState());
}
constructor() {
@@ -49,7 +51,7 @@ export class DocButtonState {
export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksButtonProps> {
private _linkButton = React.createRef<HTMLDivElement>();
public static get StartLink() { return DocButtonState.Instance.StartLink; } // prettier-ignore
- public static set StartLink(value) { runInAction(() => (DocButtonState.Instance.StartLink = value)); } // prettier-ignore
+ public static set StartLink(value) { runInAction(() => {DocButtonState.Instance.StartLink = value}); } // prettier-ignore
@observable public static StartLinkView: DocumentView | undefined = undefined;
@observable public static AnnotationId: string | undefined = undefined;
@observable public static AnnotationUri: string | undefined = undefined;
@@ -87,12 +89,14 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
e,
this.onLinkButtonMoved,
emptyFunction,
- action((e, doubleTap) => {
+ action((clickEv, doubleTap) => {
doubleTap && DocumentView.showBackLinks(this._props.View.Document);
}),
undefined,
undefined,
- action(() => (DocButtonState.Instance.LinkEditorDocView = this._props.View))
+ action(() => {
+ DocButtonState.Instance.LinkEditorDocView = this._props.View;
+ })
);
};
@@ -102,9 +106,9 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
e,
this.onLinkButtonMoved,
emptyFunction,
- action((e, doubleTap) => {
+ action((clickEv, doubleTap) => {
if (doubleTap && this._props.InMenu && this._props.StartLink) {
- //action(() => Doc.BrushDoc(this._props.View.Document));
+ // action(() => Doc.BrushDoc(this._props.View.Document));
if (DocumentLinksButton.StartLink === this._props.View.Document) {
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
@@ -118,7 +122,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
};
@undoBatch
- onLinkClick = (e: React.MouseEvent): void => {
+ onLinkClick = (): void => {
if (this._props.InMenu && this._props.StartLink) {
DocumentLinksButton.AnnotationId = undefined;
DocumentLinksButton.AnnotationUri = undefined;
@@ -126,7 +130,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
} else {
- //if this LinkButton's Document is undefined
+ // if this LinkButton's Document is undefined
DocumentLinksButton.StartLink = this._props.View.Document;
DocumentLinksButton.StartLinkView = this._props.View;
}
@@ -139,7 +143,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
e,
returnFalse,
emptyFunction,
- action(e => DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this._props.View.Document, true, this._props.View))
+ action(clickEv => DocumentLinksButton.finishLinkClick(clickEv.clientX, clickEv.clientY, DocumentLinksButton.StartLink, this._props.View.Document, true, this._props.View))
);
};
@@ -151,25 +155,17 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
DocumentLinksButton.StartLinkView = undefined;
DocumentLinksButton.AnnotationId = undefined;
DocumentLinksButton.AnnotationUri = undefined;
- //!this._props.StartLink
+ // !this._props.StartLink
} else if (startLink !== endLink) {
+ // eslint-disable-next-line no-param-reassign
endLink = endLinkView?.ComponentView?.getAnchor?.(true, pinProps) || endLink;
+ // eslint-disable-next-line no-param-reassign
startLink = DocumentLinksButton.StartLinkView?.ComponentView?.getAnchor?.(true) || startLink;
const linkDoc = DocUtils.MakeLink(startLink, endLink, { link_relationship: DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined });
LinkManager.Instance.currentLink = linkDoc;
if (linkDoc) {
- if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
- // if linking from a Hypothes.is annotation
- const linkDocData = linkDoc[DocData];
- linkDocData.linksToAnnotation = true;
- linkDocData.annotationId = DocumentLinksButton.AnnotationId;
- linkDocData.annotationUri = DocumentLinksButton.AnnotationUri;
- const dashHyperlink = Doc.globalServerPath(startIsAnnotation ? endLink : startLink);
- Hypothesis.makeLink(StrCast(startIsAnnotation ? endLink.title : startLink.title), dashHyperlink, DocumentLinksButton.AnnotationId, startIsAnnotation ? startLink : endLink); // edit annotation to add a Dash hyperlink to the linked doc
- }
-
TaskCompletionBox.textDisplayed = 'Link Created';
TaskCompletionBox.popupX = screenX;
TaskCompletionBox.popupY = screenY - 133;
@@ -192,7 +188,9 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
}
setTimeout(
- action(() => (TaskCompletionBox.taskCompleted = false)),
+ action(() => {
+ TaskCompletionBox.taskCompleted = false;
+ }),
2500
);
}
@@ -242,13 +240,13 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
showLinkCount(this._props.OnHover, this._props.Bottom)
) : (
<div className="documentLinksButton-menu">
- {this._props.StartLink ? ( //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
+ {this._props.StartLink ? ( // if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
<div className={`documentLinksButton ${isActive ? `startLink` : ``}`} ref={this._linkButton} onPointerDown={isActive ? StopEvent : this.onLinkButtonDown} onClick={isActive ? this.clearLinks : this.onLinkClick}>
<FontAwesomeIcon className="documentdecorations-icon" icon="link" />
</div>
) : null}
- {!this._props.StartLink && DocumentLinksButton.StartLink !== this._props.View.Document ? ( //if the origin node is not this node
- <div className={'documentLinksButton-endLink'} ref={this._linkButton} onPointerDown={DocumentLinksButton.StartLink && this.completeLink}>
+ {!this._props.StartLink && DocumentLinksButton.StartLink !== this._props.View.Document ? ( // if the origin node is not this node
+ <div className="documentLinksButton-endLink" ref={this._linkButton} onPointerDown={DocumentLinksButton.StartLink && this.completeLink}>
<FontAwesomeIcon className="documentdecorations-icon" icon="link" />
</div>
) : null}
@@ -262,7 +260,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
const buttonTitle = 'Tap to view links; double tap to open link collection';
const title = this._props.ShowCount ? buttonTitle : menuTitle;
- //render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu
+ // render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu
return !Array.from(this.filteredLinks).length && !this._props.AlwaysOn ? null : (
<div
className="documentLinksButton-wrapper"
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 21c7f3079..3191e04db 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,12 +1,16 @@
+/* eslint-disable no-use-before-define */
+/* eslint-disable react/jsx-props-no-spreading */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { Howl } from 'howler';
import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal';
-import { DivWidth, Utils, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../Utils';
-import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
-import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols';
+import { ClientUtils, DivWidth, isTargetChildOf as isParentOf, lightOrDark, returnFalse, returnVal, simulateMouseClick } from '../../../ClientUtils';
+import { Utils, emptyFunction, emptyPath } from '../../../Utils';
+import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc';
+import { AclAdmin, AclEdit, AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
@@ -16,22 +20,17 @@ import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { AudioField } from '../../../fields/URLField';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
+import { AudioAnnoState } from '../../../server/SharedMediaTypes';
import { DocServer } from '../../DocServer';
-import { Networking } from '../../Network';
-import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
+import { DocUtils, FollowLinkScript } from '../../documents/DocUtils';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DocUtils, Docs } from '../../documents/Documents';
-import { DictationManager } from '../../util/DictationManager';
-import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { Docs } from '../../documents/Documents';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { MakeTemplate, makeUserTemplateButton } from '../../util/DropConverter';
-import { FollowLinkScript } from '../../util/LinkFollower';
-import { LinkManager } from '../../util/LinkManager';
+import { UPDATE_SERVER_CACHE } from '../../util/LinkManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SearchUtil } from '../../util/SearchUtil';
-import { SelectionManager } from '../../util/SelectionManager';
-import { SettingsManager } from '../../util/SettingsManager';
-import { SharingManager } from '../../util/SharingManager';
import { SnappingManager } from '../../util/SnappingManager';
import { UndoManager, undoBatch, undoable } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
@@ -39,53 +38,18 @@ import { ContextMenuProps } from '../ContextMenuItem';
import { DocComponent, ViewBoxInterface } from '../DocComponent';
import { EditableView } from '../EditableView';
import { FieldsDropdown } from '../FieldsDropdown';
-import { GestureOverlay } from '../GestureOverlay';
import { LightboxView } from '../LightboxView';
-import { AudioAnnoState, StyleProp } from '../StyleProvider';
+import { PinProps } from '../PinFuncs';
+import { StyleProp } from '../StyleProp';
import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView';
import { DocumentLinksButton } from './DocumentLinksButton';
import './DocumentView.scss';
import { FieldViewProps, FieldViewSharedProps } from './FieldView';
-import { KeyValueBox } from './KeyValueBox';
-import { LinkAnchorBox } from './LinkAnchorBox';
+import { FocusViewOptions } from './FocusViewOptions';
+import { OpenWhere } from './OpenWhere';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { PresEffect, PresEffectDirection } from './trails';
-interface Window {
- MediaRecorder: MediaRecorder;
-}
-declare class MediaRecorder {
- constructor(e: any); // whatever MediaRecorder has
-}
-export enum OpenWhereMod {
- none = '',
- left = 'left',
- right = 'right',
- top = 'top',
- bottom = 'bottom',
- keyvalue = 'keyValue',
-}
-export enum OpenWhere {
- lightbox = 'lightbox',
- add = 'add',
- addLeft = 'add:left',
- addRight = 'add:right',
- addBottom = 'add:bottom',
- close = 'close',
- toggle = 'toggle',
- toggleRight = 'toggle:right',
- replace = 'replace',
- replaceRight = 'replace:right',
- replaceLeft = 'replace:left',
- inParent = 'inParent',
- inParentFromScreen = 'inParentFromScreen',
- overlay = 'overlay',
- addRightKeyvalue = 'add:right:keyValue',
-}
-
-export function returnEmptyDocViewList() {
- return [] as DocumentView[];
-}
export interface DocumentViewProps extends FieldViewSharedProps {
hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected
hideResizeHandles?: boolean; // whether to suppress resized handles on doc decorations when this document is selected
@@ -112,17 +76,20 @@ export interface DocumentViewProps extends FieldViewSharedProps {
dragConfig?: (data: DragManager.DocumentDragData) => void;
dragStarting?: () => void;
dragEnding?: () => void;
+
+ parent?: any;
}
@observer
export class DocumentViewInternal extends DocComponent<FieldViewProps & DocumentViewProps>() {
// this makes mobx trace() statements more descriptive
public get displayName() { return 'DocumentViewInternal(' + this.Document.title + ')'; } // prettier-ignore
public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered.
+
/**
* This function is filled in by MainView to allow non-viewBox views to add Docs as tabs without
* needing to know about/reference MainView
*/
- public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse;
+ public static addDocTabFunc: (doc: Doc | Doc[], location: OpenWhere) => boolean = returnFalse;
private _disposers: { [name: string]: IReactionDisposer } = {};
private _doubleClickTimeout: NodeJS.Timeout | undefined;
@@ -146,7 +113,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
@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 )
@observable _isContentActive: boolean | undefined = undefined;
@observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined = undefined;
- @observable _componentView: Opt<ViewBoxInterface> = undefined; // needs to be accessed from DocumentView wrapper class
+ @observable _componentView: Opt<ViewBoxInterface<FieldViewProps>> = undefined; // needs to be accessed from DocumentView wrapper class
@observable _animateScaleTime: Opt<number> = undefined; // milliseconds for animating between views. defaults to 300 if not uset
@observable _animateScalingTo = 0;
@@ -169,7 +136,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
@computed get borderPath() { return this.style(this.Document, StyleProp.BorderPath); } // prettier-ignore
@computed get onClickHandler() {
- return this._props.onClickScript?.() ?? this._props.onBrowseClickScript?.() ?? ScriptCast(this.Document.onClick, ScriptCast(this.layoutDoc.onClick));
+ return this._props.onClickScript?.() ?? ScriptCast(this.Document.onClick, ScriptCast(this.layoutDoc.onClick));
}
@computed get onDoubleClickHandler() {
return this._props.onDoubleClickScript?.() ?? ScriptCast(this.layoutDoc.onDoubleClick, ScriptCast(this.Document.onDoubleClick));
@@ -183,23 +150,23 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
@computed get disableClickScriptFunc() {
const onScriptDisable = this._props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
- // prettier-ignore
return (
+ // eslint-disable-next-line no-use-before-define
DocumentView.LongPress ||
onScriptDisable === 'always' ||
(onScriptDisable !== 'never' && (this.rootSelected() || this._componentView?.isAnyChildContentActive?.()))
- );
+ ); // prettier-ignore
}
@computed get _rootSelected() {
return this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
}
- /// 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
+ /// disable pointer events on content when there's an enabled onClick script (and not in explore mode) and the contents aren't forced active, or if contents are marked inactive
@computed get _contentPointerEvents() {
TraceMobx();
return this._props.contentPointerEvents ??
((!this.disableClickScriptFunc && //
this.onClickHandler &&
- !this._props.onBrowseClickScript?.() &&
+ !SnappingManager.ExploreMode &&
!this.layoutDoc.layout_isSvg &&
this.isContentActive() !== true) ||
this.isContentActive() === false)
@@ -211,11 +178,10 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
// anchors that are not rendered as DocumentViews (marked as 'layout_unrendered' with their 'annotationOn' set to this document). e.g.,
// - PDF text regions are rendered as an Annotations without generating a DocumentView, '
// - RTF selections are rendered via Prosemirror and have a mark which contains the Document ID for the annotation link
- // - and links to PDF/Web docs at a certain scroll location never create an explicit view.
- // For each of these, we create LinkAnchorBox's on the border of the DocumentView.
+ // - and links to PDF/Web docs at a certain scroll location never create an explicit anchor view.
@computed get directLinks() {
TraceMobx();
- return LinkManager.Instance.getAllRelatedLinks(this.Document).filter(
+ return Doc.Links(this.Document).filter(
link =>
(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)) ||
@@ -225,11 +191,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}
@computed get _allLinks(): Doc[] {
TraceMobx();
- return LinkManager.Instance.getAllRelatedLinks(this.Document).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document);
+ return Doc.Links(this.Document).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document);
}
@computed get filteredLinks() {
- return DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
+ return DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []);
}
componentWillUnmount() {
@@ -237,7 +203,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}
componentDidMount() {
- runInAction(() => (this._mounted = true));
+ runInAction(() => {
+ this._mounted = true;
+ });
this.setupHandlers();
this._disposers.contentActive = reaction(
() =>
@@ -249,19 +217,23 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
: Doc.ActiveTool !== InkTool.None || SnappingManager.CanEmbed || this.rootSelected() || this.Document.forceActive || this._componentView?.isAnyChildContentActive?.() || this._props.isContentActive()
? true
: undefined,
- active => (this._isContentActive = active),
+ active => {
+ this._isContentActive = active;
+ },
{ fireImmediately: true }
);
this._disposers.pointerevents = reaction(
() => this.style(this.Document, StyleProp.PointerEvents),
- pointerevents => (this._pointerEvents = pointerevents),
+ pointerevents => {
+ this._pointerEvents = pointerevents;
+ },
{ fireImmediately: true }
);
}
preDrop = (e: Event, de: DragManager.DropEvent, dropAction: dropActionType) => {
const dragData = de.complete.docDragData;
if (dragData && this.isContentActive() && !this.props.dontRegisterView) {
- dragData.dropAction = dropAction ? dropAction : dragData.dropAction;
+ dragData.dropAction = dropAction || dragData.dropAction;
e.stopPropagation();
}
};
@@ -281,7 +253,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
const docView = this._docView;
if (this._mainCont.current && docView) {
- const views = SelectionManager.Views.filter(dv => dv.ContentDiv);
+ const views = DocumentView.Selected().filter(dv => dv.ContentDiv);
const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [docView];
const dragData = new DragManager.DocumentDragData(selected.map(dv => dv.Document));
const screenXf = docView.screenToViewTransform();
@@ -290,8 +262,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
dragData.dropAction = dropAction;
dragData.removeDocument = this._props.removeDocument;
dragData.moveDocument = this._props.moveDocument;
- dragData.draggedViews = [docView];
- dragData.canEmbed = this.Document.dragAction ?? this._props.dragAction ? true : false;
+ dragData.dragEnding = () => docView.props.dragEnding?.();
+ dragData.dragStarting = () => docView.props.dragStarting?.();
+ dragData.canEmbed = !!(this.Document.dragAction ?? this._props.dragAction);
(this._props.dragConfig ?? this._componentView?.dragConfig)?.(dragData);
DragManager.StartDocumentDrag(
selected.map(dv => dv.ContentDiv!),
@@ -308,10 +281,25 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if (!StrCast(this.layoutDoc._layout_showTitle)) this.layoutDoc._layout_showTitle = 'title';
setTimeout(() => this._titleRef.current?.setIsFocused(true)); // use timeout in case title wasn't shown to allow re-render so that titleref will be defined
};
+ onBrowseClick = (e: React.MouseEvent) => {
+ const browseTransitionTime = 500;
+ DocumentView.DeselectAll();
+ DocumentView.showDocument(this.Document, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
+ const options: FocusViewOptions = { pointFocus: { X: e.clientX, Y: e.clientY }, zoomTime: browseTransitionTime };
+ if (!focused && this._docView) {
+ this._docView
+ .docViewPath()
+ .reverse()
+ .forEach(cont => cont.ComponentView?.focus?.(cont.Document, options));
+ Doc.linkFollowHighlight(this.Document, false);
+ }
+ });
+ e.stopPropagation();
+ };
onClick = action((e: React.MouseEvent | React.PointerEvent) => {
if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
const documentView = this._docView;
- if (documentView && !this.Document.ignoreClick && this._props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
+ if (documentView && !this.Document.ignoreClick && this._props.renderDepth >= 0 && ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
let stopPropagate = true;
let preventDefault = true;
!this.layoutDoc._keepZWhenDragged && this._props.bringToFront?.(this.Document);
@@ -333,7 +321,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
UndoManager.RunInBatch(() => this.onDoubleClickHandler.script.run(scriptProps, console.log).result?.select && this._props.select(false), 'on double click: ' + this.Document.title);
} else if (!Doc.IsSystem(this.Document) && defaultDblclick !== 'ignore') {
UndoManager.RunInBatch(() => LightboxView.Instance.AddDocTab(this.Document, OpenWhere.lightbox), 'double tap');
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
Doc.UnBrushDoc(this.Document);
} else {
this._singleClickFunc?.();
@@ -368,6 +356,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') {
this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300);
+ // eslint-disable-next-line no-use-before-define
} else if (!DocumentView.LongPress) {
this._singleClickFunc();
this._singleClickFunc = undefined;
@@ -380,8 +369,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
onPointerDown = (e: React.PointerEvent): void => {
if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
- this._longPressSelector = setTimeout(() => (DocumentView.LongPress && this._props.select(false), 1000));
- if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._docView;
+ // eslint-disable-next-line no-use-before-define
+ this._longPressSelector = setTimeout(() => DocumentView.LongPress && this._props.select(false), 1000);
+ if (!DocumentView.DownDocView) DocumentView.DownDocView = this._docView;
this._downX = e.clientX;
this._downY = e.clientY;
@@ -389,10 +379,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
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.onBrowseClickScript?.() &&
+ if ((this._props.isDocumentActive?.() || this._props.isContentActive?.()) &&
+ !SnappingManager.ExploreMode &&
!this.Document.ignoreClick &&
e.button === 0 &&
!Doc.IsInMyOverlay(this.layoutDoc)
@@ -404,7 +392,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || BoolCast(this.layoutDoc._dragWhenActive, this._props.dragWhenActive))) {
document.addEventListener('pointermove', this.onPointerMove);
}
- }
+ } // prettier-ignore
document.addEventListener('pointerup', this.onPointerUp);
}
};
@@ -412,7 +400,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
onPointerMove = (e: PointerEvent): void => {
if (e.buttons !== 1 || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return;
- if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
+ if (!ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
this.cleanupPointerEvents();
this._longPressSelector && clearTimeout(this._longPressSelector);
this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && dropActionType.embed) || ((this.Document.dragAction || this._props.dragAction || undefined) as dropActionType));
@@ -430,14 +418,15 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if (this.onPointerUpHandler?.script) {
this.onPointerUpHandler.script.run({ this: this.Document }, console.log);
- } else if (e.button === 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
- this._doubleTap = (this.onDoubleClickHandler?.script || this.Document.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < Utils.CLICK_TIME;
+ } else if (e.button === 0 && ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
+ this._doubleTap = (this.onDoubleClickHandler?.script || this.Document.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < ClientUtils.CLICK_TIME;
if (!this.isContentActive()) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected
}
+ // eslint-disable-next-line no-use-before-define
if (DocumentView.LongPress) e.preventDefault();
};
- toggleFollowLink = undoable((zoom?: boolean, setTargetToggle?: boolean): void => {
+ toggleFollowLink = undoable((): void => {
const hadOnClick = this.Document.onClick;
this.noOnClick();
this.Document.onClick = hadOnClick ? undefined : FollowLinkScript();
@@ -458,19 +447,17 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}, 'default on click');
deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc');
- setToggleDetail = undoable(
- (scriptFieldKey: 'onClick') =>
- (this.Document[scriptFieldKey] = ScriptField.MakeScript(
- `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
- .replace('layout_', '')
- .replace(/^layout$/, 'detail')}")`,
- { documentView: 'any' }
- )),
- 'set toggle detail'
- );
+ setToggleDetail = undoable((scriptFieldKey: 'onClick') => {
+ this.Document[scriptFieldKey] = ScriptField.MakeScript(
+ `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
+ .replace('layout_', '')
+ .replace(/^layout$/, 'detail')}")`,
+ { documentView: 'any' }
+ );
+ }, 'set toggle detail');
drop = undoable((e: Event, de: DragManager.DropEvent) => {
- if (this._props.dontRegisterView || this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
+ if (this._props.dontRegisterView) return false;
if (this.Document === Doc.ActiveDashboard) {
e.stopPropagation();
e.preventDefault();
@@ -491,7 +478,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if (linkDoc) {
de.complete.linkDocument = linkDoc;
linkDoc.layout_isSvg = true;
- DocumentManager.LinkCommonAncestor(linkDoc)?.ComponentView?.addDocument?.(linkDoc);
+ DocumentView.linkCommonAncestor(linkDoc)?.ComponentView?.addDocument?.(linkDoc);
}
}
e.stopPropagation();
@@ -505,7 +492,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
const input = document.createElement('input');
input.type = 'file';
input.accept = '.zip';
- input.onchange = _e => {
+ input.onchange = () => {
if (input.files) {
const batch = UndoManager.StartBatch('importing');
Doc.importDocument(input.files[0]).then(doc => {
@@ -523,7 +510,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if (e && this.layoutDoc.layout_hideContextMenu && Doc.noviceMode) {
e.preventDefault();
e.stopPropagation();
- //!this._props.isSelected(true) && SelectionManager.SelectView(this.DocumentView(), false);
+ // !this._props.isSelected(true) && DocumentView.SelectView(this.DocumentView(), false);
}
// the touch onContextMenu is button 0, the pointer onContextMenu is button 2
if (e) {
@@ -535,7 +522,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
e.stopPropagation();
e.persist();
- if (!navigator.userAgent.includes('Mozilla') && (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3)) {
+ if (!navigator.userAgent.includes('Mozilla') && (Math.abs(this._downX - (e?.clientX ?? 0)) > 3 || Math.abs(this._downY - (e?.clientY ?? 0)) > 3)) {
return;
}
}
@@ -583,11 +570,15 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if (this._props.bringToFront) {
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.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: 'Bring to Front', event: () => DocumentView.Selected().forEach(dv => dv._props.bringToFront?.(dv.Document, false)), icon: 'arrow-up' });
+ zorderItems.push({ description: 'Send to Back', event: () => DocumentView.Selected().forEach(dv => dv._props.bringToFront?.(dv.Document, true)), icon: 'arrow-down' });
zorderItems.push({
description: !this.layoutDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged',
- event: undoBatch(action(() => (this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged))),
+ event: undoBatch(
+ action(() => {
+ this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged;
+ })
+ ),
icon: 'hand-point-up',
});
!zorders && cm.addItem({ description: 'Z Order...', addDivider: true, noexpand: true, subitems: zorderItems, icon: 'layer-group' });
@@ -597,14 +588,14 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
const existingOnClick = cm.findByDescription('OnClick...');
const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
- onClicks.push({ description: 'Enter Portal', event: undoable(e => DocUtils.makeIntoPortal(this.Document, this.layoutDoc, this._allLinks), 'make into portal'), icon: 'window-restore' });
+ onClicks.push({ description: 'Enter Portal', event: undoable(() => DocUtils.makeIntoPortal(this.Document, this.layoutDoc, this._allLinks), 'make into portal'), icon: 'window-restore' });
!Doc.noviceMode && onClicks.push({ description: 'Toggle Detail', event: this.setToggleDetail, icon: 'concierge-bell' });
if (!this.Document.annotationOn) {
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.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) {
+ } else if (Doc.Links(this.Document).length) {
onClicks.push({ description: 'Restore On Click default', event: () => this.noOnClick(), icon: 'link' });
onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' });
!existingOnClick && cm.addItem({ description: 'OnClick...', subitems: onClicks, icon: 'mouse-pointer' });
@@ -613,9 +604,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
const funcs: ContextMenuProps[] = [];
if (!Doc.noviceMode && this.layoutDoc.onDragStart) {
- funcs.push({ description: 'Drag an Embedding', icon: 'edit', event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getEmbedding(this.dragFactory)')) });
- funcs.push({ description: 'Drag a Copy', icon: 'edit', event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
- funcs.push({ description: 'Drag Document', icon: 'edit', event: () => (this.layoutDoc.onDragStart = undefined) });
+ funcs.push({ description: 'Drag an Embedding', icon: 'edit', event: () => { this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getEmbedding(this.dragFactory)')); } }); // prettier-ignore
+ funcs.push({ description: 'Drag a Copy', icon: 'edit', event: () => { this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')); } }); // prettier-ignore
+ funcs.push({ description: 'Drag Document', icon: 'edit', event: () => { this.layoutDoc.onDragStart = undefined; } }); // prettier-ignore
cm.addItem({ description: 'OnDrag...', noexpand: true, subitems: funcs, icon: 'asterisk' });
}
@@ -624,14 +615,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if (!Doc.IsSystem(this.Document)) {
if (!Doc.noviceMode) {
moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this.Document, this._props.TemplateDataDocument), icon: 'concierge-bell' });
- moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => (this.Document._chromeHidden = !this.Document._chromeHidden), icon: 'project-diagram' });
-
- if (Cast(Doc.GetProto(this.Document).data, listSpec(Doc))) {
- moreItems.push({ description: 'Export to Google Photos Album', event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.Document }).then(console.log), icon: 'caret-square-right' });
- moreItems.push({ description: 'Tag Child Images via Google Photos', event: () => GooglePhotos.Query.TagChildImages(this.Document), icon: 'caret-square-right' });
- moreItems.push({ description: 'Write Back Link to Album', event: () => GooglePhotos.Transactions.AddTextEnrichment(this.Document), icon: 'caret-square-right' });
- }
- moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this.Document)), icon: 'fingerprint' });
+ moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => { this.Document._chromeHidden = !this.Document._chromeHidden; }, icon: 'project-diagram' }); // prettier-ignore
+ moreItems.push({ description: 'Copy ID', event: () => ClientUtils.CopyText(Doc.globalServerPath(this.Document)), icon: 'fingerprint' });
}
}
@@ -639,8 +624,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}
const constantItems: ContextMenuProps[] = [];
if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking) {
- constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.Document) });
- (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this._docView), icon: 'users' });
+ constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => DocUtils.Zip(this.Document) });
+ constantItems.push({ description: 'Share', event: () => DocumentView.ShareOpen(this._docView), icon: 'users' });
if (this._props.removeDocument && Doc.ActiveDashboard !== this.Document) {
// need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
@@ -655,8 +640,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
!Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.Document), icon: 'hand-point-right' });
!Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.dataDoc), icon: 'hand-point-right' });
- let documentationDescription: string | undefined = undefined;
- let documentationLink: string | undefined = undefined;
+ let documentationDescription: string | undefined;
+ let documentationLink: string | undefined;
switch (this.Document.type) {
case DocumentType.COL:
documentationDescription = 'See collection documentation';
@@ -690,6 +675,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
documentationDescription = 'See DataViz node documentation';
documentationLink = 'https://brown-dash.github.io/Dash-Documentation/documents/dataViz/';
break;
+ default:
}
// Add link to help documentation (unless the doc contents have been overriden in which case the documentation isn't relevant)
if (!this.docContents && documentationDescription && documentationLink) {
@@ -710,8 +696,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
panelHeight = () => this._props.PanelHeight() - this.headerMargin;
screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHandler;
- setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height));
- setContentView = action((view: ViewBoxInterface) => (this._componentView = view));
+ setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height)); } // prettier-ignore
+ setContentView = action((view: ViewBoxInterface<FieldViewProps>) => { this._componentView = view; }); // prettier-ignore
isContentActive = (): boolean | undefined => this._isContentActive;
childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
@@ -726,44 +712,18 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
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?.() ?? [], []);
return filtered.some(link => link._link_displayArrow) ? 0 : undefined;
}
+ default:
}
return this._props.styleProvider?.(doc, props, property);
};
- removeLinkByHiding = (link: Doc) => () => (link.link_displayLine = false);
- @computed get allLinkEndpoints() {
- // the small blue dots that mark the endpoints of links
- if (this._componentView instanceof KeyValueBox || this._props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this._props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
- return this.filteredLinks.map(link => (
- <div className="documentView-anchorCont" key={link[Id]}>
- <DocumentView
- {...this._props}
- isContentActive={returnFalse}
- Document={link}
- containerViewPath={this._props.docViewPath}
- PanelWidth={this.anchorPanelWidth}
- PanelHeight={this.anchorPanelHeight}
- dontRegisterView={false}
- layout_showTitle={returnEmptyString}
- hideCaptions={true}
- hideLinkAnchors={true}
- layout_fitWidth={returnTrue}
- removeDocument={this.removeLinkByHiding(link)}
- styleProvider={this.anchorStyleProvider}
- LayoutTemplate={undefined}
- LayoutTemplateString={LinkAnchorBox.LayoutString(`link_anchor_${LinkManager.anchorIndex(link, this.Document)}`)}
- />
- </div>
- ));
- }
-
@computed get viewBoxContents() {
TraceMobx();
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');
+ const noBackground = this.Document.isGroup && !this._componentView?.isUnstyledView?.() && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
return (
<div
className="documentView-contentsView"
@@ -786,38 +746,35 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
setTitleFocus={this.setTitleFocus}
hideClickBehaviors={BoolCast(this.Document.hideClickBehaviors)}
/>
- {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints}
</div>
);
}
captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
- fieldsDropdown = (placeholder: string) => {
- return (
- <div
- ref={action((r: any) => r && (this._titleDropDownInnerWidth = DivWidth(r)))}
- onPointerDown={action(e => (this._changingTitleField = true))}
- style={{ width: 'max-content', background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}>
- <FieldsDropdown
- Document={this.Document}
- placeholder={placeholder}
- selectFunc={action((field: string | number) => {
- if (this.layoutDoc.layout_showTitle) {
- this.layoutDoc._layout_showTitle = field;
- } else if (!this._props.layout_showTitle) {
- Doc.UserDoc().layout_showTitle = field;
- }
- this._changingTitleField = false;
- })}
- menuClose={action(() => (this._changingTitleField = false))}
- />
- </div>
- );
- };
+ fieldsDropdown = (placeholder: string) => (
+ <div
+ ref={action((r: any) => { r && (this._titleDropDownInnerWidth = DivWidth(r));} )} // prettier-ignore
+ onPointerDown={action(() => { this._changingTitleField = true; })} // prettier-ignore
+ style={{ width: 'max-content', background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}>
+ <FieldsDropdown
+ Document={this.Document}
+ placeholder={placeholder}
+ selectFunc={action((field: string | number) => {
+ if (this.layoutDoc.layout_showTitle) {
+ this.layoutDoc._layout_showTitle = field;
+ } else if (!this._props.layout_showTitle) {
+ Doc.UserDoc().layout_showTitle = field;
+ }
+ this._changingTitleField = false;
+ })}
+ menuClose={action(() => { this._changingTitleField = false; })} // prettier-ignore
+ />
+ </div>
+ );
/**
* displays a 'title' at the top of a document. The title contents default to the 'title' field, but can be changed to one or more fields by
* setting layout_showTitle using the format: field1[:hover]
- **/
+ * */
@computed get titleView() {
const showTitle = this.layout_showTitle?.split(':')[0];
const showTitleHover = this.layout_showTitle?.includes(':hover');
@@ -825,7 +782,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.Document;
const background = StrCast(
this.layoutDoc.layout_headingColor,
- StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor))
+ // StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor,
+ StrCast(Doc.SharingDoc().headingColor, SnappingManager.userBackgroundColor)
+ // )
);
const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._titleDropDownInnerWidth * this.titleHeight) / 30) : 0;
const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', '');
@@ -839,7 +798,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
position: this.headerMargin ? 'relative' : 'absolute',
height: this.titleHeight,
width: 100 - sidebarWidthPercent + '%',
- color: background === 'transparent' ? SettingsManager.userColor : lightOrDark(background),
+ color: background === 'transparent' ? SnappingManager.userColor : lightOrDark(background),
background,
pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
}}>
@@ -860,11 +819,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
contents={
showTitle
.split(';')
- .map(field => Field.toJavascriptString(this.Document[field] as Field))
+ .map(field => Field.toJavascriptString(this.Document[field] as FieldType))
.join(' \\ ') || '-unset-'
}
display="block"
- oneLine={true}
+ oneLine
fontSize={(this.titleHeight / 15) * 10}
GetValue={() =>
showTitle
@@ -880,7 +839,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
Doc.UserDoc().layout_showTitle = input?.substring(1) ? input.substring(1) : 'title';
}
} else if (showTitle && !showTitle.includes(';') && !showTitle.includes('Date') && showTitle !== 'author') {
- KeyValueBox.SetField(targetDoc, showTitle, input);
+ Doc.SetField(targetDoc, showTitle, input);
}
return true;
})}
@@ -905,10 +864,10 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
xPadding={10}
fieldKey={this.layout_showCaption}
styleProvider={this.captionStyleProvider}
- dontRegisterView={true}
+ dontRegisterView
rootSelected={this.rootSelected}
- noSidebar={true}
- dontScale={true}
+ noSidebar
+ dontScale
renderDepth={this._props.renderDepth}
isContentActive={this.isContentActive}
/>
@@ -952,8 +911,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
render() {
TraceMobx();
- const highlighting = this.highlighting;
- const borderPath = this.borderPath;
+ const { highlighting, borderPath } = this;
const boxShadow = !highlighting
? this.boxShadow
: highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed'
@@ -968,23 +926,22 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
});
return (
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events
<div
className={`${DocumentView.ROOT_DIV} docView-hack`}
ref={this._mainCont}
onContextMenu={this.onContextMenu}
onPointerDown={this.onPointerDown}
- onClick={this.onClick}
- onPointerEnter={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
- onPointerOver={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
+ onClick={SnappingManager.ExploreMode ? this.onBrowseClick : this.onClick}
+ onPointerEnter={() => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
+ onPointerOver={() => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
onPointerLeave={e => !isParentOf(this._contentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.Document)}
style={{
borderRadius: this.borderRounding,
pointerEvents: this._pointerEvents === 'visiblePainted' ? 'none' : this._pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here)
}}>
- <>
- {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)}
- {borderPath?.jsx}
- </>
+ {this._componentView?.isUnstyledView?.() ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation])}
+ {borderPath?.jsx}
</div>
);
}
@@ -994,7 +951,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
* @param presEffectDoc presentation effects document that specifies the animation effect parameters
* @returns a function that will wrap a JSX animation element wrapping any JSX element
*/
- public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc>, root: Doc) {
+ public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc> /* , root: Doc */) {
const dir = presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection;
const effectProps = {
left: dir === PresEffectDirection.Left,
@@ -1005,10 +962,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
delay: 0,
duration: Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)),
};
- //prettier-ignore
+ // prettier-ignore
switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) {
- default:
- case PresEffect.None: return renderDoc;
case PresEffect.Zoom: return <Zoom {...effectProps}>{renderDoc}</Zoom>;
case PresEffect.Fade: return <Fade {...effectProps}>{renderDoc}</Fade>;
case PresEffect.Flip: return <Flip {...effectProps}>{renderDoc}</Flip>;
@@ -1016,53 +971,52 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
case PresEffect.Bounce: return <Bounce {...effectProps}>{renderDoc}</Bounce>;
case PresEffect.Roll: return <Roll {...effectProps}>{renderDoc}</Roll>;
case PresEffect.Lightspeed: return <JackInTheBox {...effectProps}>{renderDoc}</JackInTheBox>;
+ case PresEffect.None:
+ default: return renderDoc;
}
}
- public static recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
- let gumStream: any;
- let recorder: any;
- navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) {
- let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null);
- if (audioTextAnnos) audioTextAnnos.push('');
- else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List<string>(['']);
- DictationManager.Controls.listen({
- interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value),
- continuous: { indefinite: false },
- }).then(results => {
- if (results && [DictationManager.Controls.Infringed].includes(results)) {
- DictationManager.Controls.stop();
- }
- onEnd?.();
- });
-
- gumStream = stream;
- recorder = new MediaRecorder(stream);
- recorder.ondataavailable = async (e: any) => {
- const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
- if (!(result instanceof Error)) {
- const audioField = new AudioField(result.accessPaths.agnostic.client);
- const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null);
- if (audioAnnos) audioAnnos.push(audioField);
- else dataDoc[field + '_audioAnnotations'] = new List([audioField]);
- }
- };
- recorder.start();
- const stopFunc = () => {
- recorder.stop();
- DictationManager.Controls.stop(false);
- dataDoc.audioAnnoState = AudioAnnoState.stopped;
- gumStream.getAudioTracks()[0].stop();
- };
- if (onRecording) onRecording(stopFunc);
- else setTimeout(stopFunc, 5000);
- });
- }
}
@observer
export class DocumentView extends DocComponent<DocumentViewProps>() {
public static ROOT_DIV = 'documentView-effectsWrapper';
- public get displayName() { return 'DocumentView(' + this.Document?.title + ')'; } // prettier-ignore
+ // Sharing Manager
+ public static ShareOpen: (target?: DocumentView, targetDoc?: Doc) => void;
+ // LinkFollower
+ public static FollowLink: (linkDoc: Opt<Doc>, sourceDoc: Doc, altKey: boolean) => boolean;
+ // selection funcs
+ public static DeselectAll: (except?: Doc) => void | undefined;
+ public static DeselectView: (dv: DocumentView | undefined) => void | undefined;
+ public static SelectView: (dv: DocumentView | undefined, extendSelection: boolean) => void | undefined;
+ public static Selected: () => DocumentView[];
+ public static SelectedDocs: () => Doc[];
+ public static SelectSchemaDoc: (doc: Doc, deselectAllFirst?: boolean) => void;
+ public static SelectedSchemaDoc: () => Opt<Doc>;
+ // view mgr funcs
+ public static activateTabView: (tabDoc: Doc) => boolean;
+ public static allViews: () => DocumentView[];
+ public static addView: (dv: DocumentView) => void | undefined;
+ public static removeView: (dv: DocumentView) => void | undefined;
+ public static addViewRenderedCb: (doc: Opt<Doc>, func: (dv: DocumentView) => any) => boolean;
+ public static getFirstDocumentView: (toFind: Doc) => DocumentView | undefined;
+ public static getDocumentView: (target: Doc | undefined, preferredCollection?: DocumentView) => Opt<DocumentView>;
+ public static getContextPath: (doc: Opt<Doc>, includeExistingViews?: boolean) => Doc[];
+ public static getLightboxDocumentView: (toFind: Doc) => Opt<DocumentView>;
+ public static showDocumentView: (targetDocView: DocumentView, options: FocusViewOptions) => Promise<void>;
+ public static showDocument: (
+ targetDoc: Doc, // document to display
+ optionsIn: FocusViewOptions, // options for how to navigate to target
+ finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done.
+ ) => Promise<void>;
+ public static linkCommonAncestor: (link: Doc) => DocumentView | undefined;
+ // pin func
+ public static PinDoc: (docIn: Doc | Doc[], pinProps: PinProps) => void;
+ // gesture
+ public static DownDocView: DocumentView | undefined; // the first DocView that receives a pointerdown event. used by GestureOverlay to determine the doc a gesture should apply to.
+ // media playing
+ @observable public static CurrentlyPlaying: DocumentView[] = [];
+
+ public get displayName() { return 'DocumentView(' + (this.Document?.title??"") + ')'; } // prettier-ignore
public ContentRef = React.createRef<HTMLDivElement>();
private _htmlOverlayEffect: Opt<Doc>;
private _disposers: { [name: string]: IReactionDisposer } = {};
@@ -1080,9 +1034,6 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}
public ViewGuid = DocumentView.UniquifyId(LightboxView.Contains(this), Utils.GenerateGuid()); // a unique id associated with the main <div>. used by LinkBox's Xanchor to find the arrowhead locations.
public DocUniqueId = DocumentView.UniquifyId(LightboxView.Contains(this), this.Document[Id]);
- @computed public static get exploreMode() {
- return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
- }
constructor(props: DocumentViewProps) {
super(props);
@@ -1100,7 +1051,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
@observable public static LongPress = false;
@computed private get shouldNotScale() {
- return (this.layout_fitWidth && !this.nativeWidth) || this._props.LayoutTemplateString?.includes(KeyValueBox.name) || [CollectionViewType.Docking].includes(this.Document._type_collection as any);
+ return (this.layout_fitWidth && !this.nativeWidth) || this.ComponentView?.isUnstyledView?.();
}
@computed private get effectiveNativeWidth() {
return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width);
@@ -1151,17 +1102,17 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
componentDidMount() {
runInAction(() => this.Document[DocViews].add(this));
this._disposers.onViewMounted = reaction(() => ScriptCast(this.Document.onViewMounted)?.script?.run({ this: this.Document }).result, emptyFunction);
- !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.AddView(this);
+ !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentView.addView(this);
}
componentWillUnmount() {
this._viewTimer && clearTimeout(this._viewTimer);
runInAction(() => this.Document[DocViews].delete(this));
Object.values(this._disposers).forEach(disposer => disposer?.());
- !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
+ !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentView.removeView(this);
}
- public set IsSelected(val) { runInAction(() => (this._selected = val)); } // prettier-ignore
+ public set IsSelected(val) { runInAction(() => { this._selected = val; }); } // prettier-ignore
public get IsSelected() { return this._selected; } // prettier-ignore
public get topMost() { return this._props.renderDepth === 0; } // prettier-ignore
public get ContentDiv() { return this._docViewInternal?._contentDiv; } // prettier-ignore
@@ -1176,7 +1127,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
}
@computed get anchorViewDoc() {
- return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
+ return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document.link_anchor_2) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document.link_anchor_1) : undefined;
}
@computed get getBounds(): Opt<{ left: number; top: number; right: number; bottom: number; transition?: string }> {
@@ -1189,20 +1140,16 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
const xf = this.screenToContentsTransform().scale(this.nativeScaling).inverse();
const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
- if (this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
- const docuBox = this.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
- if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), transition: undefined };
- }
// transition is returned so that the bounds will 'update' at the end of an animated transition. This is needed by xAnchor in LinkBox
const transition = this.docViewPath().find((parent: DocumentView) => parent.DataTransition?.() || parent.ComponentView?.viewTransition?.());
return { left, top, right, bottom, transition: transition?.DataTransition?.() || transition?.ComponentView?.viewTransition?.() };
}
@computed get nativeWidth() {
- return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
+ return returnVal(this._props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
}
@computed get nativeHeight() {
- return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
+ return returnVal(this._props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
}
@computed public get centeringX() { return this._props.dontCenter?.includes('x') ? 0 : this.Xshift; } // prettier-ignore
@computed public get centeringY() { return this._props.dontCenter?.includes('y') ? 0 : this.Yshift; } // prettier-ignore
@@ -1211,8 +1158,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
* path of DocumentViews hat contains this DocumentView (does not includes this DocumentView thouhg)
*/
public get containerViewPath() { return this._props.containerViewPath; } // prettier-ignore
- public get CollectionFreeFormView() { return this.CollectionFreeFormDocumentView?.CollectionFreeFormView; } // prettier-ignore
- public get CollectionFreeFormDocumentView() { return this._props.CollectionFreeFormDocumentView?.(); } // prettier-ignore
+ public get LocalRotation() { return this._props.LocalRotation?.(); } // prettier-ignore
public clearViewTransition = () => {
this._viewTimer && clearTimeout(this._viewTimer);
@@ -1231,18 +1177,18 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
public iconify(finished?: () => void, animateTime?: number) {
this.ComponentView?.updateIcon?.();
const animTime = this._docViewInternal?.animateScaleTime();
- runInAction(() => this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime));
+ runInAction(() => { this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime); }); // prettier-ignore
const finalFinished = action(() => {
finished?.();
this._docViewInternal && (this._docViewInternal._animateScaleTime = animTime);
});
- const layout_fieldKey = Cast(this.Document.layout_fieldKey, 'string', null);
- if (layout_fieldKey !== 'layout_icon') {
+ const layoutFieldKey = Cast(this.Document.layout_fieldKey, 'string', null);
+ if (layoutFieldKey !== 'layout_icon') {
this.switchViews(true, 'icon', finalFinished);
- if (layout_fieldKey && layout_fieldKey !== 'layout' && layout_fieldKey !== 'layout_icon') this.Document.deiconifyLayout = layout_fieldKey.replace('layout_', '');
+ if (layoutFieldKey && layoutFieldKey !== 'layout' && layoutFieldKey !== 'layout_icon') this.Document.deiconifyLayout = layoutFieldKey.replace('layout_', '');
} else {
const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null);
- this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finalFinished, true);
+ this.switchViews(!!deiconifyLayout, deiconifyLayout, finalFinished, true);
this.Document.deiconifyLayout = undefined;
this._props.bringToFront?.(this.Document);
}
@@ -1262,7 +1208,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
autoplay: true,
loop: false,
volume: 0.5,
- onend: action(() => (self.dataDoc.audioAnnoState = AudioAnnoState.stopped)),
+ onend: action(() => { self.dataDoc.audioAnnoState = AudioAnnoState.stopped; }), // prettier-ignore
});
this.dataDoc.audioAnnoState = AudioAnnoState.playing;
break;
@@ -1270,6 +1216,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
this.dataDoc[AudioPlay]?.stop();
this.dataDoc.audioAnnoState = AudioAnnoState.stopped;
break;
+ default:
}
}
};
@@ -1284,10 +1231,10 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
this._docViewInternal._animateScaleTime = time;
}
});
- public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => {
+ public setAnimEffect = (presEffect: Doc, timeInMs: number /* , afterTrans?: () => void */) => {
this._animEffectTimer && clearTimeout(this._animEffectTimer);
this.Document[Animation] = presEffect;
- this._animEffectTimer = setTimeout(() => (this.Document[Animation] = undefined), timeInMs);
+ this._animEffectTimer = setTimeout(() => { this.Document[Animation] = undefined; }, timeInMs); // prettier-ignore
};
public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => {
this._viewTimer = DocumentView.SetViewTransition([this.layoutDoc], transProp, timeInMs, this._viewTimer, afterTrans, dataTrans);
@@ -1302,9 +1249,9 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
if (checkResult) {
return Doc.UserDoc().defaultTextLayout;
}
- const view = SelectionManager.Views[0]?._props.renderDepth > 0 ? SelectionManager.Views[0] : undefined;
+ const view = DocumentView.Selected()[0]?._props.renderDepth > 0 ? DocumentView.Selected()[0] : undefined;
undoable(() => {
- var tempDoc: Opt<Doc>;
+ let tempDoc: Opt<Doc>;
if (view) {
if (!view.layoutDoc.isTemplateDoc) {
tempDoc = view.Document;
@@ -1322,6 +1269,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}
Doc.UserDoc().defaultTextLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined;
}, 'set default template')();
+ return undefined;
}
/**
@@ -1335,12 +1283,13 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
const curLayout = StrCast(this.Document.layout_fieldKey).replace('layout_', '').replace('layout', '');
if (!this.Document.layout_default && curLayout !== detailLayoutKeySuffix) this.Document.layout_default = curLayout;
const defaultLayout = StrCast(this.Document.layout_default);
- if (this.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) this.switchViews(defaultLayout ? true : false, defaultLayout, undefined, true);
+ if (this.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) this.switchViews(!!defaultLayout, defaultLayout, undefined, true);
else this.switchViews(true, detailLayoutKeySuffix, undefined, true);
};
public switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
const batch = UndoManager.StartBatch('switchView:' + view);
- runInAction(() => this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1)); // shrink doc
+ // shrink doc first..
+ runInAction(() => { this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1); }); // prettier-ignore
setTimeout(
action(() => {
if (useExistingLayout && custom && this.Document['layout_' + view]) {
@@ -1348,7 +1297,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
} else {
this.setCustomView(custom, view);
}
- this._docViewInternal && (this._docViewInternal._animateScalingTo = 1); // expand it
+ this._docViewInternal && (this._docViewInternal._animateScalingTo = 1); // now expand it
setTimeout(
action(() => {
this._docViewInternal && (this._docViewInternal._animateScalingTo = 0);
@@ -1366,15 +1315,15 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
*/
public docViewPath = () => (this.containerViewPath ? [...this.containerViewPath(), this] : [this]);
- layout_fitWidthFunc = (doc: Doc) => BoolCast(this.layout_fitWidth);
+ layout_fitWidthFunc = (/* doc: Doc */) => BoolCast(this.layout_fitWidth);
screenToLocalScale = () => this._props.ScreenToLocalTransform().Scale;
isSelected = () => this.IsSelected;
select = (extendSelection: boolean, focusSelection?: boolean) => {
- /*if (this.IsSelected && SelectionManager.Views.length > 1) SelectionManager.DeselectView(this);
- else {*/
- SelectionManager.SelectView(this, extendSelection);
+ // if (this.IsSelected && DocumentView.Selected().length > 1) DocumentView.DeselectView(this);
+ // else {
+ DocumentView.SelectView(this, extendSelection);
if (focusSelection) {
- DocumentManager.Instance.showDocument(this.Document, {
+ DocumentView.showDocument(this.Document, {
willZoomCentered: true,
zoomScale: 0.9,
zoomTime: 500,
@@ -1390,7 +1339,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
PanelWidth = () => this.panelWidth;
PanelHeight = () => this.panelHeight;
NativeDimScaling = () => this.nativeScaling;
- hideLinkCount = () => (this.hideLinkButton ? true : false);
+ hideLinkCount = () => !!this.hideLinkButton;
selfView = () => this;
/**
* @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView)
@@ -1413,17 +1362,16 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
ref={r => {
const val = r?.style.display !== 'none'; // if the outer overlay has been displayed, trigger the innner div to start it's opacity fade in transition
if (r && val !== this._enableHtmlOverlayTransitions) {
- setTimeout(action(() => (this._enableHtmlOverlayTransitions = val)));
+ setTimeout(action(() => { this._enableHtmlOverlayTransitions = val; })); // prettier-ignore
}
}}
style={{ display: !this._htmlOverlayText ? 'none' : undefined }}>
<div className="documentView-htmlOverlayInner" style={{ transition: `all 500ms`, opacity: this._enableHtmlOverlayTransitions ? 0.9 : 0 }}>
{DocumentViewInternal.AnimationEffect(
<div className="webBox-textHighlight">
- <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} />
+ <ObserverJsxParser autoCloseVoidElements key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} />
</div>,
- { ...(this._htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc,
- this.Document
+ { ...(this._htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc
)}
</div>
</div>
@@ -1436,7 +1384,15 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
const yshift = Math.abs(this.Yshift) <= 0.001 ? this._props.PanelHeight() : undefined;
return (
- <div id={this.ViewGuid} className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
+ <div
+ id={this.ViewGuid}
+ className="contentFittingDocumentView"
+ onPointerEnter={action(() => {
+ this._isHovering = true;
+ })}
+ onPointerLeave={action(() => {
+ this._isHovering = false;
+ })}>
{!this.Document || !this._props.PanelWidth() ? null : (
<div
className="contentFittingDocumentView-previewDoc"
@@ -1462,14 +1418,16 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
layout_fitWidth={this.layout_fitWidthFunc}
ScreenToLocalTransform={this.screenToContentsTransform}
focus={this._props.focus || emptyFunction}
- ref={action((r: DocumentViewInternal | null) => r && (this._docViewInternal = r))}
+ ref={action((r: DocumentViewInternal | null) => {
+ r && (this._docViewInternal = r);
+ })}
/>
{this.htmlOverlay()}
{this.ComponentView?.infoUI?.()}
</div>
)}
{/* display link count button */}
- <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover={true} Bottom={this.topMost} ShowCount={true} />
+ <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover Bottom={this.topMost} ShowCount />
</div>
);
}
@@ -1493,7 +1451,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
// shows a stacking view collection (by default, but the user can change) of all documents linked to the source
public static showBackLinks(linkAnchor: Doc) {
- const docId = Doc.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish';
+ const docId = ClientUtils.CurrentUserEmail() + Doc.GetProto(linkAnchor)[Id] + '-pivotish';
// prettier-ignore
DocServer.GetRefField(docId).then(docx =>
LightboxView.Instance.SetLightboxDoc(
@@ -1504,26 +1462,65 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}
}
+export function returnEmptyDocViewList() {
+ return emptyPath;
+}
+
+// eslint-disable-next-line default-param-last
+export function DocFocusOrOpen(docIn: Doc, optionsIn: FocusViewOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) {
+ let doc = docIn;
+ const options = optionsIn;
+ const func = () => {
+ const cv = DocumentView.getDocumentView(containingDoc);
+ const dv = DocumentView.getDocumentView(doc, cv);
+ if (dv && (!containingDoc || dv.containerViewPath?.().lastElement()?.Document === containingDoc)) {
+ DocumentView.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;
+ options.toggleTarget = undefined;
+ DocumentView.showDocument(showDoc, options, () => DocumentView.showDocument(doc, { ...options, openLocation: undefined })).then(() => {
+ const cvFound = DocumentView.getDocumentView(containingDoc);
+ const dvFound = DocumentView.getDocumentView(doc, cvFound);
+ dvFound && Doc.linkFollowHighlight(dvFound.Document);
+ });
+ }
+ };
+ if (Doc.IsDataProto(doc) && Doc.GetEmbeddings(doc).some(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))) {
+ doc = Doc.GetEmbeddings(doc).find(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))!;
+ }
+ if (doc.hidden) {
+ doc.hidden = false;
+ options.toggleTarget = false;
+ setTimeout(func);
+ } else func();
+}
+ScriptingGlobals.add(DocFocusOrOpen);
+
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) {
documentView.iconify();
documentView.select(false);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView) {
- LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); //, 0);
+ LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); // , 0);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
dv.toggleDetail(detailLayoutKeySuffix);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) {
const collectedLinks = DocListCast(linkCollection[DocData].data);
let wid = NumCast(linkSource._width);
let embedding: Doc | undefined;
- const links = LinkManager.Links(linkSource);
+ const links = Doc.Links(linkSource);
links.forEach(link => {
- const other = LinkManager.getOppositeAnchor(link, linkSource);
+ const other = Doc.getOppositeAnchor(link, linkSource);
const otherdoc = DocCast(other?.annotationOn ?? other);
if (otherdoc && !collectedLinks?.some(d => Doc.AreProtosEqual(d, otherdoc))) {
embedding = Doc.MakeEmbedding(otherdoc);
@@ -1534,9 +1531,10 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour
Doc.AddDocToList(Doc.GetProto(linkCollection), 'data', embedding);
}
});
- embedding && DocServer.UPDATE_SERVER_CACHE(); // if a new embedding was made, update the client's server cache so that it will not come back as a promise
+ embedding && UPDATE_SERVER_CACHE(); // if a new embedding was made, update the client's server cache so that it will not come back as a promise
return links;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function updateTagsCollection(collection: Doc) {
const tag = StrCast(collection.title).split('-->')[1];
const matchedTags = Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, tag, false, ['tags']).keys());
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index a557cff4f..32d08fbe7 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -1,11 +1,14 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { action, makeObservable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { DivHeight, DivWidth } from '../../../Utils';
-import { Id } from '../../../fields/FieldSymbols';
+import { DivHeight, DivWidth } from '../../../ClientUtils';
+import { Doc } from '../../../fields/Doc';
import { NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { DocUtils, Docs } from '../../documents/Documents';
+import { DocUtils } from '../../documents/DocUtils';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxBaseComponent } from '../DocComponent';
import { LightboxView } from '../LightboxView';
@@ -18,7 +21,6 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(EquationBox, fieldKey);
}
- public static SelectOnLoad: string = '';
_ref: React.RefObject<EquationEditor> = React.createRef();
constructor(props: FieldViewProps) {
@@ -28,12 +30,12 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
componentDidMount() {
this._props.setContentViewBox?.(this);
- if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()))) {
+ if (Doc.SelectOnLoad === this.Document && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()))) {
this._props.select(false);
this._ref.current!.mathField.focus();
this.dataDoc.text === 'x' && this._ref.current!.mathField.select();
- EquationBox.SelectOnLoad = '';
+ Doc.SetSelectOnLoad(undefined);
}
reaction(
() => StrCast(this.dataDoc.text),
@@ -42,7 +44,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._ref.current!.mathField.latex(text);
}
}
- //{ fireImmediately: true }
+ // { fireImmediately: true }
);
reaction(
() => this._props.isSelected(),
@@ -68,7 +70,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
x: NumCast(this.layoutDoc.x),
y: NumCast(this.layoutDoc.y) + _height + 10,
});
- EquationBox.SelectOnLoad = nextEq[Id];
+ Doc.SetSelectOnLoad(nextEq);
this._props.addDocument?.(nextEq);
e.stopPropagation();
}
@@ -88,7 +90,9 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (e.key === 'Backspace' && !this.dataDoc.text) this._props.removeDocument?.(this.Document);
};
@undoBatch
- onChange = (str: string) => (this.dataDoc.text = str);
+ onChange = (str: string) => {
+ this.dataDoc.text = str;
+ };
updateSize = () => {
const style = this._ref.current && getComputedStyle(this._ref.current.element.current);
@@ -111,7 +115,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
const scale = (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
return (
<div
- ref={r => this.updateSize()}
+ ref={() => this.updateSize()}
className="equationBox-cont"
onPointerDown={e => !e.ctrlKey && e.stopPropagation()}
style={{
@@ -122,8 +126,13 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
fontSize: StrCast(this.layoutDoc._text_fontSize),
}}
onKeyDown={e => e.stopPropagation()}>
- <EquationEditor ref={this._ref} value={StrCast(this.dataDoc.text, 'x')} spaceBehavesLikeTab={true} onChange={this.onChange} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" />
+ <EquationEditor ref={this._ref} value={StrCast(this.dataDoc.text, 'x')} spaceBehavesLikeTab onChange={this.onChange} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" />
</div>
);
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.EQUATION, {
+ layout: { view: EquationBox, dataField: 'text' },
+ options: { acl: '', fontSize: '14px', _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true, layout_hideDecorationTitle: true, systemIcon: 'BsCalculatorFill' }, // systemIcon: 'BsSuperscript' + BsSubscript
+});
diff --git a/src/client/views/nodes/FaceRectangle.tsx b/src/client/views/nodes/FaceRectangle.tsx
index 46bc6eb03..2b66b83fe 100644
--- a/src/client/views/nodes/FaceRectangle.tsx
+++ b/src/client/views/nodes/FaceRectangle.tsx
@@ -8,11 +8,17 @@ export default class FaceRectangle extends React.Component<{ rectangle: Rectangl
@observable private opacity = 0;
componentDidMount() {
- setTimeout(() => runInAction(() => (this.opacity = 1)), 500);
+ setTimeout(
+ () =>
+ runInAction(() => {
+ this.opacity = 1;
+ }),
+ 500
+ );
}
render() {
- const rectangle = this.props.rectangle;
+ const { rectangle } = this.props;
return (
<div
style={{
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 771856788..8a37000f7 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable react/no-unused-prop-types */
+/* eslint-disable react/require-default-props */
+import { computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { DateField } from '../../../fields/DateField';
@@ -5,34 +8,16 @@ import { Doc, Field, Opt } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { ScriptField } from '../../../fields/ScriptField';
import { WebField } from '../../../fields/URLField';
-import { dropActionType } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
import { Transform } from '../../util/Transform';
import { ViewBoxInterface } from '../DocComponent';
-import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
-import { DocumentView, OpenWhere } from './DocumentView';
-import { PinProps } from './trails';
-import { computed } from 'mobx';
+import { PinProps } from '../PinFuncs';
+import { DocumentView } from './DocumentView';
+import { FocusViewOptions } from './FocusViewOptions';
+import { OpenWhere } from './OpenWhere';
-export interface FocusViewOptions {
- willPan?: boolean; // determines whether to pan to target document
- willZoomCentered?: boolean; // determines whether to zoom in on target document. if zoomScale is 0, this just centers the document
- zoomScale?: number; // percent of containing frame to zoom into document
- zoomTime?: number;
- didMove?: boolean; // whether a document was changed during the showDocument process
- docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom. when this happens the docTransform must accumulate all the transforms of each level of the hierarchy
- instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom)
- preview?: boolean; // whether changes should be previewed by the componentView or written to the document
- effect?: Doc; // animation effect for focus
- noSelect?: boolean; // whether target should be selected after focusing
- playAudio?: boolean; // whether to play audio annotation on focus
- playMedia?: boolean; // whether to play start target videos
- openLocation?: OpenWhere; // where to open a missing document
- zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections
- toggleTarget?: boolean; // whether to toggle target on and off
- anchorDoc?: Doc; // doc containing anchor info to apply at end of focus to target doc
- easeFunc?: 'linear' | 'ease'; // transition method for scrolling
-}
export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt<number>;
+// eslint-disable-next-line no-use-before-define
export type StyleProviderFuncType = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => any;
//
// these properties get assigned through the render() method of the DocumentView when it creates this node.
@@ -56,11 +41,11 @@ export interface FieldViewSharedProps {
disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
hideClickBehaviors?: boolean; // whether to suppress menu item options for changing click behaviors
ignoreUsePath?: boolean; // ignore the usePath field for selecting the fieldKey (eg., on text docs)
- CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
+ LocalRotation?: () => number | undefined; // amount of rotation applied to freeformdocumentview containing document view
containerViewPath?: () => DocumentView[];
fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
isGroupActive?: () => string | undefined; // is this document part of a group that is active
- setContentViewBox?: (view: ViewBoxInterface) => any; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox
+ setContentViewBox?: (view: ViewBoxInterface<any>) => any; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox
PanelWidth: () => number;
PanelHeight: () => number;
isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
@@ -74,14 +59,14 @@ export interface FieldViewSharedProps {
onDoubleClickScript?: () => ScriptField;
onPointerDownScript?: () => ScriptField;
onPointerUpScript?: () => ScriptField;
- onBrowseClickScript?: () => ScriptField | undefined;
+ // eslint-disable-next-line no-use-before-define
onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
layout_fitWidth?: (doc: Doc) => boolean | undefined;
searchFilterDocs: () => Doc[];
layout_showTitle?: () => string;
whenChildContentsActiveChanged: (isActive: boolean) => void;
rootSelected?: () => boolean; // whether the root of a template has been selected
- addDocTab: (doc: Doc, where: OpenWhere) => boolean;
+ addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean;
filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
removeDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
@@ -122,7 +107,7 @@ export interface FieldViewProps extends FieldViewSharedProps {
@observer
export class FieldView extends React.Component<FieldViewProps> {
public static LayoutString(fieldType: { name: string }, fieldStr: string) {
- return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "<ImageBox {...props} fieldKey={'data'} />"
+ return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; // e.g., "<ImageBox {...props} fieldKey={'data'} />"
}
@computed get fieldval() {
return this.props.Document[this.props.fieldKey];
@@ -137,6 +122,6 @@ export class FieldView extends React.Component<FieldViewProps> {
if (field instanceof List) return <div> {field.map(f => Field.toString(f)).join(', ')} </div>;
if (field instanceof WebField) return <p>{Field.toString(field.url.href)}</p>;
if (!(field instanceof Promise)) return <p>{Field.toString(field)}</p>;
- return <p> {'Waiting for server...'} </p>;
+ return <p> Waiting for server... </p>;
}
}
diff --git a/src/client/views/nodes/FocusViewOptions.ts b/src/client/views/nodes/FocusViewOptions.ts
new file mode 100644
index 000000000..bb0d2b03c
--- /dev/null
+++ b/src/client/views/nodes/FocusViewOptions.ts
@@ -0,0 +1,24 @@
+import { Doc } from '../../../fields/Doc';
+import { Transform } from '../../util/Transform';
+import { OpenWhere } from './OpenWhere';
+
+export interface FocusViewOptions {
+ willPan?: boolean; // determines whether to pan to target document
+ willZoomCentered?: boolean; // determines whether to zoom in on target document. if zoomScale is 0, this just centers the document
+ zoomScale?: number; // percent of containing frame to zoom into document
+ zoomTime?: number;
+ didMove?: boolean; // whether a document was changed during the showDocument process
+ docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom. when this happens the docTransform must accumulate all the transforms of each level of the hierarchy
+ instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom)
+ preview?: boolean; // whether changes should be previewed by the componentView or written to the document
+ effect?: Doc; // animation effect for focus // bcz: needs to be changed to something more generic than a Doc
+ noSelect?: boolean; // whether target should be selected after focusing
+ playAudio?: boolean; // whether to play audio annotation on focus
+ playMedia?: boolean; // whether to play start target videos
+ openLocation?: OpenWhere; // where to open a missing document
+ zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections
+ toggleTarget?: boolean; // whether to toggle target on and off
+ easeFunc?: 'linear' | 'ease'; // transition method for scrolling
+ pointFocus?: { X: number; Y: number }; // clientX and clientY coordinates to focus on instead of a document target (used by explore mode)
+ contextPath?: Doc[]; // path of inner documents that will also be focused
+}
diff --git a/src/client/views/nodes/FontIconBox/ButtonInterface.ts b/src/client/views/nodes/FontIconBox/ButtonInterface.ts
index 1c034bfbe..0d0d7b1c3 100644
--- a/src/client/views/nodes/FontIconBox/ButtonInterface.ts
+++ b/src/client/views/nodes/FontIconBox/ButtonInterface.ts
@@ -1,5 +1,5 @@
-import { Doc } from '../../../../fields/Doc';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { Doc } from '../../../../fields/Doc';
import { ButtonType } from './FontIconBox';
export interface IButtonProps {
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 57ae92359..5e3bb9fec 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -1,23 +1,25 @@
+/* eslint-disable react/jsx-props-no-spreading */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, MultiToggle, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
+import { Button, ColorPicker, Dropdown, DropdownType, IconButton, IListItemProps, MultiToggle, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { ClientUtils, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnTrue, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { emptyFunction } from '../../../../Utils';
+import { Docs } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
-import { SelectionManager } from '../../../util/SelectionManager';
-import { SettingsManager } from '../../../util/SettingsManager';
+import { SnappingManager } from '../../../util/SnappingManager';
import { undoable, UndoManager } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { EditableView } from '../../EditableView';
import { SelectedDocView } from '../../selectedDoc';
-import { StyleProp } from '../../StyleProvider';
-import { OpenWhere } from '../DocumentView';
+import { StyleProp } from '../../StyleProp';
import { FieldView, FieldViewProps } from '../FieldView';
+import { OpenWhere } from '../OpenWhere';
import './FontIconBox.scss';
import TrailsIcon from './TrailsIcon';
@@ -33,7 +35,7 @@ export enum ButtonType {
NumberSliderButton = 'numSliderBtn',
NumberDropdownButton = 'numDropdownBtn',
NumberInlineButton = 'numInlineBtn',
- EditableText = 'editableText',
+ EditText = 'editableText',
}
export interface ButtonProps extends FieldViewProps {
@@ -49,15 +51,6 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
super(props);
makeObservable(this);
}
- //
- // This controls whether fontIconButtons will display labels under their icons or not
- //
- public static get ShowIconLabels() {
- return BoolCast(Doc.UserDoc()._showLabel);
- }
- public static set ShowIconLabels(show: boolean) {
- Doc.UserDoc()._showLabel = show;
- }
@observable noTooltip = false;
@@ -82,7 +75,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
if (iconFalse) {
icon = StrCast(this.dataDoc[this.fieldKey ?? 'iconFalse'] ?? this.dataDoc.icon, 'user') as any;
if (icon) return <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />;
- else return null;
+ return null;
}
icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as any;
return !icon ? null : icon === 'pres-trail' ? TrailsIcon(color) : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />;
@@ -108,7 +101,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
* - Color button
* - Dropdown list
* - Number button
- **/
+ * */
_batch: UndoManager.Batch | undefined = undefined;
/**
@@ -117,18 +110,13 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
@computed get numberDropdown() {
let type: NumberDropdownType;
switch (this.type) {
- case ButtonType.NumberDropdownButton:
- type = 'dropdown';
- break;
- case ButtonType.NumberInlineButton:
- type = 'input';
- break;
+ case ButtonType.NumberDropdownButton: type = 'dropdown'; break;
+ case ButtonType.NumberInlineButton: type = 'input'; break;
case ButtonType.NumberSliderButton:
- default:
- type = 'slider';
+ default: type = 'slider';
break;
- }
- const numScript = (value?: number) => ScriptCast(this.Document.script).script.run({ this: this.Document, self: this.Document, value, _readOnly_: value === undefined });
+ } // prettier-ignore
+ const numScript = (value?: number) => ScriptCast(this.Document.script).script.run({ this: 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)));
@@ -136,7 +124,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
return (
<NumberDropdown
color={color}
- background={SettingsManager.userBackgroundColor}
+ background={SnappingManager.userBackgroundColor}
numberDropdownType={type}
showPlusMinus={false}
tooltip={this.label}
@@ -154,12 +142,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
setupMoveUpEvents(
this,
e,
- (e: PointerEvent) => {
- return ScriptCast(this.Document.onDragScript)?.script.run({ this: this.Document, self: this.Document, value: { doc: value, e } }).result;
- },
+ () => ScriptCast(this.Document.onDragScript)?.script.run({ this: this.Document, value: { doc: value, e } }).result,
emptyFunction,
emptyFunction
- );
+ ); // prettier-ignore
return false;
};
@@ -173,9 +159,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
let text: string | undefined;
let getStyle: (val: string) => any = () => {};
let icon: IconProp = 'caret-down';
- const isViewDropdown = script?.script.originalScript.startsWith('setView');
+ const isViewDropdown = script?.script.originalScript.startsWith('{ return setView');
if (isViewDropdown) {
- const selected = SelectionManager.Docs;
+ const selected = Array.from(script?.script.run({ _readOnly_: true }).result) as Doc[];
+ // const selected = DocumentView.SelectedDocs();
if (selected.lastElement()) {
if (StrCast(selected.lastElement().type) === DocumentType.COL) {
text = StrCast(selected.lastElement()._type_collection);
@@ -183,27 +170,27 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
if (selected.length > 1) {
text = selected.length + ' selected';
} else {
- text = Utils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType);
+ text = ClientUtils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType, '' as CollectionViewType);
icon = Doc.toIcon(selected.lastElement());
}
return (
<Popup
- icon={<FontAwesomeIcon size={'1x'} icon={icon} />}
+ icon={<FontAwesomeIcon size="1x" icon={icon} />}
text={text}
type={Type.TERT}
- color={SettingsManager.userColor}
- background={SettingsManager.userVariantColor}
+ color={SnappingManager.userColor}
+ background={SnappingManager.userVariantColor}
popup={<SelectedDocView selectedDocs={selected} />}
fillWidth
/>
);
}
} else {
- return <Button text="None Selected" type={Type.TERT} color={SettingsManager.userColor} background={SettingsManager.userVariantColor} fillWidth inactive />;
+ return <Button text="None Selected" type={Type.TERT} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} fillWidth inactive />;
}
noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Carousel3D, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
} else {
- text = script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result;
+ text = script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result;
// text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
getStyle = (val: string) => ({ fontFamily: val });
}
@@ -221,9 +208,9 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
return (
<Dropdown
selectedVal={text}
- setSelectedVal={undoable(value => script.script.run({ this: this.Document, self: this.Document, value }), `dropdown select ${this.label}`)}
- color={SettingsManager.userColor}
- background={SettingsManager.userVariantColor}
+ setSelectedVal={undoable(value => script.script.run({ this: this.Document, value }), `dropdown select ${this.label}`)}
+ color={SnappingManager.userColor}
+ background={SnappingManager.userVariantColor}
type={Type.TERT}
closeOnSelect={false}
dropdownType={DropdownType.SELECT}
@@ -245,17 +232,17 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
*/
@computed get colorButton() {
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 curColor = this.colorScript?.script.run({ this: 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.Document, self: this.Document, value: value, _readOnly_: false });
+ this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false });
}}
setFinalColor={value => {
- this.colorScript?.script.run({ this: this.Document, self: this.Document, value: value, _readOnly_: false });
+ this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false });
this.colorBatch?.end();
this.colorBatch = undefined;
}}
@@ -263,7 +250,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
selectedColor={curColor}
type={Type.PRIM}
color={color}
- background={SettingsManager.userBackgroundColor}
+ background={SnappingManager.userBackgroundColor}
icon={this.Icon(color)!}
tooltip={tooltip}
label={this.label}
@@ -274,8 +261,8 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
// Determine the type of toggle button
const tooltip: string = StrCast(this.Document.toolTip);
- const script = ScriptCast(this.Document.onClick);
- const toggleStatus = script ? script.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result : false;
+ // const script = ScriptCast(this.Document.onClick);
+ // const toggleStatus = script ? script.script.run({ this: this.Document, value: undefined, _readOnly_: true }).result : false;
// Colors
const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
const items = DocListCast(this.dataDoc.data);
@@ -284,17 +271,17 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
tooltip={`Toggle ${tooltip}`}
type={Type.PRIM}
color={color}
- background={SettingsManager.userBackgroundColor}
+ background={SnappingManager.userBackgroundColor}
label={this.label}
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),
}))}
- selectedVal={StrCast(items.find(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: undefined, _readOnly_: true }).result)?.toolType)}
+ selectedVal={StrCast(items.find(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: undefined, _readOnly_: true }).result)?.toolType)}
setSelectedVal={(val: string | number) => {
const itemDoc = items.find(item => item.toolType === val);
- itemDoc && ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: val, _readOnly_: false });
+ itemDoc && ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: val, _readOnly_: false });
}}
/>
);
@@ -309,10 +296,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
const script = ScriptCast(this.Document.onClick);
const double = ScriptCast(this.Document.onDoubleClick);
- const toggleStatus = script?.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result ?? false;
+ const toggleStatus = script?.script.run({ this: this.Document, value: undefined, _readOnly_: true }).result ?? false;
// Colors
const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
- const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
+ // const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
return (
<Toggle
@@ -322,7 +309,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
toggleStatus={toggleStatus}
text={buttonText}
color={color}
- //background={SettingsManager.userBackgroundColor}
+ // background={SnappingManager.userBackgroundColor}
icon={this.Icon(color)!}
label={this.label}
onPointerDown={e =>
@@ -331,10 +318,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
e,
returnTrue,
emptyFunction,
- action((e, doubleTap) => {
- (!doubleTap || !double) && script?.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false });
- doubleTap && double?.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false });
- this._hackToRecompute = this._hackToRecompute + 1;
+ action((clickEv, doubleTap) => {
+ (!doubleTap || !double) && script?.script.run({ this: this.Document, value: !toggleStatus, _readOnly_: false });
+ doubleTap && double?.script.run({ this: this.Document, value: !toggleStatus, _readOnly_: false });
+ this._hackToRecompute += 1;
})
)
}
@@ -347,27 +334,22 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
*/
@computed get defaultButton() {
const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
- const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
const tooltip: string = StrCast(this.Document.toolTip);
return <IconButton tooltip={tooltip} icon={this.Icon(color)!} label={this.label} />;
}
@computed get editableText() {
- // Script for running the toggle
const script = ScriptCast(this.Document.script);
- // Function to run the script
- const checkResult = script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result;
+ const checkResult = script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result;
- const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ this: this.Document, self: this.Document, value, _readOnly_: false }).result;
-
- return <EditableText editing={false} setEditing={(editing: boolean) => {}} />;
+ const setValue = (value: string): boolean => script?.script.run({ this: this.Document, value, _readOnly_: false }).result;
return (
<div className="menuButton editableText">
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'lock'} />
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon="lock" />
<div style={{ width: 'calc(100% - .875em)', paddingLeft: '4px' }}>
- <EditableView GetValue={() => script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} />
+ <EditableView GetValue={() => script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine contents={checkResult} />
</div>
</div>
);
@@ -376,14 +358,14 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
renderButton = () => {
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 scriptFunc = () => ScriptCast(this.Document.onClick)?.script.run({ this: this.Document, _readOnly_: false });
const btnProps = { tooltip, icon: this.Icon(color)!, label: this.label };
// prettier-ignore
switch (this.type) {
case ButtonType.NumberDropdownButton:
case ButtonType.NumberInlineButton:
case ButtonType.NumberSliderButton: return this.numberDropdown;
- case ButtonType.EditableText: return this.editableText;
+ case ButtonType.EditText: return this.editableText;
case ButtonType.DropdownList: return this.dropdownListButton;
case ButtonType.ColorButton: return this.colorButton;
case ButtonType.MultiToggleButton: return this.multiToggleButton;
@@ -391,9 +373,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
case ButtonType.ClickButton:return <IconButton {...btnProps} size={Size.MEDIUM} color={color} />;
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.dataDoc.buttonText)}/>;
+ background={SnappingManager.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={SnappingManager.userBackgroundColor} size={Size.LARGE} tooltipPlacement='right' onPointerDown={scriptFunc} />;
+ default:
}
return this.defaultButton;
};
@@ -406,3 +389,8 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
);
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.FONTICON, {
+ layout: { view: FontIconBox, dataField: 'icon' },
+ options: { acl: '', defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', layout_hideContextMenu: true, layout_hideLinkButton: true, _width: 40, _height: 40 },
+});
diff --git a/src/client/views/nodes/FontIconBox/TrailsIcon.tsx b/src/client/views/nodes/FontIconBox/TrailsIcon.tsx
index 09fd6e3ae..76f00b2f4 100644
--- a/src/client/views/nodes/FontIconBox/TrailsIcon.tsx
+++ b/src/client/views/nodes/FontIconBox/TrailsIcon.tsx
@@ -1,10 +1,11 @@
import * as React from 'react';
-const TrailsIcon = (fill: string) => (
- <svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 1080.000000 1080.000000" preserveAspectRatio="xMidYMid meet">
- <g transform="translate(0.000000,1080.000000) scale(0.100000,-0.100000)" fill={fill} stroke="none">
- <path
- d="M665 9253 c-74 -10 -157 -38 -240 -81 -74 -37 -107 -63 -186 -141
+function TrailsIcon(fill: string) {
+ return (
+ <svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 1080.000000 1080.000000" preserveAspectRatio="xMidYMid meet">
+ <g transform="translate(0.000000,1080.000000) scale(0.100000,-0.100000)" fill={fill} stroke="none">
+ <path
+ d="M665 9253 c-74 -10 -157 -38 -240 -81 -74 -37 -107 -63 -186 -141
-104 -104 -156 -191 -201 -334 l-23 -72 0 -3215 c0 -3072 1 -3218 18 -3280 10
-36 39 -108 64 -160 40 -82 59 -107 142 -190 81 -81 111 -103 191 -143 52 -26
122 -55 155 -65 57 -16 322 -17 4775 -20 3250 -2 4736 1 4784 8 256 39 486
@@ -14,68 +15,69 @@ const TrailsIcon = (fill: string) => (
-62 -101 -108 -126 l-42 -22 -4435 -3 c-3954 -2 -4440 0 -4481 13 -26 9 -63
33 -87 56 -79 79 -72 -205 -72 3012 0 2156 3 2889 12 2918 20 70 91 136 168
160 14 4 2010 8 4436 8 3710 1 4418 -1 4456 -13z"
- />
- <path
- d="M7692 7839 c-46 -14 -109 -80 -122 -128 -7 -27 -9 -472 -8 -1443 l3
+ />
+ <path
+ d="M7692 7839 c-46 -14 -109 -80 -122 -128 -7 -27 -9 -472 -8 -1443 l3
-1403 24 -38 c13 -21 42 -50 64 -65 l41 -27 816 0 816 0 41 27 c22 15 51 44
64 65 l24 38 0 1425 0 1425 -24 38 c-13 21 -42 50 -64 65 l-41 27 -800 2
c-488 1 -814 -2 -834 -8z"
- />
- <path
- d="M1982 7699 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -308 -8 -893 l3
+ />
+ <path
+ d="M1982 7699 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -308 -8 -893 l3
-853 24 -38 c13 -21 42 -50 64 -65 l41 -27 1386 0 1386 0 41 27 c22 15 51 44
64 65 l24 38 0 876 0 875 -27 41 c-15 22 -44 51 -65 64 l-38 24 -1370 2 c-847
1 -1383 -2 -1403 -8z"
- />
- <path
- d="M6413 7093 c-13 -2 -23 -9 -23 -15 0 -24 21 -307 26 -343 l5 -40 182
+ />
+ <path
+ d="M6413 7093 c-13 -2 -23 -9 -23 -15 0 -24 21 -307 26 -343 l5 -40 182
-1 c200 -1 307 -15 484 -65 57 -16 107 -29 112 -29 5 0 36 75 69 168 33 92 63
175 67 184 6 14 -10 22 -92 48 -126 39 -308 76 -447 89 -106 11 -337 13 -383
4z"
- />
- <path
- d="M5840 7033 c-63 -8 -238 -29 -388 -47 -150 -18 -274 -35 -276 -37 -2
+ />
+ <path
+ d="M5840 7033 c-63 -8 -238 -29 -388 -47 -150 -18 -274 -35 -276 -37 -2
-2 8 -89 23 -194 22 -163 29 -190 44 -193 10 -2 91 6 180 17 89 12 258 32 376
46 118 14 216 27 218 28 7 8 -43 391 -52 392 -5 1 -62 -4 -125 -12z"
- />
- <path
- d="M4762 4789 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -323 -8 -943 l3
+ />
+ <path
+ d="M4762 4789 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -323 -8 -943 l3
-903 24 -38 c13 -21 42 -50 64 -65 l41 -27 926 0 926 0 41 27 c22 15 51 44 64
65 l24 38 0 926 0 925 -27 41 c-15 22 -44 51 -65 64 l-38 24 -910 2 c-557 1
-923 -2 -943 -8z"
- />
- <path
- d="M8487 4297 c-26 -215 -161 -474 -307 -585 -27 -20 -49 -40 -49 -44
+ />
+ <path
+ d="M8487 4297 c-26 -215 -161 -474 -307 -585 -27 -20 -49 -40 -49 -44
-1 -3 49 -79 110 -167 l110 -161 44 31 c176 126 333 350 418 594 30 86 77 282
77 320 0 8 -57 19 -167 34 -93 13 -182 25 -199 28 -31 5 -31 5 -37 -50z"
- />
- <path
- d="M3965 4233 c-106 -9 -348 -36 -415 -47 -55 -8 -75 -15 -74 -26 1 -20
+ />
+ <path
+ d="M3965 4233 c-106 -9 -348 -36 -415 -47 -55 -8 -75 -15 -74 -26 1 -20
56 -374 59 -377 1 -2 46 4 101 12 159 24 409 45 526 45 l108 0 0 200 0 200
-132 -2 c-73 -1 -151 -3 -173 -5z"
- />
- <path
- d="M3020 4079 c-85 -23 -292 -94 -368 -125 -97 -40 -298 -140 -305 -151
+ />
+ <path
+ d="M3020 4079 c-85 -23 -292 -94 -368 -125 -97 -40 -298 -140 -305 -151
-5 -7 172 -315 192 -336 4 -4 41 10 82 32 103 55 272 123 414 165 66 20 125
38 132 41 11 4 -4 70 -78 348 -10 39 -14 41 -69 26z"
- />
- <path
- d="M6955 3538 c-21 -91 -74 -362 -72 -364 7 -7 260 -44 367 -54 146 -13
+ />
+ <path
+ d="M6955 3538 c-21 -91 -74 -362 -72 -364 7 -7 260 -44 367 -54 146 -13
359 -13 475 0 49 6 90 12 91 13 2 1 -12 90 -29 197 -26 155 -36 194 -47 192
-8 -2 -85 -6 -170 -9 -160 -6 -357 7 -505 33 -103 18 -104 18 -110 -8z"
- />
- <path
- d="M1993 3513 c-52 -67 -71 -106 -98 -198 -35 -122 -44 -284 -21 -415 9
+ />
+ <path
+ d="M1993 3513 c-52 -67 -71 -106 -98 -198 -35 -122 -44 -284 -21 -415 9
-51 18 -96 21 -98 4 -5 360 79 375 88 7 4 7 24 0 60 -21 109 -7 244 31 307
l20 31 -146 131 c-80 72 -147 131 -149 131 -2 0 -17 -17 -33 -37z"
- />
- <path
- d="M2210 2519 c-91 -50 -166 -92 -168 -94 -2 -1 11 -26 28 -54 l32 -51
+ />
+ <path
+ d="M2210 2519 c-91 -50 -166 -92 -168 -94 -2 -1 11 -26 28 -54 l32 -51
244 0 c134 0 244 2 244 5 0 3 -23 33 -51 67 -28 35 -72 98 -97 140 -26 43 -51
77 -57 77 -5 0 -84 -41 -175 -90z"
- />
- </g>
- </svg>
-);
+ />
+ </g>
+ </svg>
+ );
+}
export default TrailsIcon;
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index a86bdbd79..3d1bd7563 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -7,13 +7,14 @@ import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { Cast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { DocUtils, Docs } from '../../documents/Documents';
+import { DocUtils } from '../../documents/DocUtils';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
+import { PinDocView, PinProps } from '../PinFuncs';
import { FieldView, FieldViewProps } from './FieldView';
-import { PinProps, PresBox } from './trails';
-import { LinkManager } from '../../util/LinkManager';
@observer
export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@@ -40,15 +41,15 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
}
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document });
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), datarange: true } }, this.Document);
+ 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);
return anchor;
};
@computed get graphFuncs() {
- const links = LinkManager.Instance.getAllRelatedLinks(this.Document)
- .map(d => LinkManager.getOppositeAnchor(d, this.Document))
+ const links = Doc.Links(this.Document)
+ .map(d => Doc.getOppositeAnchor(d, this.Document))
.filter(d => d)
.map(d => d!);
const funcs = links.concat(DocListCast(this.dataDoc[this.fieldKey])).map(doc =>
@@ -89,7 +90,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData?.droppedDocuments.length) {
const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => {
- ///const ret = res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc);
+ // const ret = res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc);
if (res) {
const link = DocUtils.MakeLink(doc, this.Document, { link_relationship: 'function', link_description: 'input' });
link && this._props.addDocument?.(link);
@@ -138,3 +139,8 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
);
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.FUNCPLOT, {
+ layout: { view: FunctionPlotBox, dataField: 'data' },
+ options: { acl: '', _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true },
+});
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index a79dda74e..d4f8b5550 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,57 +1,62 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { Colors } from 'browndash-components';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { extname } from 'path';
import * as React from 'react';
-import { Doc, Opt } from '../../../fields/Doc';
+import { ClientUtils, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
+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 { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
+import { emptyFunction } from '../../../Utils';
+import { Docs } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
+import { DocUtils } from '../../documents/DocUtils';
import { Networking } from '../../Network';
-import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
-import { ContextMenu } from '../../views/ContextMenu';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { OverlayView } from '../OverlayView';
import { AnchorMenu } from '../pdf/AnchorMenu';
-import { StyleProp } from '../StyleProvider';
-import { OpenWhere } from './DocumentView';
-import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
+import { PinDocView, PinProps } from '../PinFuncs';
+import { StyleProp } from '../StyleProp';
+import { DocumentView } from './DocumentView';
+import { FieldView, FieldViewProps } from './FieldView';
+import { FocusViewOptions } from './FocusViewOptions';
import './ImageBox.scss';
-import { PinProps, PresBox } from './trails';
+import { OpenWhere } from './OpenWhere';
export class ImageEditorData {
+ // eslint-disable-next-line no-use-before-define
private static _instance: ImageEditorData;
private static get imageData() { return (ImageEditorData._instance ?? new ImageEditorData()).imageData; } // prettier-ignore
@observable imageData: { rootDoc: Doc | undefined; open: boolean; source: string; addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean> } = observable({ rootDoc: undefined, open: false, source: '', addDoc: undefined });
- @action private static set = (open: boolean, rootDoc: Doc | undefined, source: string, addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) => (this._instance.imageData = { open, rootDoc, source, addDoc });
+ @action private static set = (open: boolean, rootDoc: Doc | undefined, source: string, addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) => {
+ this._instance.imageData = { open, rootDoc, source, addDoc };
+ };
constructor() {
makeObservable(this);
ImageEditorData._instance = this;
}
- public static get Open() { return ImageEditorData.imageData.open; } // prettier-ignore
- public static get Source() { return ImageEditorData.imageData.source; } // prettier-ignore
- public static get RootDoc() { return ImageEditorData.imageData.rootDoc; } // prettier-ignore
- public static get AddDoc() { return ImageEditorData.imageData.addDoc; } // prettier-ignore
+ public static get Open() { return ImageEditorData.imageData.open; } // prettier-ignore
public static set Open(open: boolean) { ImageEditorData.set(open, this.imageData.rootDoc, this.imageData.source, this.imageData.addDoc); } // prettier-ignore
+ public static get Source() { return ImageEditorData.imageData.source; } // prettier-ignore
public static set Source(source: string) { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, source, this.imageData.addDoc); } // prettier-ignore
+ public static get RootDoc() { return ImageEditorData.imageData.rootDoc; } // prettier-ignore
public static set RootDoc(rootDoc: Opt<Doc>) { ImageEditorData.set(this.imageData.open, rootDoc, this.imageData.source, this.imageData.addDoc); } // prettier-ignore
+ public static get AddDoc() { return ImageEditorData.imageData.addDoc; } // prettier-ignore
public static set AddDoc(addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, this.imageData.source, addDoc); } // prettier-ignore
}
@observer
@@ -66,7 +71,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
private _getAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
private _overlayIconRef = React.createRef<HTMLDivElement>();
private _marqueeref = React.createRef<MarqueeAnnotator>();
+ private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+ private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
+ @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@observable _curSuffix = '';
+ @observable _error = '';
+ @observable _isHovering = false; // flag to switch between primary and alternate images on hover
+ _ffref = React.createRef<CollectionFreeFormView>();
constructor(props: FieldViewProps) {
super(props);
@@ -93,7 +104,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
if (anchor) {
if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
addAsAnnotation && this.addDocument(anchor);
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: visibleAnchor ? false : true } }, this.Document);
+ PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !visibleAnchor } }, this.Document);
return anchor;
}
return this.Document;
@@ -106,10 +117,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
scrSize: (this.ScreenToLocalBoxXf().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth) * NumCast(this.layoutDoc._freeform_scale, 1),
selected: this._props.isSelected(),
}),
- ({ forceFull, scrSize, selected }) => (this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o'),
+ ({ forceFull, scrSize, selected }) => {
+ this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o';
+ },
{ fireImmediately: true, delay: 1000 }
);
- const layoutDoc = this.layoutDoc;
+ const { layoutDoc } = this;
this._disposers.path = reaction(
() => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width) }),
({ nativeSize, width }) => {
@@ -121,10 +134,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
);
this._disposers.scroll = reaction(
() => this.layoutDoc.layout_scrollTop,
- s_top => {
+ sTop => {
this._forcedScroll = true;
- !this._ignoreScroll && this._mainCont.current && (this._mainCont.current.scrollTop = NumCast(s_top));
- this._mainCont.current?.scrollTo({ top: NumCast(s_top) });
+ !this._ignoreScroll && this._mainCont.current && (this._mainCont.current.scrollTop = NumCast(sTop));
+ this._mainCont.current?.scrollTo({ top: NumCast(sTop) });
this._forcedScroll = false;
},
{ fireImmediately: true }
@@ -138,7 +151,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
@undoBatch
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData) {
- let added: boolean | undefined = undefined;
+ let added: boolean | undefined;
const targetIsBullseye = (ele: HTMLElement): boolean => {
if (!ele) return false;
if (ele === this._overlayIconRef.current) return true;
@@ -168,7 +181,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
};
@undoBatch
- resolution = () => (this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes);
+ resolution = () => {
+ this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes;
+ };
@undoBatch
setNativeSize = action(() => {
@@ -189,7 +204,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
const nh = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']);
const w = this.layoutDoc._width;
const h = this.layoutDoc._height;
- this.dataDoc[this.fieldKey + '-rotation'] = (NumCast(this.dataDoc[this.fieldKey + '-rotation']) + 90) % 360;
+ this.dataDoc[this.fieldKey + '_rotation'] = (NumCast(this.dataDoc[this.fieldKey + '_rotation']) + 90) % 360;
this.dataDoc[this.fieldKey + '_nativeWidth'] = nh;
this.dataDoc[this.fieldKey + '_nativeHeight'] = nw;
this.layoutDoc._width = h;
@@ -197,7 +212,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
});
crop = (region: Doc | undefined, addCrop?: boolean) => {
- if (!region) return;
+ if (!region) return undefined;
const cropping = Doc.MakeCopy(region, true);
const regionData = region[DocData];
regionData.lockedPosition = true;
@@ -223,8 +238,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
croppingProto.type = DocumentType.IMG;
croppingProto.layout = ImageBox.LayoutString('data');
croppingProto.data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField);
- croppingProto['data_nativeWidth'] = anchw;
- croppingProto['data_nativeHeight'] = anchh;
+ croppingProto.data_nativeWidth = anchw;
+ croppingProto.data_nativeHeight = anchh;
croppingProto.freeform_scale = viewScale;
croppingProto.freeform_scale_min = viewScale;
croppingProto.freeform_panX = anchx / viewScale;
@@ -239,19 +254,19 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
cropping.y = NumCast(this.Document.y);
this._props.addDocTab(cropping, OpenWhere.inParent);
}
- DocumentManager.Instance.AddViewRenderedCb(cropping, dv => setTimeout(() => (dv.ComponentView as ImageBox).setNativeSize(), 200));
+ DocumentView.addViewRenderedCb(cropping, dv => setTimeout(() => (dv.ComponentView as ImageBox).setNativeSize(), 200));
this._props.bringToFront?.(cropping);
return cropping;
};
- specificContextMenu = (e: React.MouseEvent): void => {
+ specificContextMenu = (): void => {
const field = Cast(this.dataDoc[this.fieldKey], ImageField);
if (field) {
const funcs: ContextMenuProps[] = [];
funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'redo-alt' });
funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand' });
funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' });
- funcs.push({ description: 'Copy path', event: () => Utils.CopyText(this.choosePath(field.url)), icon: 'copy' });
+ funcs.push({ description: 'Copy path', event: () => ClientUtils.CopyText(this.choosePath(field.url)), icon: 'copy' });
funcs.push({
description: 'Open Image Editor',
event: action(() => {
@@ -266,23 +281,23 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
}
};
- choosePath(url: URL) {
+ choosePath = (url: URL) => {
if (!url?.href) return '';
const lower = url.href.toLowerCase();
if (url.protocol === 'data') return url.href;
- if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return Utils.CorsProxy(url.href);
+ if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return ClientUtils.CorsProxy(url.href);
if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower) || lower.endsWith('/assets/unknown-file-icon-hi.png')) return `/assets/unknown-file-icon-hi.png`;
const ext = extname(url.href);
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();
const nativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth'], 500));
const nativeHeight = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight'], 500));
- const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '-nativeOrientation'], 1);
+ const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '_nativeOrientation'], 1);
return { nativeWidth, nativeHeight, nativeOrientation };
}
@computed get overlayImageIcon() {
@@ -307,7 +322,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
<div
className="imageBox-alternateDropTarget"
ref={this._overlayIconRef}
- onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))}
+ onPointerDown={e =>
+ setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => {
+ this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined;
+ })
+ }
style={{
display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || this.dataDoc[this.fieldKey + '_alternates'] ? 'block' : 'none',
width: 'min(10%, 25px)',
@@ -323,8 +342,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
@computed get paths() {
const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
- const alts = this.dataDoc[this.fieldKey + '_alternates'] as any as List<Doc>; // retrieve alternate documents that may be rendered as alternate images
- const defaultUrl = new URL(Utils.prepend('/assets/unknown-file-icon-hi.png'));
+ const alts = DocListCast(this.dataDoc[this.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images
+ const defaultUrl = new URL(ClientUtils.prepend('/assets/unknown-file-icon-hi.png'));
const altpaths =
alts
?.map(doc => (doc instanceof Doc ? ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url ?? defaultUrl : defaultUrl))
@@ -334,9 +353,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
return paths.length ? paths : [defaultUrl.href];
}
- @observable _error = '';
-
- @observable _isHovering = false; // flag to switch between primary and alternate images on hover
@computed get content() {
TraceMobx();
@@ -344,8 +360,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
const backAlpha = backColor.red() === 0 && backColor.green() === 0 && backColor.blue() === 0 ? backColor.alpha() : 1;
const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0];
const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement();
- const { nativeWidth, nativeHeight, nativeOrientation } = this.nativeSize;
- const rotation = NumCast(this.dataDoc[this.fieldKey + '-rotation']);
+ const { nativeWidth, nativeHeight /* , nativeOrientation */ } = this.nativeSize;
+ const rotation = NumCast(this.dataDoc[this.fieldKey + '_rotation']);
const aspect = rotation % 180 ? nativeHeight / nativeWidth : 1;
let transformOrigin = 'center center';
let transform = `translate(0%, 0%) rotate(${rotation}deg) scale(${aspect})`;
@@ -361,12 +377,32 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
const usePath = this.layoutDoc[`_${this.fieldKey}_usePath`];
return (
- <div className="imageBox-cont" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))} key={this.layoutDoc[Id]} ref={this.createDropTarget} onPointerDown={this.marqueeDown}>
+ <div
+ className="imageBox-cont"
+ onPointerEnter={action(() => {
+ this._isHovering = true;
+ })}
+ onPointerLeave={action(() => {
+ this._isHovering = false;
+ })}
+ key={this.layoutDoc[Id]}
+ ref={this.createDropTarget}
+ onPointerDown={this.marqueeDown}>
<div className="imageBox-fader" style={{ opacity: backAlpha }}>
- <img key="paths" src={srcpath} style={{ transform, transformOrigin }} onError={action(e => (this._error = e.toString()))} draggable={false} width={nativeWidth} />
+ <img
+ alt=""
+ key="paths"
+ src={srcpath}
+ style={{ transform, transformOrigin }}
+ onError={action(e => {
+ this._error = e.toString();
+ })}
+ draggable={false}
+ width={nativeWidth}
+ />
{fadepath === srcpath ? null : (
<div className={`imageBox-fadeBlocker${(this._isHovering && usePath === 'alternate:hover') || usePath === 'alternate' ? '-hover' : ''}`} style={{ transition: StrCast(this.layoutDoc.viewTransition, 'opacity 1000ms') }}>
- <img className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} />
+ <img alt="" className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} />
</div>
)}
</div>
@@ -375,23 +411,27 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
);
}
- private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
- private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
- @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@computed get annotationLayer() {
TraceMobx();
return <div className="imageBox-annotationLayer" style={{ height: this._props.PanelHeight() }} ref={this._annotationLayer} />;
}
screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale);
marqueeDown = (e: React.PointerEvent) => {
- if (!this.dataDoc[this.fieldKey]) return this.chooseImage();
- if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ if (!this.dataDoc[this.fieldKey]) {
+ this.chooseImage();
+ } else if (
+ !e.altKey &&
+ e.button === 0 &&
+ NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) &&
+ this._props.isContentActive() &&
+ ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)
+ ) {
setupMoveUpEvents(
this,
e,
- action(e => {
+ action(moveEv => {
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
- this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
+ this._marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]);
return true;
}),
returnFalse,
@@ -408,7 +448,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
};
focus = (anchor: Doc, options: FocusViewOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options));
- _ffref = React.createRef<CollectionFreeFormView>();
savedAnnotations = () => this._savedAnnotations;
render() {
TraceMobx();
@@ -419,7 +458,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
className="imageBox"
onContextMenu={this.specificContextMenu}
ref={this._mainCont}
- onScroll={action(e => {
+ onScroll={action(() => {
if (!this._forcedScroll) {
if (this.layoutDoc._layout_scrollTop || this._mainCont.current?.scrollTop) {
this._ignoreScroll = true;
@@ -437,6 +476,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
}}>
<CollectionFreeFormView
ref={this._ffref}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={emptyFunction}
NativeWidth={returnZero}
@@ -444,8 +484,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
renderDepth={this._props.renderDepth + 1}
fieldKey={this.annotationKey}
styleProvider={this._props.styleProvider}
- isAnnotationOverlay={true}
- annotationLayerHostsContent={true}
+ isAnnotationOverlay
+ annotationLayerHostsContent
PanelWidth={this._props.PanelWidth}
PanelHeight={this._props.PanelHeight}
ScreenToLocalTransform={this.screenToLocalTransform}
@@ -476,7 +516,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
selectionText={returnEmptyString}
annotationLayer={this._annotationLayer.current}
marqueeContainer={this._mainCont.current}
- highlightDragSrcColor={''}
+ highlightDragSrcColor=""
anchorMenuCrop={this.crop}
/>
)}
@@ -489,7 +529,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
input.type = 'file';
input.multiple = true;
input.accept = 'image/*';
- input.onchange = async _e => {
+ input.onchange = async () => {
const file = input.files?.[0];
if (file) {
const disposer = OverlayView.ShowSpinner();
@@ -508,3 +548,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
input.click();
};
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.IMG, {
+ layout: { view: ImageBox, dataField: 'data' },
+ options: { acl: '', freeform: '', systemIcon: 'BsFileEarmarkImageFill' },
+});
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 31a2367fc..66e210c03 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -1,26 +1,28 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { returnAlways, returnTrue } from '../../../Utils';
-import { Doc, Field, FieldResult } from '../../../fields/Doc';
+import { returnAlways, returnTrue } from '../../../ClientUtils';
+import { Doc, Field, FieldResult, FieldType } 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 { DocumentType } from '../../documents/DocumentTypes';
import { Docs } from '../../documents/Documents';
import { SetupDrag } from '../../util/DragManager';
import { CompiledScript } from '../../util/Scripting';
-import { undoBatch } from '../../util/UndoManager';
+import { undoable } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { ObservableReactComponent } from '../ObservableReactComponent';
+import { ViewBoxBaseComponent } from '../DocComponent';
import { DocumentIconContainer } from './DocumentIcon';
-import { OpenWhere } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { ImageBox } from './ImageBox';
import './KeyValueBox.scss';
import { KeyValuePair } from './KeyValuePair';
+import { OpenWhere } from './OpenWhere';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
export type KVPScript = {
@@ -29,7 +31,7 @@ export type KVPScript = {
onDelegate: boolean;
};
@observer
-export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
+export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString() {
return FieldView.LayoutString(KeyValueBox, 'data');
}
@@ -46,16 +48,16 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
componentDidMount() {
this._props.setContentViewBox?.(this);
}
- isKeyValueBox = returnTrue;
- able = returnAlways;
- layout_fitWidth = returnTrue;
- onClickScriptDisable = returnAlways;
+ // ViewBoxInterface overrides
+ override isUnstyledView = returnTrue; // used by style provider via ViewBoxInterface - ignore opacity, anim effects, titles
+ override dontRegisterView = returnTrue; // don't want to follow links to this view
+ override onClickScriptDisable = returnAlways;
@observable private rows: KeyValuePair[] = [];
@observable _splitPercentage = 50;
get fieldDocToLayout() {
- return DocCast(this._props.Document);
+ return DocCast(this.Document);
}
@action
@@ -79,7 +81,8 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
* @param value
* @returns
*/
- public static CompileKVPScript(rawvalue: string): KVPScript | undefined {
+ public static CompileKVPScript = (rawvalueIn: string): KVPScript | undefined => {
+ let rawvalue = rawvalueIn;
const onDelegate = rawvalue.startsWith('=');
rawvalue = onDelegate ? rawvalue.substring(1) : rawvalue;
const type: 'computed' | 'script' | false = rawvalue.startsWith(':=') ? 'computed' : rawvalue.startsWith('$=') ? 'script' : false;
@@ -87,28 +90,28 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
rawvalue = rawvalue.replace(/.*\(\((.*)\)\)/, 'dashCallChat(_setCacheResult_, this, `$1`)');
const value = ["'", '"', '`'].includes(rawvalue.length ? rawvalue[0] : '') || !isNaN(rawvalue as any) ? rawvalue : '`' + rawvalue + '`';
- var script = ScriptField.CompileScript(rawvalue, {}, true, undefined, DocumentIconContainer.getTransformer());
+ let script = ScriptField.CompileScript(rawvalue, {}, true, undefined, DocumentIconContainer.getTransformer());
if (!script.compiled) {
script = ScriptField.CompileScript(value, {}, true, undefined, DocumentIconContainer.getTransformer());
}
return !script.compiled ? undefined : { script, type, onDelegate };
- }
+ };
- public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) {
+ public static ApplyKVPScript = (doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) => {
const { script, type, onDelegate } = kvpScript;
- //const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates
+ // const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates
const target = forceOnDelegate || onDelegate || key.startsWith('_') ? doc : DocCast(doc.proto, doc);
- let field: Field | undefined;
+ let field: FieldType | undefined;
switch (type) {
case 'computed': field = new ComputedField(script); break; // prettier-ignore
case 'script': field = new ScriptField(script); break; // prettier-ignore
default: {
const _setCacheResult_ = (value: FieldResult) => {
- field = value as Field;
+ field = value as FieldType;
if (setResult) setResult?.(value);
else target[key] = field;
};
- const res = script.run({ this: Doc.Layout(doc), self: doc, _setCacheResult_ }, console.log);
+ const res = script.run({ this: Doc.Layout(doc), _setCacheResult_ }, console.log);
if (!res.success) {
if (key) target[key] = script.originalScript;
return false;
@@ -122,14 +125,13 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
return true;
}
return false;
- }
+ };
- @undoBatch
- public static SetField(doc: Doc, key: string, value: string, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) {
- const script = this.CompileKVPScript(value);
+ public static SetField = undoable((doc: Doc, key: string, value: string, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) => {
+ const script = KeyValueBox.CompileKVPScript(value);
if (!script) return false;
- return this.ApplyKVPScript(doc, key, script, forceOnDelegate, setResult);
- }
+ return KeyValueBox.ApplyKVPScript(doc, key, script, forceOnDelegate, setResult);
+ }, 'Set Doc Field');
onPointerDown = (e: React.PointerEvent): void => {
if (e.buttons === 1 && this._props.isSelected()) {
@@ -153,20 +155,20 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
const ids: { [key: string]: string } = {};
const protos = Doc.GetAllPrototypes(doc);
- for (const proto of protos) {
+ protos.forEach(proto => {
Object.keys(proto).forEach(key => {
if (!(key in ids) && realDoc[key] !== ComputedField.undefined) {
ids[key] = key;
}
});
- }
+ });
const rows: JSX.Element[] = [];
let i = 0;
const self = this;
const keys = Object.keys(ids).slice();
- //for (const key of [...keys.filter(id => id !== 'layout' && !id.includes('_')).sort(), ...keys.filter(id => id === 'layout' || id.includes('_')).sort()]) {
- for (const key of keys.sort((a: string, b: string) => {
+ // for (const key of [...keys.filter(id => id !== 'layout' && !id.includes('_')).sort(), ...keys.filter(id => id === 'layout' || id.includes('_')).sort()]) {
+ const sortedKeys = keys.sort((a: string, b: string) => {
const a_ = a.split('_')[0];
const b_ = b.split('_')[0];
if (a_ < b_) return -1;
@@ -174,7 +176,8 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
if (a === a_) return -1;
if (b === b_) return 1;
return a === b ? 0 : a < b ? -1 : 1;
- })) {
+ });
+ sortedKeys.forEach(key => {
rows.push(
<KeyValuePair
doc={realDoc}
@@ -195,7 +198,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
keyName={key}
/>
);
- }
+ });
return rows;
}
@computed get newKeyValue() {
@@ -229,7 +232,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
this._splitPercentage = Math.max(0, 100 - Math.round(((e.clientX - nativeWidth.left) / nativeWidth.width) * 100));
};
@action
- onDividerUp = (e: PointerEvent): void => {
+ onDividerUp = (): void => {
document.removeEventListener('pointermove', this.onDividerMove);
document.removeEventListener('pointerup', this.onDividerUp);
};
@@ -243,35 +246,36 @@ export class KeyValueBox extends ObservableReactComponent<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 });
- for (const row of rows) {
- const field = this.createFieldView(DocCast(this._props.Document), row);
+ const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this.Document).title}`, _chromeHidden: true });
+ rows.forEach(row => {
+ const field = this.createFieldView(DocCast(this.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.Document), rows.lastElement()) : undefined;
};
createFieldView = (templateDoc: Doc, row: KeyValuePair) => {
const metaKey = row._props.keyName;
- const fieldTemplate = Doc.IsDelegateField(templateDoc, metaKey) ? Doc.MakeDelegate(templateDoc) : Doc.MakeEmbedding(templateDoc);
- fieldTemplate.title = metaKey;
- fieldTemplate.layout_fitWidth = true;
- fieldTemplate._xMargin = 10;
- fieldTemplate._yMargin = 10;
- fieldTemplate._width = 100;
- fieldTemplate._height = 40;
- fieldTemplate.layout = this.inferType(templateDoc[metaKey], metaKey);
- return fieldTemplate;
+ const fieldTempDoc = Doc.IsDelegateField(templateDoc, metaKey) ? Doc.MakeDelegate(templateDoc) : Doc.MakeEmbedding(templateDoc);
+ fieldTempDoc.title = metaKey;
+ fieldTempDoc.layout_fitWidth = true;
+ fieldTempDoc._xMargin = 10;
+ fieldTempDoc._yMargin = 10;
+ fieldTempDoc._width = 100;
+ fieldTempDoc._height = 40;
+ fieldTempDoc.layout = this.inferType(templateDoc[metaKey], metaKey);
+ return fieldTempDoc;
};
inferType = (data: FieldResult, metaKey: string) => {
const options = { _width: 300, _height: 300, title: metaKey };
if (data instanceof RichTextField || typeof data === 'string' || typeof data === 'number') {
return FormattedTextBox.LayoutString(metaKey);
- } else if (data instanceof List) {
+ }
+ if (data instanceof List) {
if (data.length === 0) {
return Docs.Create.StackingDocument([], options);
}
@@ -280,28 +284,25 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
return Docs.Create.StackingDocument([], options);
}
switch (first.data.constructor) {
- case RichTextField:
- return Docs.Create.TreeDocument([], options);
- case ImageField:
- return Docs.Create.MasonryDocument([], options);
- default:
- console.log(`Template for ${first.data.constructor} not supported!`);
- return undefined;
- }
+ case RichTextField: return Docs.Create.TreeDocument([], options);
+ case ImageField: return Docs.Create.MasonryDocument([], options);
+ default: console.log(`Template for ${first.data.constructor} not supported!`);
+ return undefined;
+ } // prettier-ignore
} else if (data instanceof ImageField) {
return ImageBox.LayoutString(metaKey);
}
return new Doc();
};
- specificContextMenu = (e: React.MouseEvent): void => {
+ specificContextMenu = (): void => {
const cm = ContextMenu.Instance;
const open = cm.findByDescription('Change Perspective...');
const openItems: ContextMenuProps[] = open && 'subitems' in open ? open.subitems : [];
openItems.push({
description: 'Default Perspective',
event: () => {
- this._props.addDocTab(this._props.Document, OpenWhere.close);
+ this._props.addDocTab(this.Document, OpenWhere.close);
this._props.addDocTab(this.fieldDocToLayout, OpenWhere.addRight);
},
icon: 'image',
@@ -337,4 +338,12 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
</div>
);
}
+ public static Init() {
+ Doc.SetField = KeyValueBox.SetField;
+ }
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.KVP, {
+ layout: { view: KeyValueBox, dataField: 'data' },
+ options: { acl: '', _layout_fitWidth: true, _height: 150 },
+});
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index f9e8ce4f3..397cc15ed 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,8 +1,10 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
import { Tooltip } from '@mui/material';
import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc, Field } from '../../../fields/Doc';
import { DocCast } from '../../../fields/Types';
import { DocumentOptions, FInfo } from '../../documents/Documents';
@@ -12,10 +14,10 @@ import { ContextMenu } from '../ContextMenu';
import { EditableView } from '../EditableView';
import { ObservableReactComponent } from '../ObservableReactComponent';
import { DefaultStyleProvider } from '../StyleProvider';
-import { OpenWhere, returnEmptyDocViewList } from './DocumentView';
-import { KeyValueBox } from './KeyValueBox';
+import { returnEmptyDocViewList } from './DocumentView';
import './KeyValueBox.scss';
import './KeyValuePair.scss';
+import { OpenWhere } from './OpenWhere';
// Represents one row in a key value plane
@@ -62,7 +64,7 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
render() {
// let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")";
let protoCount = 0;
- let doc = this._props.doc;
+ let { doc } = this._props;
while (doc) {
if (Object.keys(doc).includes(this._props.keyName)) {
break;
@@ -76,10 +78,18 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
const hover = { transition: '0.3s ease opacity', opacity: this.isPointerOver || this.isChecked ? 1 : 0 };
return (
- <tr className={this._props.rowStyle} onPointerEnter={action(() => (this.isPointerOver = true))} onPointerLeave={action(() => (this.isPointerOver = false))}>
+ <tr
+ className={this._props.rowStyle}
+ onPointerEnter={action(() => {
+ this.isPointerOver = true;
+ })}
+ onPointerLeave={action(() => {
+ this.isPointerOver = false;
+ })}>
<td className="keyValuePair-td-key" style={{ width: `${this._props.keyWidth}%` }}>
<div className="keyValuePair-td-key-container">
<button
+ type="button"
style={hover}
className="keyValuePair-td-key-delete"
onClick={undoBatch(() => {
@@ -91,7 +101,7 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
</button>
<input className="keyValuePair-td-key-check" type="checkbox" style={hover} onChange={this.handleCheck} ref={this.checkbox} />
<Tooltip title={Object.entries(new DocumentOptions()).find((pair: [string, FInfo]) => pair[0].replace(/^_/, '') === this._props.keyName)?.[1].description ?? ''}>
- <div className="keyValuePair-keyField" style={{ marginLeft: 20 * (this._props.keyName.match(/_/g)?.length || 0), color: keyStyle }}>
+ <div className="keyValuePair-keyField" style={{ marginLeft: 20 * (this._props.keyName.replace(/__/g, '').match(/_/g)?.length || 0), color: keyStyle }}>
{'('.repeat(parenCount)}
{this._props.keyName}
{')'.repeat(parenCount)}
@@ -125,7 +135,7 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
pinToPres: returnZero,
}}
GetValue={() => Field.toKeyValueString(this._props.doc, this._props.keyName)}
- SetValue={(value: string) => KeyValueBox.SetField(this._props.doc, this._props.keyName, value)}
+ SetValue={(value: string) => Doc.SetField(this._props.doc, this._props.keyName, value)}
/>
</div>
</td>
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 74e78c671..f80ff5f94 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -1,21 +1,22 @@
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast, Field } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxBaseComponent } from '../DocComponent';
-import { StyleProp } from '../StyleProvider';
+import { PinDocView, PinProps } from '../PinFuncs';
+import { StyleProp } from '../StyleProp';
import { FieldView, FieldViewProps } from './FieldView';
import BigText from './LabelBigText';
import './LabelBox.scss';
-import { PinProps, PresBox } from './trails';
-import { Docs } from '../../documents/Documents';
@observer
export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -23,7 +24,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
return FieldView.LayoutString(LabelBox, fieldKey);
}
public static LayoutStringWithTitle(fieldStr: string, label?: string) {
- return !label ? LabelBox.LayoutString(fieldStr) : `<LabelBox fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; //e.g., "<ImageBox {...props} fieldKey={"data} />"
+ return !label ? LabelBox.LayoutString(fieldStr) : `<LabelBox fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; // e.g., "<ImageBox {...props} fieldKey={"data} />"
}
private dropDisposer?: DragManager.DragDropDisposer;
private _timeout: any;
@@ -41,7 +42,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
@computed get Title() {
- return Field.toString(this.dataDoc[this.fieldKey] as Field) || StrCast(this.Document.title);
+ return Field.toString(this.dataDoc[this.fieldKey] as FieldType) || StrCast(this.Document.title);
}
protected createDropTarget = (ele: HTMLDivElement) => {
@@ -54,14 +55,16 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
get paramsDoc() {
return Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? this.dataDoc : this.layoutDoc;
}
- specificContextMenu = (e: React.MouseEvent): void => {
+ specificContextMenu = (): void => {
const funcs: ContextMenuProps[] = [];
!Doc.noviceMode &&
funcs.push({
description: 'Clear Script Params',
event: () => {
const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []);
- params?.map(p => (this.paramsDoc[p] = undefined));
+ params?.forEach(p => {
+ this.paramsDoc[p] = undefined;
+ });
},
icon: 'trash',
});
@@ -71,7 +74,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
@undoBatch
drop = (e: Event, de: DragManager.DropEvent) => {
- const docDragData = de.complete.docDragData;
+ const { docDragData } = de.complete;
const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []);
const missingParams = params?.filter(p => !this.paramsDoc[p]);
if (docDragData && missingParams?.includes((e.target as any).textContent)) {
@@ -94,7 +97,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (anchor) {
if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
// addAsAnnotation && this.addDocument(anchor);
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}) } }, this.Document);
+ PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}) } }, this.Document);
return anchor;
}
return anchor;
@@ -131,7 +134,10 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
this._timeout = undefined;
if (!r) return params;
- if (!r.offsetHeight || !r.offsetWidth) return (this._timeout = setTimeout(() => this.fitTextToBox(r)));
+ if (!r.offsetHeight || !r.offsetWidth) {
+ this._timeout = setTimeout(() => this.fitTextToBox(r));
+ return this._timeout;
+ }
const parent = r.parentNode;
const parentStyle = parent.style;
parentStyle.display = '';
@@ -154,8 +160,13 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
return (
<div
className="labelBox-outerDiv"
- onMouseLeave={action(() => (this._mouseOver = false))}
- onMouseOver={action(() => (this._mouseOver = true))}
+ onMouseLeave={action(() => {
+ this._mouseOver = false;
+ })}
+ // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events
+ onMouseOver={action(() => {
+ this._mouseOver = true;
+ })}
ref={this.createDropTarget}
onContextMenu={this.specificContextMenu}
style={{ boxShadow: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BoxShadow) }}>
@@ -193,3 +204,12 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.LABEL, {
+ layout: { view: LabelBox, dataField: 'title' },
+ options: { acl: '', _singleLine: true, _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true },
+});
+Docs.Prototypes.TemplateMap.set(DocumentType.BUTTON, {
+ layout: { view: LabelBox, dataField: 'title' },
+ options: { acl: '', _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true },
+});
diff --git a/src/client/views/nodes/LinkAnchorBox.scss b/src/client/views/nodes/LinkAnchorBox.scss
deleted file mode 100644
index caff369df..000000000
--- a/src/client/views/nodes/LinkAnchorBox.scss
+++ /dev/null
@@ -1,34 +0,0 @@
-.linkAnchorBox-cont,
-.linkAnchorBox-cont-small {
- cursor: default;
- position: absolute;
- width: 15;
- height: 15;
- border-radius: 20px;
- user-select: none;
- pointer-events: all;
-
- .linkAnchorBox-linkCloser {
- position: absolute;
- width: 18;
- height: 18;
- background: rgb(219, 21, 21);
- top: -1px;
- left: -1px;
- border-radius: 5px;
- display: flex;
- justify-content: center;
- align-items: center;
- padding-left: 2px;
- padding-top: 1px;
- }
- .linkAnchorBox-button {
- position: relative;
- display: inline-block;
- }
-}
-
-.linkAnchorBox-cont-small {
- width: 5px;
- height: 5px;
-} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
deleted file mode 100644
index ff1e62885..000000000
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import { action, computed, makeObservable } from 'mobx';
-import { observer } from 'mobx-react';
-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 { DragManager, dropActionType } from '../../util/DragManager';
-import { LinkFollower } from '../../util/LinkFollower';
-import { SelectionManager } from '../../util/SelectionManager';
-import { ViewBoxBaseComponent } from '../DocComponent';
-import { StyleProp } from '../StyleProvider';
-import { FieldView, FieldViewProps } from './FieldView';
-import './LinkAnchorBox.scss';
-import { LinkInfo } from './LinkDocPreview';
-const { MEDIUM_GRAY } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
-@observer
-export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
- public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(LinkAnchorBox, fieldKey);
- }
- _doubleTap = false;
- _lastTap: number = 0;
- _ref = React.createRef<HTMLDivElement>();
- _isOpen = false;
- _timeout: NodeJS.Timeout | undefined;
-
- constructor(props: FieldViewProps) {
- super(props);
- makeObservable(this);
- }
-
- componentDidMount() {
- this._props.setContentViewBox?.(this);
- }
-
- @computed get linkSource() {
- return this.DocumentView?.().containerViewPath?.().lastElement().Document; // this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.LinkSource);
- }
-
- onPointerDown = (e: React.PointerEvent) => {
- const linkSource = this.linkSource;
- linkSource &&
- setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => {
- if (doubleTap) LinkFollower.FollowLink(this.Document, linkSource, false);
- else this._props.select(false);
- });
- };
- onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => {
- const cdiv = this._ref?.current?.parentElement;
- if (!this._isOpen && cdiv) {
- const bounds = cdiv.getBoundingClientRect();
- 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.Document]);
- dragData.dropAction = dropActionType.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.layoutDoc[this.fieldKey + '_x'] = ((pt[0] - bounds.left) / bounds.width) * 100;
- this.layoutDoc[this.fieldKey + '_y'] = ((pt[1] - bounds.top) / bounds.height) * 100;
- this.layoutDoc.link_autoMoveAnchors = false;
- }
- }
- return false;
- });
-
- specificContextMenu = (e: React.MouseEvent): void => {};
-
- render() {
- TraceMobx();
- 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')
- ? 'link_anchor_1'
- : 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 =>
- LinkInfo.SetLinkInfo({
- DocumentView: this.DocumentView,
- styleProvider: this._props.styleProvider,
- linkSrc: this.linkSource,
- linkDoc: this.Document,
- showHeader: true,
- location: [e.clientX, e.clientY + 20],
- noPreview: false,
- })
- }
- onPointerDown={this.onPointerDown}
- onContextMenu={this.specificContextMenu}
- style={{
- 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)`,
- transform: `scale(${anchorScale})`,
- cursor: 'grab',
- }}
- />
- );
- }
-}
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 3a2509c3d..6caa38a7f 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,19 +1,23 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import Xarrow from 'react-xarrows';
+import { DashColor, lightOrDark, returnFalse } from '../../../ClientUtils';
import { FieldResult } from '../../../fields/Doc';
import { DocCss, DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
+import { List } from '../../../fields/List';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { DashColor, emptyFunction, lightOrDark, returnFalse } from '../../../Utils';
-import { DocumentManager } from '../../util/DocumentManager';
+import { emptyFunction } from '../../../Utils';
+import { Docs } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
import { SnappingManager } from '../../util/SnappingManager';
import { ViewBoxBaseComponent } from '../DocComponent';
import { EditableView } from '../EditableView';
import { LightboxView } from '../LightboxView';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
import { ComparisonBox } from './ComparisonBox';
import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
@@ -38,7 +42,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
anchor = (which: number) => {
const anch = DocCast(this.dataDoc['link_anchor_' + which]);
const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch;
- return DocumentManager.Instance.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement());
+ return DocumentView.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement());
};
_hackToSeeIfDeleted: any;
componentWillUnmount() {
@@ -49,15 +53,19 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._props.setContentViewBox?.(this);
this._disposers.deleting = reaction(
() => !this.anchor1 && !this.anchor2 && this.DocumentView?.() && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView!())),
- empty => empty && ((this._hackToSeeIfDeleted = setTimeout(() =>
- (!this.anchor1 && !this.anchor2) && this._props.removeDocument?.(this.Document)
- )), 1000) // prettier-ignore
+ empty => {
+ if (empty) {
+ this._hackToSeeIfDeleted = setTimeout(() => {
+ !this.anchor1 && !this.anchor2 && this._props.removeDocument?.(this.Document);
+ }, 1000);
+ }
+ }
);
this._disposers.dragging = reaction(
() => SnappingManager.IsDragging,
() => setTimeout( action(() => {// need to wait for drag manager to set 'hidden' flag on dragged DOM elements
- const a = this.anchor1,
- b = this.anchor2;
+ const a = this.anchor1;
+ const b = this.anchor2;
let a1 = a && document.getElementById(a.ViewGuid);
let a2 = b && document.getElementById(b.ViewGuid);
// test whether the anchors themselves are hidden,...
@@ -66,7 +74,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
// .. or whether any of their DOM parents are hidden
for (; a1 && !a1.hidden; a1 = a1.parentElement);
for (; a2 && !a2.hidden; a2 = a2.parentElement);
- this._hide = a1 || a2 ? true : false;
+ this._hide = !!(a1 || a2);
}
})) // prettier-ignore
);
@@ -91,24 +99,25 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
a.Document[DocCss];
b.Document[DocCss];
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
const axf = a.screenToViewTransform(); // these force re-render when a or b moves (so do NOT remove)
const bxf = b.screenToViewTransform();
const scale = docView?.screenToViewTransform().Scale ?? 1;
const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition
const bt = b.getBounds?.transition; // inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation)
- var foundParent = false;
+ let foundParent = false;
const getAnchor = (field: FieldResult): Element[] => {
const docField = DocCast(field);
const doc = docField?.layout_unrendered ? DocCast(docField.annotationOn, docField) : docField;
const ele = document.getElementById(DocumentView.UniquifyId(LightboxView.Contains(this.DocumentView?.()), doc[Id]));
if (ele?.className === 'linkBox-label') foundParent = true;
if (ele?.getBoundingClientRect().width) return [ele];
- const eles = Array.from(document.getElementsByClassName(doc[Id])).filter(ele => ele?.getBoundingClientRect().width);
+ const eles = Array.from(document.getElementsByClassName(doc[Id])).filter(el => el?.getBoundingClientRect().width);
const annoOn = DocCast(doc.annotationOn);
if (eles.length || !annoOn) return eles;
const pareles = getAnchor(annoOn);
- foundParent = pareles.length ? true : false;
+ foundParent = !!pareles.length;
return pareles;
};
// if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext <a>),
@@ -121,26 +130,38 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
const aid = targetAhyperlinks?.find(alink => container?.contains(alink))?.id ?? targetAhyperlinks?.lastElement()?.id;
const bid = targetBhyperlinks?.find(blink => container?.contains(blink))?.id ?? targetBhyperlinks?.lastElement()?.id;
if (!aid || !bid) {
- setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01)));
+ setTimeout(
+ action(() => {
+ this._forceAnimate += 0.01;
+ })
+ );
return null;
}
if (foundParent) {
setTimeout(
- action(() => (this._forceAnimate = this._forceAnimate + 0.01)),
+ action(() => {
+ this._forceAnimate += 0.01;
+ }),
1
);
}
- if (at || bt) setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); // this forces an update during a transition animation
+ if (at || bt)
+ setTimeout(
+ action(() => {
+ this._forceAnimate += 0.01;
+ })
+ ); // this forces an update during a transition animation
const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined;
const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
const fontFamily = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily);
const fontSize = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
const fontColor = (c => (c !== 'transparent' ? c : undefined))(StrCast(this.layoutDoc.link_fontColor));
- const { stroke_markerScale, stroke_width, stroke_startMarker, stroke_endMarker, stroke_dash } = this.Document;
+ // eslint-disable-next-line camelcase
+ const { stroke_markerScale: strokeMarkerScale, stroke_width: strokeRawWidth, stroke_startMarker: strokeStartMarker, stroke_endMarker: strokeEndMarker, stroke_dash: strokeDash } = this.Document;
- const strokeWidth = NumCast(stroke_width, 4);
+ const strokeWidth = NumCast(strokeRawWidth, 4);
const linkDesc = StrCast(this.dataDoc.link_description) || ' ';
const labelText = linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '');
return (
@@ -151,12 +172,12 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
start={aid}
end={bid} //
strokeWidth={strokeWidth + Math.max(2, strokeWidth * 0.1)}
- showHead={stroke_startMarker ? true : false}
- showTail={stroke_endMarker ? true : false}
- headSize={NumCast(stroke_markerScale, 3)}
- tailSize={NumCast(stroke_markerScale, 3)}
- tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
- headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
+ showHead={!!strokeStartMarker}
+ showTail={!!strokeEndMarker}
+ headSize={NumCast(strokeMarkerScale, 3)}
+ tailSize={NumCast(strokeMarkerScale, 3)}
+ tailShape={strokeEndMarker === 'dot' ? 'circle' : 'arrow1'}
+ headShape={strokeStartMarker === 'dot' ? 'circle' : 'arrow1'}
color={highlightColor}
/>
)}
@@ -165,23 +186,23 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
start={aid}
end={bid} //
strokeWidth={strokeWidth}
- dashness={Number(stroke_dash) ? true : false}
- showHead={stroke_startMarker ? true : false}
- showTail={stroke_endMarker ? true : false}
- headSize={NumCast(stroke_markerScale, 3)}
- tailSize={NumCast(stroke_markerScale, 3)}
- tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
- headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
+ dashness={!!Number(strokeDash)}
+ showHead={!!strokeStartMarker}
+ showTail={!!strokeEndMarker}
+ headSize={NumCast(strokeMarkerScale, 3)}
+ tailSize={NumCast(strokeMarkerScale, 3)}
+ tailShape={strokeEndMarker === 'dot' ? 'circle' : 'arrow1'}
+ headShape={strokeStartMarker === 'dot' ? 'circle' : 'arrow1'}
color={color}
labels={
<div
id={this.DocumentView?.().DocUniqueId}
- className={'linkBox-label'}
+ className="linkBox-label"
style={{
borderRadius: '8px',
pointerEvents: this._props.isDocumentActive?.() ? 'all' : undefined,
fontSize,
- fontFamily /*, fontStyle: 'italic'*/,
+ fontFamily /* , fontStyle: 'italic' */,
color: fontColor || lightOrDark(DashColor(color).fade(0.5).toString()),
paddingLeft: 4,
paddingRight: 4,
@@ -222,16 +243,19 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
setTimeout(
- action(() => (this._forceAnimate = this._forceAnimate + 1)),
+ action(() => {
+ this._forceAnimate += 1;
+ }),
2
);
return (
<div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}>
<ComparisonBox
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this.props} //
fieldKey="link_anchor"
setHeight={emptyFunction}
- dontRegisterView={true}
+ dontRegisterView
renderDepth={this._props.renderDepth + 1}
addDocument={returnFalse}
removeDocument={returnFalse}
@@ -241,3 +265,18 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.LINK, {
+ layout: { view: LinkBox, dataField: 'link' },
+ options: {
+ acl: '',
+ childDontRegisterViews: true,
+ layout_hideLinkAnchors: true,
+ _height: 1,
+ _width: 1,
+ link: '',
+ link_description: '',
+ color: 'lightBlue', // lightblue is default color for linking dot and link documents text comment area
+ _dropPropertiesToRemove: new List(['onClick']),
+ },
+});
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 2a96ce458..ff95f8547 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -2,15 +2,17 @@ import { action, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { DocData } from '../../../fields/DocSymbols';
+import { StrCast } from '../../../fields/Types';
import { LinkManager } from '../../util/LinkManager';
import './LinkDescriptionPopup.scss';
import { TaskCompletionBox } from './TaskCompletedBox';
-import { StrCast } from '../../../fields/Types';
@observer
export class LinkDescriptionPopup extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: LinkDescriptionPopup;
@observable public display: boolean = false;
+ // eslint-disable-next-line react/no-unused-class-component-methods
@observable public showDescriptions: string = 'ON';
@observable public popupX: number = 700;
@observable public popupY: number = 350;
@@ -23,6 +25,20 @@ export class LinkDescriptionPopup extends React.Component<{}> {
LinkDescriptionPopup.Instance = this;
}
+ componentDidMount() {
+ document.addEventListener('pointerdown', this.onClick, true);
+ reaction(
+ () => this.display,
+ display => {
+ display && (this.description = StrCast(LinkManager.Instance.currentLink?.link_description));
+ }
+ );
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('pointerdown', this.onClick, true);
+ }
+
@action
descriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
this.description = e.currentTarget.value;
@@ -39,25 +55,13 @@ export class LinkDescriptionPopup extends React.Component<{}> {
@action
onClick = (e: PointerEvent) => {
- if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) {
+ if (this.popupRef && !this.popupRef.current?.contains(e.target as any)) {
this.display = false;
this.description = '';
TaskCompletionBox.taskCompleted = false;
}
};
- componentDidMount() {
- document.addEventListener('pointerdown', this.onClick, true);
- reaction(
- () => this.display,
- display => display && (this.description = StrCast(LinkManager.Instance.currentLink?.link_description))
- );
- }
-
- componentWillUnmount() {
- document.removeEventListener('pointerdown', this.onClick, true);
- }
-
render() {
return !this.display ? null : (
<div
@@ -78,11 +82,11 @@ export class LinkDescriptionPopup extends React.Component<{}> {
onChange={e => this.descriptionChanged(e)}
/>
<div className="linkDescriptionPopup-btn">
- <div className="linkDescriptionPopup-btn-dismiss" onPointerDown={e => this.onDismiss(false)}>
+ <div className="linkDescriptionPopup-btn-dismiss" onPointerDown={() => this.onDismiss(false)}>
{' '}
Dismiss{' '}
</div>
- <div className="linkDescriptionPopup-btn-add" onPointerDown={e => this.onDismiss(true)}>
+ <div className="linkDescriptionPopup-btn-add" onPointerDown={() => this.onDismiss(true)}>
{' '}
Add{' '}
</div>
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index c9c8f9260..508008569 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -4,53 +4,59 @@ import { action, computed, makeObservable, observable, runInAction } from 'mobx'
import { observer } from 'mobx-react';
import * as React from 'react';
import wiki from 'wikijs';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types';
import { DocServer } from '../../DocServer';
import { DocumentType } from '../../documents/DocumentTypes';
import { Docs } from '../../documents/Documents';
-import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from '../../util/DragManager';
-import { LinkFollower } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
import { SearchUtil } from '../../util/SearchUtil';
-import { SettingsManager } from '../../util/SettingsManager';
+import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { ObservableReactComponent } from '../ObservableReactComponent';
-import { DocumentView, OpenWhere } from './DocumentView';
+import { DocumentView } from './DocumentView';
import { StyleProviderFuncType } from './FieldView';
import './LinkDocPreview.scss';
+import { OpenWhere } from './OpenWhere';
+interface LinkDocPreviewProps {
+ linkDoc?: Doc;
+ linkSrc?: Doc;
+ DocumentView?: () => DocumentView;
+ styleProvider?: StyleProviderFuncType;
+ location: number[];
+ hrefs?: string[];
+ showHeader?: boolean;
+ noPreview?: boolean;
+}
export class LinkInfo {
+ // eslint-disable-next-line no-use-before-define
private static _instance: Opt<LinkInfo>;
constructor() {
LinkInfo._instance = this;
makeObservable(this);
}
+ // eslint-disable-next-line no-use-before-define
@observable public LinkInfo: Opt<LinkDocPreviewProps> = undefined;
public static get Instance() {
return LinkInfo._instance ?? new LinkInfo();
}
public static Clear() {
- runInAction(() => LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = undefined));
+ runInAction(() => {
+ LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = undefined);
+ });
}
public static SetLinkInfo(info?: LinkDocPreviewProps) {
- runInAction(() => LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = info));
+ runInAction(() => {
+ LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = info);
+ });
}
}
-interface LinkDocPreviewProps {
- linkDoc?: Doc;
- linkSrc?: Doc;
- DocumentView?: () => DocumentView;
- styleProvider?: StyleProviderFuncType;
- location: number[];
- hrefs?: string[];
- showHeader?: boolean;
- noPreview?: boolean;
-}
@observer
export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps> {
_infoRef = React.createRef<HTMLDivElement>();
@@ -68,13 +74,13 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
@action
init() {
- var linkTarget = this._props.linkDoc;
+ let linkTarget = this._props.linkDoc;
this._linkSrc = this._props.linkSrc;
this._linkDoc = this._props.linkDoc;
- const link_anchor_1 = DocCast(this._linkDoc?.link_anchor_1);
- const link_anchor_2 = DocCast(this._linkDoc?.link_anchor_2);
- if (link_anchor_1 && link_anchor_2) {
- linkTarget = Doc.AreProtosEqual(link_anchor_1, this._linkSrc) || Doc.AreProtosEqual(link_anchor_1?.annotationOn as Doc, this._linkSrc) ? link_anchor_2 : link_anchor_1;
+ const linkAnchor1 = DocCast(this._linkDoc?.link_anchor_1);
+ const linkAnchor2 = DocCast(this._linkDoc?.link_anchor_2);
+ if (linkAnchor1 && linkAnchor2) {
+ linkTarget = Doc.AreProtosEqual(linkAnchor1, this._linkSrc) || Doc.AreProtosEqual(linkAnchor1?.annotationOn as Doc, this._linkSrc) ? linkAnchor2 : linkAnchor1;
}
if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) {
linkTarget = DocCast(linkTarget.annotationOn); // want to show annotation embedContainer document if annotation is not text
@@ -110,7 +116,13 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
if (href.startsWith('https://en.wikipedia.org/wiki/')) {
wiki()
.page(href.replace('https://en.wikipedia.org/wiki/', ''))
- .then(page => page.summary().then(action(summary => (this._toolTipText = summary.substring(0, 500)))));
+ .then(page =>
+ page.summary().then(
+ action(summary => {
+ this._toolTipText = summary.substring(0, 500);
+ })
+ )
+ );
} else {
this._toolTipText = 'url => ' + href;
}
@@ -120,19 +132,19 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
const anchorDoc = anchorDocId ? PromiseValue(DocCast(DocServer.GetCachedRefField(anchorDocId) ?? DocServer.GetRefField(anchorDocId))) : undefined;
anchorDoc?.then?.(
action(anchor => {
- if (anchor instanceof Doc && LinkManager.Links(anchor).length) {
- this._linkDoc = this._linkDoc ?? LinkManager.Links(anchor)[0];
+ if (anchor instanceof Doc && Doc.Links(anchor).length) {
+ this._linkDoc = this._linkDoc ?? Doc.Links(anchor)[0];
const automaticLink = this._linkDoc.link_relationship === LinkManager.AutoKeywords;
if (automaticLink) {
// automatic links specify the target in the link info, not the source
const linkTarget = anchor;
- this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget);
+ this._linkSrc = Doc.getOppositeAnchor(this._linkDoc, linkTarget);
this._markerTargetDoc = this._targetDoc = linkTarget;
} else {
this._linkSrc = anchor;
- const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
+ const linkTarget = Doc.getOppositeAnchor(this._linkDoc, this._linkSrc);
this._markerTargetDoc = linkTarget;
- this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
+ this._targetDoc = /* linkTarget?.type === DocumentType.MARKER && */ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
}
if (LinkInfo.Instance?.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink();
}
@@ -155,8 +167,8 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
LinkManager.Instance.currentLink = this._linkDoc;
LinkManager.Instance.currentLinkAnchor = this._linkSrc;
this._props.DocumentView?.().select(false);
- if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
- SettingsManager.Instance.propertiesWidth = 250;
+ if ((SnappingManager.PropertiesWidth ?? 0) < 100) {
+ SnappingManager.SetPropertiesWidth(250);
}
})
);
@@ -182,18 +194,18 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
followLink = () => {
LinkInfo.Clear();
if (this._linkDoc && this._linkSrc) {
- LinkFollower.FollowLink(this._linkDoc, this._linkSrc, false);
+ DocumentView.FollowLink(this._linkDoc, this._linkSrc, false);
} else if (this._props.hrefs?.length) {
const webDoc =
Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this._props.hrefs[0], false).keys())
.filter(doc => doc.type === DocumentType.WEB)
.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, {
+ DocumentView.showDocument(webDoc, {
openLocation: OpenWhere.lightbox,
willPan: true,
zoomTime: 500,
});
- //this._props.docProps?.addDocTab(webDoc, OpenWhere.lightbox);
+ // this._props.docProps?.addDocTab(webDoc, OpenWhere.lightbox);
}
};
@@ -215,7 +227,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
};
@computed get previewHeader() {
return !this._linkDoc || !this._markerTargetDoc || !this._targetDoc || !this._linkSrc ? null : (
- <div className="linkDocPreview-info" style={{ background: SettingsManager.userBackgroundColor }}>
+ <div className="linkDocPreview-info" style={{ background: SnappingManager.userBackgroundColor }}>
<div className="linkDocPreview-buttonBar" style={{ float: 'left' }}>
<Tooltip title={<div className="dash-tooltip">Edit Link</div>} placement="top">
<div className="linkDocPreview-button" onPointerDown={this.editLink}>
@@ -248,9 +260,9 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
setupMoveUpEvents(
this,
e,
- (e, down, delta) => {
- if (Math.abs(e.clientX - down[0]) + Math.abs(e.clientY - down[1]) > 100) {
- DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY);
+ (moveEv, down) => {
+ if (Math.abs(moveEv.clientX - down[0]) + Math.abs(moveEv.clientY - down[1]) > 100) {
+ DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), moveEv.pageX, moveEv.pageY);
LinkInfo.Clear();
return true;
}
@@ -268,7 +280,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
) : (
<DocumentView
ref={r => {
- const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
+ const targetanchor = this._linkDoc && this._linkSrc && Doc.getOppositeAnchor(this._linkDoc, this._linkSrc);
targetanchor && this._targetDoc !== targetanchor && r?._props.focus?.(targetanchor, {});
}}
Document={this._targetDoc!}
@@ -283,18 +295,18 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
removeDocument={returnFalse}
addDocTab={returnFalse}
pinToPres={returnFalse}
- dontRegisterView={true}
+ dontRegisterView
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
renderDepth={0}
- suppressSetHeight={true}
+ suppressSetHeight
PanelWidth={this.width}
PanelHeight={this.height}
pointerEvents={returnNone}
focus={emptyFunction}
whenChildContentsActiveChanged={returnFalse}
- ignoreAutoHeight={true} // need to ignore layout_autoHeight otherwise layout_autoHeight text boxes will expand beyond the preview panel size.
+ ignoreAutoHeight // need to ignore layout_autoHeight otherwise layout_autoHeight text boxes will expand beyond the preview panel size.
NativeWidth={Doc.NativeWidth(this._targetDoc) ? () => Doc.NativeWidth(this._targetDoc) : undefined}
NativeHeight={Doc.NativeHeight(this._targetDoc) ? () => Doc.NativeHeight(this._targetDoc) : undefined}
/>
@@ -311,7 +323,7 @@ export class LinkDocPreview extends ObservableReactComponent<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: SnappingManager.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 adccc9db6..5f343bdfe 100644
--- a/src/client/views/nodes/LoadingBox.tsx
+++ b/src/client/views/nodes/LoadingBox.tsx
@@ -6,7 +6,8 @@ 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 { DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { FieldView, FieldViewProps } from './FieldView';
import './LoadingBox.scss';
@@ -37,30 +38,18 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(LoadingBox, fieldKey);
}
- // removes from currently loading display
- public static removeCurrentlyLoading(doc: Doc) {
- 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
- public static addCurrentlyLoading(doc: Doc) {
- if (DocumentManager.Instance.CurrentlyLoading.indexOf(doc) === -1) {
- runInAction(() => DocumentManager.Instance.CurrentlyLoading.push(doc));
- }
- }
_timer: any;
@observable progress = '';
componentDidMount() {
- if (!DocumentManager.Instance.CurrentlyLoading?.includes(this.Document)) {
+ if (!Doc.CurrentlyLoading?.includes(this.Document)) {
this.Document.loadingError = 'Upload interrupted, please try again';
} else {
const updateFunc = async () => {
const result = await Networking.QueryYoutubeProgress(StrCast(this.Document[Id])); // We use the guid of the overwriteDoc to track file uploads.
- runInAction(() => (this.progress = result.progress));
+ runInAction(() => {
+ this.progress = result.progress;
+ });
!this.Document.loadingError && this._timer && (this._timer = setTimeout(updateFunc, 1000));
};
this._timer = setTimeout(updateFunc, 1000);
@@ -87,3 +76,7 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
);
}
}
+Docs.Prototypes.TemplateMap.set(DocumentType.LOADING, {
+ layout: { view: LoadingBox, dataField: '' },
+ options: { acl: '', _layout_fitWidth: true, _layout_nativeDimEditable: true },
+});
diff --git a/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx b/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx
index d54a175b2..f4ece627f 100644
--- a/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx
+++ b/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx
@@ -1,35 +1,44 @@
-import * as React from "react";
+import * as React from 'react';
export const slowSpeedIcon: JSX.Element = (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 435.62">
<defs>
- <style type="text/css">
- {`
+ <style type="text/css">
+ {`
.fil0 { fill: black; fill-rule: nonzero; }
.fil1 { fill: #FE0000; fill-rule: nonzero; }
`}
- </style>
+ </style>
</defs>
- <path className="fil0" d="M174.84 343.06c-7.31,-13.12 -13.03,-27.28 -16.89,-42.18 -3.76,-14.56 -5.77,-29.71 -5.77,-45.17 0,-11.94 1.19,-23.66 3.43,-35.03 2.29,-11.57 5.74,-22.83 10.2,-33.63 13.7,-33.14 37.01,-61.29 66.42,-80.96 25.38,-16.96 55.28,-27.66 87.45,-29.87l0 -30.17c0,-0.46 0.02,-0.92 0.06,-1.37l-33.7 0c-5.53,0 -10.05,-4.52 -10.05,-10.04l0 -24.59c0,-5.53 4.52,-10.05 10.05,-10.05l101.27 0c5.53,0 10.05,4.52 10.05,10.05l0 24.59c0,5.52 -4.52,10.04 -10.05,10.04l-33.69 0c0.03,0.45 0.05,0.91 0.05,1.37l0 31.03 -0.1 0c41.1,4.89 77.94,23.63 105.73,51.42 32.56,32.55 52.7,77.54 52.7,127.21 0,49.67 -20.14,94.66 -52.7,127.21 -32.55,32.55 -77.54,52.7 -127.21,52.7 -33.16,0 -64.29,-9.04 -91.05,-24.78 -27.66,-16.27 -50.59,-39.73 -66.2,-67.78zm148.42 -36.62l-80.33 0 0 -25.71 28.6 0 0 -42.57 -28.6 1.93 0 -25.71 36.95 -8.35 25.38 0 0 74.7 18 0 0 25.71zm44.34 -100.41l11.08 26.83 1.61 0 11.09 -26.83 34.86 0 -22.33 48.52 22.33 51.89 -35.67 0 -12.05 -28.92 -1.44 0 -11.89 28.92 -34.06 0 21.85 -50.93 -21.85 -49.48 36.47 0zm126.08 -74.6c6.98,-16.66 6.15,-34.13 -3.84,-45.82 -12,-14.03 -33.67,-15.64 -53.8,-5.77 21.32,14.62 40.68,31.63 57.64,51.59zm-323.17 0c-6.98,-16.66 -6.16,-34.13 3.84,-45.82 11.99,-14.03 33.67,-15.64 53.79,-5.77 -21.32,14.62 -40.68,31.63 -57.63,51.59zm15.31 162.23c3.23,12.5 8.04,24.39 14.18,35.42 13.13,23.58 32.39,43.29 55.6,56.94 22.37,13.16 48.52,20.71 76.49,20.71 41.71,0 79.47,-16.9 106.8,-44.23 27.32,-27.32 44.23,-65.08 44.23,-106.79 0,-41.71 -16.91,-79.47 -44.23,-106.8 -27.33,-27.32 -65.09,-44.23 -106.8,-44.23 -31.07,0 -59.91,9.34 -83.84,25.33 -24.74,16.54 -44.33,40.19 -55.82,67.98 -3.68,8.91 -6.56,18.35 -8.5,28.22 -1.87,9.49 -2.86,19.36 -2.86,29.5 0,13.24 1.65,25.96 4.75,37.95z"/>
- <path className="fil1" d="M55.23 188.52c-7.98,0 -14.45,-6.47 -14.45,-14.44 0,-7.98 6.47,-14.45 14.45,-14.45l63.94 0c7.98,0 14.45,6.47 14.45,14.45 0,7.97 -6.47,14.44 -14.45,14.44l-63.94 0zm0.72 167.68c-7.97,0 -14.44,-6.47 -14.44,-14.45 0,-7.97 6.47,-14.45 14.44,-14.45l64.58 0c7.97,0 14.45,6.48 14.45,14.45 0,7.98 -6.48,14.45 -14.45,14.45l-64.58 0zm-41.5 -84.94c-7.98,0 -14.45,-6.47 -14.45,-14.45 0,-7.97 6.47,-14.44 14.45,-14.44l89.12 0c7.98,0 14.45,6.47 14.45,14.44 0,7.98 -6.47,14.45 -14.45,14.45l-89.12 0z"/>
+ <path
+ className="fil0"
+ d="M174.84 343.06c-7.31,-13.12 -13.03,-27.28 -16.89,-42.18 -3.76,-14.56 -5.77,-29.71 -5.77,-45.17 0,-11.94 1.19,-23.66 3.43,-35.03 2.29,-11.57 5.74,-22.83 10.2,-33.63 13.7,-33.14 37.01,-61.29 66.42,-80.96 25.38,-16.96 55.28,-27.66 87.45,-29.87l0 -30.17c0,-0.46 0.02,-0.92 0.06,-1.37l-33.7 0c-5.53,0 -10.05,-4.52 -10.05,-10.04l0 -24.59c0,-5.53 4.52,-10.05 10.05,-10.05l101.27 0c5.53,0 10.05,4.52 10.05,10.05l0 24.59c0,5.52 -4.52,10.04 -10.05,10.04l-33.69 0c0.03,0.45 0.05,0.91 0.05,1.37l0 31.03 -0.1 0c41.1,4.89 77.94,23.63 105.73,51.42 32.56,32.55 52.7,77.54 52.7,127.21 0,49.67 -20.14,94.66 -52.7,127.21 -32.55,32.55 -77.54,52.7 -127.21,52.7 -33.16,0 -64.29,-9.04 -91.05,-24.78 -27.66,-16.27 -50.59,-39.73 -66.2,-67.78zm148.42 -36.62l-80.33 0 0 -25.71 28.6 0 0 -42.57 -28.6 1.93 0 -25.71 36.95 -8.35 25.38 0 0 74.7 18 0 0 25.71zm44.34 -100.41l11.08 26.83 1.61 0 11.09 -26.83 34.86 0 -22.33 48.52 22.33 51.89 -35.67 0 -12.05 -28.92 -1.44 0 -11.89 28.92 -34.06 0 21.85 -50.93 -21.85 -49.48 36.47 0zm126.08 -74.6c6.98,-16.66 6.15,-34.13 -3.84,-45.82 -12,-14.03 -33.67,-15.64 -53.8,-5.77 21.32,14.62 40.68,31.63 57.64,51.59zm-323.17 0c-6.98,-16.66 -6.16,-34.13 3.84,-45.82 11.99,-14.03 33.67,-15.64 53.79,-5.77 -21.32,14.62 -40.68,31.63 -57.63,51.59zm15.31 162.23c3.23,12.5 8.04,24.39 14.18,35.42 13.13,23.58 32.39,43.29 55.6,56.94 22.37,13.16 48.52,20.71 76.49,20.71 41.71,0 79.47,-16.9 106.8,-44.23 27.32,-27.32 44.23,-65.08 44.23,-106.79 0,-41.71 -16.91,-79.47 -44.23,-106.8 -27.33,-27.32 -65.09,-44.23 -106.8,-44.23 -31.07,0 -59.91,9.34 -83.84,25.33 -24.74,16.54 -44.33,40.19 -55.82,67.98 -3.68,8.91 -6.56,18.35 -8.5,28.22 -1.87,9.49 -2.86,19.36 -2.86,29.5 0,13.24 1.65,25.96 4.75,37.95z"
+ />
+ <path
+ className="fil1"
+ d="M55.23 188.52c-7.98,0 -14.45,-6.47 -14.45,-14.44 0,-7.98 6.47,-14.45 14.45,-14.45l63.94 0c7.98,0 14.45,6.47 14.45,14.45 0,7.97 -6.47,14.44 -14.45,14.44l-63.94 0zm0.72 167.68c-7.97,0 -14.44,-6.47 -14.44,-14.45 0,-7.97 6.47,-14.45 14.44,-14.45l64.58 0c7.97,0 14.45,6.48 14.45,14.45 0,7.98 -6.48,14.45 -14.45,14.45l-64.58 0zm-41.5 -84.94c-7.98,0 -14.45,-6.47 -14.45,-14.45 0,-7.97 6.47,-14.44 14.45,-14.44l89.12 0c7.98,0 14.45,6.47 14.45,14.44 0,7.98 -6.47,14.45 -14.45,14.45l-89.12 0z"
+ />
</svg>
);
export const mediumSpeedIcon: JSX.Element = (
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 122.88 104.55">
- <defs><style>{`.cls-1{fill:#fe0000;}`}</style></defs>
- <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.45l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.09l5.36,12.45H93.74L90.85,66.6H90.5l-2.85,6.94H79.47l5.25-12.22L79.47,49.45ZM58.65,56.08l-1-5.75a33.58,33.58,0,0,1,9.68-1.46c1.28,0,2.35,0,3.22.11a11.77,11.77,0,0,1,2.67.58,5.41,5.41,0,0,1,2.2,1.28c1.24,1.23,1.85,3.12,1.85,5.66s-.72,4.42-2.16,5.63S70.64,64.73,66,66.3v1.08H76.89v6.16H57.11V68.72a10.73,10.73,0,0,1,.81-4.12,8.4,8.4,0,0,1,2.43-2.7,12.13,12.13,0,0,1,2.79-1.7l3.32-1.52c1-.47,1.88-.87,2.52-1.17V55.42a28.59,28.59,0,0,0-3.2-.19,30.66,30.66,0,0,0-7.13.85Zm59.83-24.54c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z"/>
- <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Z"/>
- <path className="cls-1" d="M3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z"/>
- <path className="cls-1" d="M13.43,85.49a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94Z"/>
+ <defs>
+ <style>{`.cls-1{fill:#fe0000;}`}</style>
+ </defs>
+ <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.45l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.09l5.36,12.45H93.74L90.85,66.6H90.5l-2.85,6.94H79.47l5.25-12.22L79.47,49.45ZM58.65,56.08l-1-5.75a33.58,33.58,0,0,1,9.68-1.46c1.28,0,2.35,0,3.22.11a11.77,11.77,0,0,1,2.67.58,5.41,5.41,0,0,1,2.2,1.28c1.24,1.23,1.85,3.12,1.85,5.66s-.72,4.42-2.16,5.63S70.64,64.73,66,66.3v1.08H76.89v6.16H57.11V68.72a10.73,10.73,0,0,1,.81-4.12,8.4,8.4,0,0,1,2.43-2.7,12.13,12.13,0,0,1,2.79-1.7l3.32-1.52c1-.47,1.88-.87,2.52-1.17V55.42a28.59,28.59,0,0,0-3.2-.19,30.66,30.66,0,0,0-7.13.85Zm59.83-24.54c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z" />
+ <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Z" />
+ <path className="cls-1" d="M3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z" />
+ <path className="cls-1" d="M13.43,85.49a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94Z" />
</svg>
);
export const fastSpeedIcon: JSX.Element = (
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 122.88 104.55">
- <defs><style>{`.cls-1{fill:#fe0000;`}</style></defs>
- <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.61l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.26l5.36,12.45H93.74l-2.9-6.94H90.5l-2.86,6.94H79.47l5.24-12.22L79.47,49.61Zm-19,8.48v-2.5a24.92,24.92,0,0,0-3.74-.2A33.25,33.25,0,0,0,59,56.2l-1-5.7A30.47,30.47,0,0,1,67.13,49a22.86,22.86,0,0,1,5.48.47,6.91,6.91,0,0,1,2.5,1.11,5.62,5.62,0,0,1,1.78,4.55,5.84,5.84,0,0,1-3.2,5.56v.19a5.73,5.73,0,0,1,3.81,5.74,8.67,8.67,0,0,1-.63,3.49,6,6,0,0,1-1.6,2.24,7.15,7.15,0,0,1-2.55,1.25,25.64,25.64,0,0,1-6.61.66,37.78,37.78,0,0,1-8.54-1l1.08-6.37a27.22,27.22,0,0,0,6.21.89,35.79,35.79,0,0,0,4.35-.23V65.11l-6.63-.65V58.87l6.63-.78Zm49.27-26.55c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z"/>
- <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Zm.18,40.24a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94ZM3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z"/>
+ <defs>
+ <style>{`.cls-1{fill:#fe0000;`}</style>
+ </defs>
+ <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.61l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.26l5.36,12.45H93.74l-2.9-6.94H90.5l-2.86,6.94H79.47l5.24-12.22L79.47,49.61Zm-19,8.48v-2.5a24.92,24.92,0,0,0-3.74-.2A33.25,33.25,0,0,0,59,56.2l-1-5.7A30.47,30.47,0,0,1,67.13,49a22.86,22.86,0,0,1,5.48.47,6.91,6.91,0,0,1,2.5,1.11,5.62,5.62,0,0,1,1.78,4.55,5.84,5.84,0,0,1-3.2,5.56v.19a5.73,5.73,0,0,1,3.81,5.74,8.67,8.67,0,0,1-.63,3.49,6,6,0,0,1-1.6,2.24,7.15,7.15,0,0,1-2.55,1.25,25.64,25.64,0,0,1-6.61.66,37.78,37.78,0,0,1-8.54-1l1.08-6.37a27.22,27.22,0,0,0,6.21.89,35.79,35.79,0,0,0,4.35-.23V65.11l-6.63-.65V58.87l6.63-.78Zm49.27-26.55c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z" />
+ <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Zm.18,40.24a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94ZM3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z" />
</svg>
);
-
diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts
index 35153f439..f4bae66bb 100644
--- a/src/client/views/nodes/MapBox/AnimationUtility.ts
+++ b/src/client/views/nodes/MapBox/AnimationUtility.ts
@@ -87,25 +87,24 @@ export class AnimationUtility {
@computed get currentPitch(): number {
if (!this.isStreetViewAnimation) return 50;
if (!this.terrainDisplayed) return 80;
- else {
- // const groundElevation = 0;
- const heightAboveGround = this.currentAnimationAltitude;
- const horizontalDistance = 500;
-
- let pitch;
- if (heightAboveGround >= 0) {
- pitch = 90 - Math.atan(heightAboveGround / horizontalDistance) * (180 / Math.PI);
- } else {
- pitch = 80;
- }
- console.log(Math.max(50, Math.min(pitch, 85)));
+ // const groundElevation = 0;
+ const heightAboveGround = this.currentAnimationAltitude;
+ const horizontalDistance = 500;
- if (this.previousPitch) {
- return this.lerp(Math.max(50, Math.min(pitch, 85)), this.previousPitch, 0.02);
- }
- return Math.max(50, Math.min(pitch, 85));
+ let pitch;
+ if (heightAboveGround >= 0) {
+ pitch = 90 - Math.atan(heightAboveGround / horizontalDistance) * (180 / Math.PI);
+ } else {
+ pitch = 80;
+ }
+
+ console.log(Math.max(50, Math.min(pitch, 85)));
+
+ if (this.previousPitch) {
+ return this.lerp(Math.max(50, Math.min(pitch, 85)), this.previousPitch, 0.02);
}
+ return Math.max(50, Math.min(pitch, 85));
}
@computed get flyInEndPitch() {
@@ -214,8 +213,9 @@ export class AnimationUtility {
currentAnimationPhase: number;
updateAnimationPhase: (newAnimationPhase: number) => void;
updateFrameId: (newFrameId: number) => void;
- }) => {
- return new Promise<void>(async resolve => {
+ }) =>
+ // eslint-disable-next-line no-async-promise-executor
+ new Promise<void>(async resolve => {
let startTime: number | null = null;
const frame = async (currentTime: number) => {
@@ -257,7 +257,7 @@ export class AnimationUtility {
updateAnimationPhase(animationPhase);
// compute corrected camera ground position, so that he leading edge of the path is in view
- var correctedPosition = this.computeCameraPosition(
+ const correctedPosition = this.computeCameraPosition(
this.isStreetViewAnimation,
this.currentPitch,
bearing,
@@ -277,7 +277,7 @@ export class AnimationUtility {
map.setFreeCameraOptions(camera);
this.previousAltitude = this.currentAnimationAltitude;
- this.previousPitch = this.previousPitch;
+ // this.previousPitch = this.previousPitch;
// repeat!
const innerFrameId = await window.requestAnimationFrame(frame);
@@ -287,15 +287,15 @@ export class AnimationUtility {
const outerFrameId = await window.requestAnimationFrame(frame);
updateFrameId(outerFrameId);
});
- };
- public flyInAndRotate = async ({ map, updateFrameId }: { map: MapRef; updateFrameId: (newFrameId: number) => void }) => {
- return new Promise<{ bearing: number; altitude: number }>(async resolve => {
+ public flyInAndRotate = async ({ map, updateFrameId }: { map: MapRef; updateFrameId: (newFrameId: number) => void }) =>
+ // eslint-disable-next-line no-async-promise-executor
+ new Promise<{ bearing: number; altitude: number }>(async resolve => {
let start: number | null;
- var currentAltitude;
- var currentBearing;
- var currentPitch;
+ let currentAltitude;
+ let currentBearing;
+ let currentPitch;
// the animation frame will run as many times as necessary until the duration has been reached
const frame = async (time: number) => {
@@ -319,7 +319,7 @@ export class AnimationUtility {
currentPitch = this.FLY_IN_START_PITCH + (this.flyInEndPitch - this.FLY_IN_START_PITCH) * d3.easeCubicOut(animationPhase);
// compute corrected camera ground position, so the start of the path is always in view
- var correctedPosition = this.computeCameraPosition(false, currentPitch, currentBearing, this.FIRST_LNG_LAT, currentAltitude);
+ const correctedPosition = this.computeCameraPosition(false, currentPitch, currentBearing, this.FIRST_LNG_LAT, currentAltitude);
// set the pitch and bearing of the camera
const camera = map.getFreeCameraOptions();
@@ -349,13 +349,10 @@ export class AnimationUtility {
const outerFrameId = await window.requestAnimationFrame(frame);
updateFrameId(outerFrameId);
});
- };
previousCameraPosition: { lng: number; lat: number } | null = null;
- lerp = (start: number, end: number, amt: number) => {
- return (1 - amt) * start + amt * end;
- };
+ lerp = (start: number, end: number, amt: number) => (1 - amt) * start + amt * end;
computeCameraPosition = (isStreetViewAnimation: boolean, pitch: number, bearing: number, targetPosition: { lng: number; lat: number }, altitude: number, smooth = false) => {
const bearingInRadian = (bearing * Math.PI) / 180;
diff --git a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
index 7e99795b5..b8fd8ac6a 100644
--- a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
@@ -4,15 +4,17 @@ import { IconButton } from 'browndash-components';
import { IReactionDisposer, ObservableMap, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { returnFalse, unimplementedFunction } from '../../../../Utils';
+import { returnFalse } from '../../../../ClientUtils';
+import { unimplementedFunction } from '../../../../Utils';
import { Doc, Opt } from '../../../../fields/Doc';
import { NumCast, StrCast } from '../../../../fields/Types';
-import { SelectionManager } from '../../../util/SelectionManager';
import { SettingsManager } from '../../../util/SettingsManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { DocumentView } from '../DocumentView';
@observer
export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
+ // eslint-disable-next-line no-use-before-define
static Instance: DirectionsAnchorMenu;
private _disposer: IReactionDisposer | undefined;
@@ -23,8 +25,8 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public OnClick: (e: PointerEvent) => void = unimplementedFunction;
// public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
- public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined;
- public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
+ public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = () => undefined;
+ public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
public Delete: () => void = unimplementedFunction;
// public MakeTargetToggle: () => void = unimplementedFunction;
// public ShowTargetTrail: () => void = unimplementedFunction;
@@ -54,8 +56,8 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
componentDidMount() {
this._disposer = reaction(
- () => SelectionManager.Views.slice(),
- sel => DirectionsAnchorMenu.Instance.fadeOut(true)
+ () => DocumentView.Selected().slice(),
+ () => DirectionsAnchorMenu.Instance.fadeOut(true)
);
}
// audioDown = (e: React.PointerEvent) => {
@@ -103,8 +105,8 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
color={SettingsManager.userColor}
/>
- <IconButton tooltip="Animate route" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />
- <IconButton tooltip="Add to calendar" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
+ <IconButton tooltip="Animate route" onPointerDown={this.Delete} /* *TODO: fix */ icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />
+ <IconButton tooltip="Add to calendar" onPointerDown={this.Delete} /* *TODO: fix */ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
</div>
);
diff --git a/src/client/views/nodes/MapBox/GeocoderControl.tsx b/src/client/views/nodes/MapBox/GeocoderControl.tsx
index e4ba51316..e118c57d9 100644
--- a/src/client/views/nodes/MapBox/GeocoderControl.tsx
+++ b/src/client/views/nodes/MapBox/GeocoderControl.tsx
@@ -3,8 +3,6 @@
// import { ControlPosition, MarkerProps, useControl } from "react-map-gl";
// import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css'
-
-
// export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & {
// mapboxAccessToken: string;
// marker?: Omit<MarkerProps, 'longitude' | 'latitude'>;
@@ -31,7 +29,6 @@
// ctrl.on('results', props.onResults);
// ctrl.on('result', evt => {
// props.onResult(evt);
-
// // const {result} = evt;
// // const location =
// // result &&
@@ -49,8 +46,6 @@
// position: props.position
// }
// );
-
-
// // @ts-ignore (TS2339) private member
// if (geocoder._map) {
// if (geocoder.getProximity() !== props.proximity && props.proximity !== undefined) {
@@ -104,4 +99,4 @@
// onLoading: noop,
// onResults: noop,
// onError: noop
-// }; \ No newline at end of file
+// };
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index 08bea5d9d..103a35434 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/button-has-type */
import { IconLookup, faAdd, faArrowDown, faArrowLeft, faArrowsRotate, faBicycle, faCalendarDays, faCar, faDiamondTurnRight, faEdit, faPersonWalking, faRoute } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material';
@@ -7,13 +8,14 @@ import { IReactionDisposer, ObservableMap, action, makeObservable, observable, r
import { observer } from 'mobx-react';
import * as React from 'react';
import { CirclePicker, ColorResult } from 'react-color';
-import { returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../../Utils';
+import { returnFalse, setupMoveUpEvents } from '../../../../ClientUtils';
+import { unimplementedFunction } from '../../../../Utils';
import { Doc, Opt } from '../../../../fields/Doc';
import { NumCast, StrCast } from '../../../../fields/Types';
import { CalendarManager } from '../../../util/CalendarManager';
-import { SelectionManager } from '../../../util/SelectionManager';
import { SettingsManager } from '../../../util/SettingsManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { DocumentView } from '../DocumentView';
import './MapAnchorMenu.scss';
import { MapboxApiUtility, TransportationType } from './MapboxApiUtility';
import { MarkerIcons } from './MarkerIcons';
@@ -23,6 +25,7 @@ type MapAnchorMenuType = 'standard' | 'routeCreation' | 'calendar' | 'customize'
@observer
export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
+ // eslint-disable-next-line no-use-before-define
static Instance: MapAnchorMenu;
private _disposer: IReactionDisposer | undefined;
@@ -35,8 +38,8 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public OnClick: (e: PointerEvent) => void = unimplementedFunction;
// public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
- public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined;
- public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
+ public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = () => undefined;
+ public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
public Delete: () => void = unimplementedFunction;
// public MakeTargetToggle: () => void = unimplementedFunction;
// public ShowTargetTrail: () => void = unimplementedFunction;
@@ -123,8 +126,8 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
componentDidMount() {
this._disposer = reaction(
- () => SelectionManager.Views.slice(),
- sel => MapAnchorMenu.Instance.fadeOut(true)
+ () => DocumentView.Selected().slice(),
+ () => MapAnchorMenu.Instance.fadeOut(true)
);
}
// audioDown = (e: React.PointerEvent) => {
@@ -147,12 +150,12 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
setupMoveUpEvents(
this,
e,
- (e: PointerEvent) => {
- this.StartDrag(e, this._commentRef.current!);
+ moveEv => {
+ this.StartDrag(moveEv, this._commentRef.current!);
return true;
},
returnFalse,
- e => this.OnClick(e)
+ clickEv => this.OnClick(clickEv)
);
};
@@ -274,7 +277,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
HandleAddRouteClick = () => {
if (this.currentRouteInfoMap && this.selectedTransportationType && this.selectedDestinationFeature) {
- const coordinates = this.currentRouteInfoMap[this.selectedTransportationType].coordinates;
+ const { coordinates } = this.currentRouteInfoMap[this.selectedTransportationType];
console.log(coordinates);
console.log(this.selectedDestinationFeature);
this.AddNewRouteToMap(coordinates, this.title ?? '', this.selectedDestinationFeature, this.createPinForDestination);
@@ -293,34 +296,30 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
getDirectionsButton: JSX.Element = (<IconButton tooltip="Get directions" onPointerDown={this.DirectionsClick} icon={<FontAwesomeIcon icon={faDiamondTurnRight as IconLookup} />} color={SettingsManager.userColor} />);
- getAddToCalendarButton = (docType: string): JSX.Element => {
- return (
- <IconButton
- tooltip="Add to calendar"
- onPointerDown={() => {
- CalendarManager.Instance.open(undefined, docType === 'pin' ? this.pinDoc : this.routeDoc);
- }}
- icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />}
- color={SettingsManager.userColor}
- />
- );
- };
+ getAddToCalendarButton = (docType: string): JSX.Element => (
+ <IconButton
+ tooltip="Add to calendar"
+ onPointerDown={() => {
+ CalendarManager.Instance.open(undefined, docType === 'pin' ? this.pinDoc : this.routeDoc);
+ }}
+ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />}
+ color={SettingsManager.userColor}
+ />
+ );
addToCalendarButton: JSX.Element = (
<IconButton tooltip="Add to calendar" onPointerDown={() => CalendarManager.Instance.open(undefined, this.pinDoc)} icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
);
- getLinkNoteToDocButton = (docType: string): JSX.Element => {
- return (
- <div ref={this._commentRef}>
- <IconButton
- tooltip={`Link Note to ${docType === 'pin' ? 'Pin' : 'Route'}`} //
- onPointerDown={this.notePointerDown}
- icon={<FontAwesomeIcon icon="sticky-note" />}
- color={SettingsManager.userColor}
- />
- </div>
- );
- };
+ getLinkNoteToDocButton = (docType: string): JSX.Element => (
+ <div ref={this._commentRef}>
+ <IconButton
+ tooltip={`Link Note to ${docType === 'pin' ? 'Pin' : 'Route'}`} //
+ onPointerDown={this.notePointerDown}
+ icon={<FontAwesomeIcon icon="sticky-note" />}
+ color={SettingsManager.userColor}
+ />
+ </div>
+ );
linkNoteToPinOrRoutenButton: JSX.Element = (
<div ref={this._commentRef}>
@@ -362,16 +361,14 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
/>
);
- getDeleteButton = (type: string) => {
- return (
- <IconButton
- tooltip={`Delete ${type === 'pin' ? 'Pin' : 'Route'}`} //
- onPointerDown={this.Delete}
- icon={<FontAwesomeIcon icon="trash-alt" />}
- color={SettingsManager.userColor}
- />
- );
- };
+ getDeleteButton = (type: string) => (
+ <IconButton
+ tooltip={`Delete ${type === 'pin' ? 'Pin' : 'Route'}`} //
+ onPointerDown={this.Delete}
+ icon={<FontAwesomeIcon icon="trash-alt" />}
+ color={SettingsManager.userColor}
+ />
+ );
animateRouteButton: JSX.Element = (<IconButton tooltip="Animate route" onPointerDown={() => this.OpenAnimationPanel(this.routeDoc)} icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />);
@@ -452,18 +449,17 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
}}
options={this.destinationFeatures.filter(feature => feature.place_name).map(feature => feature)}
getOptionLabel={(feature: any) => feature.place_name}
+ // eslint-disable-next-line react/jsx-props-no-spreading
renderInput={(params: any) => <TextField {...params} placeholder="Enter a destination" />}
/>
- {this.selectedDestinationFeature && (
- <>
- {!this.allMapPinDocs.some(pinDoc => pinDoc.title === this.selectedDestinationFeature.place_name) && (
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '5px' }}>
- <FormControlLabel label="Create pin for destination?" control={<Checkbox color="success" checked={this.createPinForDestination} onChange={this.toggleCreatePinForDestinationCheckbox} />} />
- </div>
- )}
- </>
- )}
- <button id="get-routes-button" disabled={this.selectedDestinationFeature ? false : true} onClick={() => this.getRoutes(this.selectedDestinationFeature)}>
+ {!this.selectedDestinationFeature
+ ? null
+ : !this.allMapPinDocs.some(pinDoc => pinDoc.title === this.selectedDestinationFeature.place_name) && (
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '5px' }}>
+ <FormControlLabel label="Create pin for destination?" control={<Checkbox color="success" checked={this.createPinForDestination} onChange={this.toggleCreatePinForDestinationCheckbox} />} />
+ </div>
+ )}
+ <button id="get-routes-button" disabled={!this.selectedDestinationFeature} onClick={() => this.getRoutes(this.selectedDestinationFeature)}>
Get routes
</button>
@@ -516,7 +512,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
</div>
))}
</div>
- <div style={{ width: '100%', height: '3px', color: 'white' }}></div>
+ <div style={{ width: '100%', height: '3px', color: 'white' }} />
</div>
)}
{this.menuType === 'route' && this.routeDoc && <div>{StrCast(this.routeDoc.title)}</div>}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index b73898f59..ac8010f11 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { IconLookup, faCircleXmark, faGear, faPause, faPlay, faRotate } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Checkbox, FormControlLabel, TextField } from '@mui/material';
@@ -5,31 +7,30 @@ import * as turf from '@turf/turf';
import { IconButton, Size, Type } from 'browndash-components';
import * as d3 from 'd3';
import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, Position } from 'geojson';
-import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent } from 'mapbox-gl';
+import mapboxgl, { LngLatBoundsLike, MapLayerMouseEvent } from 'mapbox-gl';
import { IReactionDisposer, ObservableMap, action, autorun, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { CirclePicker, ColorResult } from 'react-color';
import { Layer, MapProvider, MapRef, Map as MapboxMap, Marker, Source, ViewState, ViewStateChangeEvent } from 'react-map-gl';
import { MarkerEvent } from 'react-map-gl/dist/esm/types';
-import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils';
+import { ClientUtils, setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
-import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
+import { DocCast, NumCast, StrCast, toList } from '../../../../fields/Types';
+import { DocUtils } from '../../../documents/DocUtils';
import { DocumentType } from '../../../documents/DocumentTypes';
-import { DocUtils, Docs } from '../../../documents/Documents';
-import { DocumentManager } from '../../../util/DocumentManager';
+import { Docs } from '../../../documents/Documents';
import { DragManager } from '../../../util/DragManager';
-import { LinkManager } from '../../../util/LinkManager';
-import { SnappingManager } from '../../../util/SnappingManager';
import { UndoManager, undoable } from '../../../util/UndoManager';
import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
+import { PinDocView, PinProps } from '../../PinFuncs';
import { SidebarAnnos } from '../../SidebarAnnos';
import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
import { Colors } from '../../global/globalEnums';
import { DocumentView } from '../DocumentView';
-import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
-import { FormattedTextBox } from '../formattedText/FormattedTextBox';
-import { PinProps, PresBox } from '../trails';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { FocusViewOptions } from '../FocusViewOptions';
import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
import { AnimationSpeed, AnimationStatus, AnimationUtility } from './AnimationUtility';
import { MapAnchorMenu } from './MapAnchorMenu';
@@ -53,9 +54,6 @@ import { MarkerIcons } from './MarkerIcons';
*/
const MAPBOX_ACCESS_TOKEN = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNscHgwNDd1MDA3MXIydm92ODdianp6cGYifQ.WFAqbhwxtMHOWSPtu0l2uQ';
-const MAPBOX_FORWARD_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
-
-const MAPBOX_REVERSE_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
type PopupInfo = {
longitude: number;
@@ -111,13 +109,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
};
// this list contains pushpins and configs
- @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } //prettier-ignore
- @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } //prettier-ignore
- @computed get allPushpins() { return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN); } //prettier-ignore
- @computed get allRoutes() { return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE); } //prettier-ignore
- @computed get SidebarShown() { return this.layoutDoc._layout_showSidebar ? true : false; } //prettier-ignore
- @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } //prettier-ignore
- @computed get SidebarKey() { return this.fieldKey + '_sidebar'; } //prettier-ignore
+ @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } // prettier-ignore
+ @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } // prettier-ignore
+ @computed get allPushpins() { return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN); } // prettier-ignore
+ @computed get allRoutes() { return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE); } // prettier-ignore
+ @computed get SidebarShown() { return !!this.layoutDoc._layout_showSidebar; } // prettier-ignore
+ @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } // prettier-ignore
+ @computed get SidebarKey() { return this.fieldKey + '_sidebar'; } // prettier-ignore
@computed get sidebarColor() {
return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
}
@@ -235,14 +233,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
/**
* 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 docs
* @param sidebarKey
* @returns
*/
- sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
+ sidebarAddDocument = (docs: Doc | Doc[], sidebarKey?: string) => {
if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
- const docs = doc instanceof Doc ? [doc] : doc;
- docs.forEach(doc => {
+ toList(docs).forEach(doc => {
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));
@@ -250,7 +247,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
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)) {
+ if (!Doc.Links(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;
@@ -258,14 +255,17 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
}
});
}
- }); //add to annotation list
+ }); // add to annotation list
- return this.addDocument(doc, sidebarKey); // add to sidebar list
+ return this.addDocument(docs, sidebarKey); // add to sidebar list
};
removeMapDocument = (doc: Doc | Doc[], annotationKey?: string) => {
- const docs = doc instanceof Doc ? [doc] : doc;
- this.allAnnotations.filter(anno => docs.includes(DocCast(anno.mapPin))).forEach(anno => (anno.mapPin = undefined));
+ this.allAnnotations
+ .filter(anno => toList(doc).includes(DocCast(anno.mapPin)))
+ .forEach(anno => {
+ anno.mapPin = undefined;
+ });
return this.removeDocument(doc, annotationKey, undefined);
};
@@ -285,7 +285,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
setupMoveUpEvents(
this,
e,
- (e, down, delta) =>
+ (moveEv, down, delta) =>
runInAction(() => {
const localDelta = this._props
.ScreenToLocalTransform()
@@ -325,7 +325,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
}}
onPointerDown={this.sidebarBtnDown}>
- <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
</div>
);
}
@@ -354,16 +354,16 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const targetCreator = (annotationOn: Doc | undefined) => {
const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
- FormattedTextBox.SetSelectOnLoad(target);
+ Doc.SetSelectOnLoad(target);
return target;
};
const docView = this.DocumentView?.();
docView &&
DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
- dragComplete: e => {
- if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
- e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document;
- e.annoDragData.linkSourceDoc.followLinkZoom = false;
+ dragComplete: dragEv => {
+ if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) {
+ dragEv.annoDragData.linkSourceDoc.followLinkToggle = dragEv.annoDragData.dropDocument.annotationOn === this.Document;
+ dragEv.annoDragData.linkSourceDoc.followLinkZoom = false;
}
},
});
@@ -389,7 +389,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
sidebarDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
};
- sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
+ sidebarMove = (e: PointerEvent) => {
const bounds = this._ref.current!.getBoundingClientRect();
this.layoutDoc._layout_sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%';
this.layoutDoc._layout_showSidebar = this.layoutDoc._layout_sidebarWidthPercent !== '0%';
@@ -401,8 +401,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
- transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
- opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
+ transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter];
+ opaqueFilter = () => [...this._props.childFilters(), ClientUtils.OpaqueBackgroundFilter];
infoWidth = () => this._props.PanelWidth() / 5;
infoHeight = () => this._props.PanelHeight() / 5;
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
@@ -435,7 +435,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
this.toggleSidebar();
options.didMove = true;
}
- return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ return new Promise<Opt<DocumentView>>(res => {
+ DocumentView.addViewRenderedCb(doc, dv => res(dv));
+ });
};
/*
@@ -458,7 +460,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
if (anchor) {
if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
addAsAnnotation && this.addDocument(anchor);
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.Document);
+ PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.Document);
return anchor;
}
return this.Document;
@@ -526,6 +528,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu);
};
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
recolorPin = (pin: Doc, color?: string) => {
// this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin));
// this.map_docToPinMap.delete(pin);
@@ -580,6 +583,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
MapAnchorMenu.Instance.fadeOut(true);
return mapRoute;
}
+ return undefined;
// TODO: Display error that can't create route to same location
}, 'createmaproute');
@@ -652,9 +656,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
console.error(features);
if (features && features.length > 0 && features[0].properties && features[0].geometry) {
- const geometry = features[0].geometry as LineString;
- const routeTitle: string = features[0].properties['routeTitle'];
- const routeDoc: Doc | undefined = this.allRoutes.find(routeDoc => routeDoc.title === routeTitle);
+ const { routeTitle } = features[0].properties;
+ const routeDoc: Doc | undefined = this.allRoutes.find(rtDoc => rtDoc.title === routeTitle);
this.deselectPinOrRoute(); // TODO: Also deselect route if selected
if (routeDoc) {
this._selectedPinOrRoute = routeDoc;
@@ -698,7 +701,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
*/
handleMapDblClick = async (e: MapLayerMouseEvent) => {
e.preventDefault();
- const lngLat: LngLat = e.lngLat;
+ const { lngLat } = e;
const longitude: number = lngLat.lng;
const latitude: number = lngLat.lat;
@@ -834,6 +837,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
if (!this._isAnimating) {
return this.mapboxMapViewState;
}
+ return undefined;
}
@action
@@ -917,69 +921,73 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
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));
+ runInAction(
+ () =>
+ // eslint-disable-next-line no-async-promise-executor
+ 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);
+ }
- const updateFrameId = (newFrameId: number) => this.setFrameId(newFrameId);
- const updateAnimationPhase = (newAnimationPhase: number) => this.setAnimationPhase(newAnimationPhase);
+ runInAction(() => {
+ this._finishedFlyTo = true;
+ });
- if (status !== AnimationStatus.RESUME) {
- const result = await animationUtil.flyInAndRotate({
+ // 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!,
- // targetLngLat,
- // duration 3000
- // startAltitude: 3000000,
- // endAltitude: this.isStreetViewAnimation ? 80 : 12000,
- // startBearing: 0,
- // endBearing: -20,
- // startPitch: 40,
- // endPitch: this.isStreetViewAnimation ? 80 : 50,
+ // path: this.path,
+ // startBearing: -20,
+ // startAltitude: this.isStreetViewAnimation ? 80 : 12000,
+ // pitch: this.isStreetViewAnimation ? 80: 50,
+ currentAnimationPhase: this._animationPhase,
+ updateAnimationPhase,
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);
+ // 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]];
+ const bbox2d: LngLatBoundsLike = [bbox3d[0], bbox3d[1], bbox3d[2], bbox3d[3]];
- this._mapRef.current!.fitBounds(bbox2d, {
- duration: 3000,
- pitch: 30,
- bearing: 0,
- padding: 120,
- });
+ this._mapRef.current!.fitBounds(bbox2d, {
+ duration: 3000,
+ pitch: 30,
+ bearing: 0,
+ padding: 120,
+ });
- setTimeout(() => {
- this._isStreetViewAnimation = false;
- resolve();
- }, 10000);
- });
- });
+ setTimeout(() => {
+ this._isStreetViewAnimation = false;
+ resolve();
+ }, 10000);
+ })
+ );
};
@action
@@ -1008,53 +1016,49 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
}
};
- getRouteAnimationOptions = (): JSX.Element => {
- return (
- <>
+ getRouteAnimationOptions = (): JSX.Element => (
+ <>
+ <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={this._isAnimating && this._finishedFlyTo ? 'Pause Animation' : 'Play Animation'}
+ tooltip="Restart 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
- }
+ this.stopAnimation(false);
+ this.playAnimation(AnimationStatus.START);
}}
- icon={this._isAnimating && this._finishedFlyTo ? <FontAwesomeIcon icon={faPause as IconLookup} /> : <FontAwesomeIcon icon={faPlay as IconLookup} />}
+ icon={<FontAwesomeIcon icon={faRotate 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>
- </>
- </>
- );
- };
+ )}
+ <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 = () => {
@@ -1066,7 +1070,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
@action
toggleSettings = () => {
- if (!this._isAnimating && this._animationPhase == 0) {
+ if (!this._isAnimating && this._animationPhase === 0) {
this._featuresFromGeocodeResults = [];
this._settingsOpen = !this._settingsOpen;
}
@@ -1123,7 +1127,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
};
@action
- onMapZoom = (e: ViewStateChangeEvent) => (this.dataDoc.map_zoom = e.viewState.zoom);
+ onMapZoom = (e: ViewStateChangeEvent) => {
+ this.dataDoc.map_zoom = e.viewState.zoom;
+ };
@action
onMapMove = (e: ViewStateChangeEvent) => {
@@ -1132,7 +1138,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
};
@action
- toggleShowTerrain = () => (this._showTerrain = !this._showTerrain);
+ toggleShowTerrain = () => {
+ this._showTerrain = !this._showTerrain;
+ };
getMarkerIcon = (pinDoc: Doc): JSX.Element | null => {
const markerType = StrCast(pinDoc.markerType);
@@ -1146,7 +1154,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const scale = this._props.NativeDimScaling?.() || 1;
const parscale = scale === 1 ? 1 : this.ScreenToLocalBoxXf().Scale ?? 1;
- const renderAnnotations = (childFilters?: () => string[]) => null;
return (
<div className="mapBox" ref={this._ref}>
<div
@@ -1154,15 +1161,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
onWheel={e => e.stopPropagation()}
onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
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.IsDragging ? null : renderAnnotations()}
{!this._routeToAnimate && (
<div className="mapBox-searchbar" style={{ width: `${100 / scale}%`, zIndex: 1, position: 'relative', background: 'lightGray' }}>
<TextField ref={this._textRef} fullWidth placeholder="Enter a location" onKeyDown={this.searchbarKeyDown} onChange={(e: any) => this.handleSearchChange(e.target.value)} />
- <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={e => this.toggleSettings()} />
+ <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={() => this.toggleSettings()} />
<div style={{ opacity: 0 }}>
- <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={e => this.toggleSettings()} />
+ <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={() => this.toggleSettings()} />
</div>
</div>
)}
@@ -1210,22 +1214,21 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
)}
{this._featuresFromGeocodeResults.length > 0 && (
<div className="mapbox-geocoding-search-results">
- <>
- <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>
- ))}
- </>
+ <h4>Choose a location for your pin: </h4>
+ {this._featuresFromGeocodeResults
+ .filter(feature => feature.place_name)
+ .map((feature, idx) => (
+ <div
+ // eslint-disable-next-line react/no-array-index-key
+ key={idx}
+ className="search-result-container"
+ onClick={() => {
+ this.handleSearchChange('');
+ this.addMarkerForFeature(feature);
+ }}>
+ <div className="search-result-place-name">{feature.place_name}</div>
+ </div>
+ ))}
</div>
)}
<MapProvider>
@@ -1252,7 +1255,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
<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 && (
+ {!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) && (
@@ -1316,16 +1319,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
</>
)}
- <>
- {!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._isAnimating &&
+ this._animationPhase === 0 &&
+ this.allPushpins // .filter(anno => !anno.layout_unrendered)
+ .map((pushpin, idx) => (
+ // eslint-disable-next-line react/no-array-index-key
+ <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}/>
@@ -1336,12 +1338,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
<div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
<SidebarAnnos
ref={this._sidebarRef}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
fieldKey={this.fieldKey}
Document={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
- usePanelWidth={true}
+ usePanelWidth
showSidebar={this.SidebarShown}
nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
@@ -1356,3 +1359,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
);
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.MAP, {
+ layout: { view: MapBox, dataField: 'data' },
+ options: { acl: '', map: '', _height: 600, _width: 800, _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true, systemIcon: 'BsFillPinMapFill' },
+});
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index 9825824bd..7697fd295 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -509,9 +509,9 @@
// // 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());
+// // console.log("print the selected views in Document.Selected:")
+// // if (DocumentView.Selected().lastElement()) {
+// // console.log(DocumentView.Selected().lastElement());
// // }
// };
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index 6ccbbbe1c..c69cd8e89 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -32,7 +32,7 @@
// 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.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();
diff --git a/src/client/views/nodes/MapBox/MapPushpinBox.tsx b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
index 8ebc90157..f3dc44755 100644
--- a/src/client/views/nodes/MapBox/MapPushpinBox.tsx
+++ b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
@@ -2,6 +2,8 @@ import * as React from 'react';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { FieldView, FieldViewProps } from '../FieldView';
import { MapBoxContainer } from '../MapboxMapBox/MapboxContainer';
+import { Docs } from '../../../documents/Documents';
+import { DocumentType } from '../../../documents/DocumentTypes';
/**
* Map Pushpin doc class
@@ -28,3 +30,8 @@ export class MapPushpinBox extends ViewBoxBaseComponent<FieldViewProps>() {
return <div />;
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.PUSHPIN, {
+ layout: { view: MapPushpinBox, dataField: 'data' },
+ options: { acl: '' },
+});
diff --git a/src/client/views/nodes/MapBox/MapboxApiUtility.ts b/src/client/views/nodes/MapBox/MapboxApiUtility.ts
index 592330ac2..5c5192372 100644
--- a/src/client/views/nodes/MapBox/MapboxApiUtility.ts
+++ b/src/client/views/nodes/MapBox/MapboxApiUtility.ts
@@ -1,4 +1,3 @@
-
const MAPBOX_FORWARD_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
const MAPBOX_REVERSE_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
const MAPBOX_DIRECTIONS_BASE_URL = 'https://api.mapbox.com/directions/v5/mapbox';
@@ -7,92 +6,79 @@ const MAPBOX_ACCESS_TOKEN = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNscHgwNDd1MDA3
export type TransportationType = 'driving' | 'cycling' | 'walking';
export class MapboxApiUtility {
-
static forwardGeocodeForFeatures = async (searchText: string) => {
try {
- const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) +'.json' +`?access_token=${MAPBOX_ACCESS_TOKEN}`;
+ const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) + `.json?access_token=${MAPBOX_ACCESS_TOKEN}`;
const response = await fetch(url);
const data = await response.json();
return data.features;
- } catch (error: any){
- // TODO: handle error in better way
+ } catch (error: any) {
+ // TODO: handle error in better way
return null;
}
- }
+ };
static reverseGeocodeForFeatures = async (longitude: number, latitude: number) => {
try {
- const url = MAPBOX_REVERSE_GEOCODE_BASE_URL + encodeURI(longitude.toString() + "," + latitude.toString()) + '.json' +
- `?access_token=${MAPBOX_ACCESS_TOKEN}`;
+ const url = MAPBOX_REVERSE_GEOCODE_BASE_URL + encodeURI(longitude.toString() + ',' + latitude.toString()) + `.json?access_token=${MAPBOX_ACCESS_TOKEN}`;
const response = await fetch(url);
const data = await response.json();
return data.features;
- } catch (error: any){
+ } catch (error: any) {
return null;
}
- }
+ };
static getDirections = async (origin: number[], destination: number[]): Promise<Record<TransportationType, any> | undefined> => {
try {
-
const directionsPromises: Promise<any>[] = [];
const transportationTypes: TransportationType[] = ['driving', 'cycling', 'walking'];
- transportationTypes.forEach((type) => {
- directionsPromises.push(
- fetch(
- `${MAPBOX_DIRECTIONS_BASE_URL}/${type}/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`
- ).then((response) => response.json())
- );
- });
+ transportationTypes.forEach(type => {
+ directionsPromises.push(fetch(`${MAPBOX_DIRECTIONS_BASE_URL}/${type}/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`).then(response => response.json()));
+ });
const results = await Promise.all(directionsPromises);
const routeInfoMap: Record<TransportationType, any> = {
- 'driving': {},
- 'cycling': {},
- 'walking': {},
+ driving: {},
+ cycling: {},
+ walking: {},
};
transportationTypes.forEach((type, index) => {
const routeData = results[index].routes[0];
if (routeData) {
- const geometry = routeData.geometry;
- const coordinates = geometry.coordinates;
-
- routeInfoMap[type] = {
- duration: this.secondsToMinutesHours(routeData.duration),
- distance: this.metersToMiles(routeData.distance),
- coordinates: coordinates,
- };
+ const { geometry } = routeData;
+ const { coordinates } = geometry;
+
+ routeInfoMap[type] = {
+ duration: this.secondsToMinutesHours(routeData.duration),
+ distance: this.metersToMiles(routeData.distance),
+ coordinates: coordinates,
+ };
}
- });
+ });
return routeInfoMap;
- // return current route info, and the temporary route
-
- } catch (error: any){
+ // return current route info, and the temporary route
+ } catch (error: any) {
return undefined;
- console.log("Error: ", error);
}
- }
+ };
private static secondsToMinutesHours = (seconds: number) => {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60).toFixed(2);
- if (hours === 0){
- return `${minutes} min`
- } else {
- return `${hours} hr ${minutes} min`
+ if (hours === 0) {
+ return `${minutes} min`;
}
- }
-
- private static metersToMiles = (meters: number) => {
- return `${parseFloat((meters/1609.34).toFixed(2))} mi`;
- }
+ return `${hours} hr ${minutes} min`;
+ };
+ private static metersToMiles = (meters: number) => `${parseFloat((meters / 1609.34).toFixed(2))} mi`;
}
// const drivingQuery = await fetch(
@@ -136,4 +122,4 @@ export class MapboxApiUtility {
// distance: this.metersToMiles(routeData.distance),
// coordinates: coordinates
// }
-// }) \ No newline at end of file
+// })
diff --git a/src/client/views/nodes/MapBox/MarkerIcons.tsx b/src/client/views/nodes/MapBox/MarkerIcons.tsx
index a580fcaa0..087472112 100644
--- a/src/client/views/nodes/MapBox/MarkerIcons.tsx
+++ b/src/client/views/nodes/MapBox/MarkerIcons.tsx
@@ -17,8 +17,6 @@ import {
faHouse,
faLandmark,
faLocationDot,
- faLocationPin,
- faMapPin,
faMasksTheater,
faMugSaucer,
faPersonHiking,
@@ -65,6 +63,7 @@ export class MarkerIcons {
iconProps.color = color;
}
+ // eslint-disable-next-line react/jsx-props-no-spreading
return <FontAwesomeIcon {...iconProps} size={size} />;
}
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index 3eb051dbf..bfd40692b 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -4,28 +4,28 @@ import { IReactionDisposer, ObservableMap, action, computed, makeObservable, obs
import { observer } from 'mobx-react';
import * as React from 'react';
import { MapProvider, Map as MapboxMap } from 'react-map-gl';
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils';
+import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
import { DocCss, Highlight } from '../../../../fields/DocSymbols';
-import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
+import { Id } from '../../../../fields/FieldSymbols';
+import { DocCast, NumCast, StrCast, toList } from '../../../../fields/Types';
+import { DocUtils } from '../../../documents/DocUtils';
import { DocumentType } from '../../../documents/DocumentTypes';
-import { DocUtils, Docs } from '../../../documents/Documents';
-import { DocumentManager } from '../../../util/DocumentManager';
+import { Docs } from '../../../documents/Documents';
import { DragManager } from '../../../util/DragManager';
-import { LinkManager } from '../../../util/LinkManager';
-import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { UndoManager, undoable } from '../../../util/UndoManager';
import { ViewBoxAnnotatableComponent } from '../../DocComponent';
+import { PinDocView, PinProps } from '../../PinFuncs';
import { SidebarAnnos } from '../../SidebarAnnos';
import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
import { Colors } from '../../global/globalEnums';
import { DocumentView } from '../DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { FocusViewOptions } from '../FocusViewOptions';
import { MapAnchorMenu } from '../MapBox/MapAnchorMenu';
-import { FormattedTextBox } from '../formattedText/FormattedTextBox';
-import { PinProps, PresBox } from '../trails';
-import './MapBox.scss';
+import '../MapBox/MapBox.scss';
/**
* MapBox architecture:
@@ -41,7 +41,6 @@ import './MapBox.scss';
*/
const mapboxApiKey = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNsbnc2eHJpbTA1ZTUyam85aGx4Z2FhbGwifQ.2Kqw9mk-9wAAg9kmHmKzcg';
-const bingApiKey = process.env.BING_MAPS; // if you're running local, get a Bing Maps api key here: https://www.bingmapsportal.com/ and then add it to the .env file in the Dash-Web root directory as: _CLIENT_BING_MAPS=<your apikey>
/**
* Consider integrating later: allows for drawing, circling, making shapes on map
@@ -87,7 +86,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN);
}
@computed get SidebarShown() {
- return this.layoutDoc._layout_showSidebar ? true : false;
+ return !!this.layoutDoc._layout_showSidebar;
}
@computed get sidebarWidthPercent() {
return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
@@ -118,9 +117,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
* @param sidebarKey
* @returns
*/
- sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
+ sidebarAddDocument = (docsIn: Doc | Doc[], sidebarKey?: string) => {
if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
- const docs = doc instanceof Doc ? [doc] : doc;
+ const docs = toList(docsIn);
docs.forEach(doc => {
let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this.selectedPin;
if (doc.latitude !== undefined && doc.longitude !== undefined && !existingPin) {
@@ -129,7 +128,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
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)) {
+ if (!Doc.Links(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;
@@ -137,15 +136,19 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
}
});
}
- }); //add to annotation list
+ }); // add to annotation list
- return this.addDocument(doc, sidebarKey); // add to sidebar list
+ return this.addDocument(docs, sidebarKey); // add to sidebar list
};
- removeMapDocument = (doc: Doc | Doc[], annotationKey?: string) => {
- const docs = doc instanceof Doc ? [doc] : doc;
- this.allAnnotations.filter(anno => docs.includes(DocCast(anno.mapPin))).forEach(anno => (anno.mapPin = undefined));
- return this.removeDocument(doc, annotationKey, undefined);
+ removeMapDocument = (docsIn: Doc | Doc[], annotationKey?: string) => {
+ const docs = toList(docsIn);
+ this.allAnnotations
+ .filter(anno => docs.includes(DocCast(anno.mapPin)))
+ .forEach(anno => {
+ anno.mapPin = undefined;
+ });
+ return this.removeDocument(docsIn, annotationKey, undefined);
};
/**
@@ -164,7 +167,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
setupMoveUpEvents(
this,
e,
- (e, down, delta) =>
+ (moveEv, down, delta) =>
runInAction(() => {
const localDelta = this._props
.ScreenToLocalTransform()
@@ -204,7 +207,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
}}
onPointerDown={this.sidebarBtnDown}>
- <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
</div>
);
}
@@ -233,16 +236,16 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
const targetCreator = (annotationOn: Doc | undefined) => {
const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
- FormattedTextBox.SetSelectOnLoad(target);
+ Doc.SetSelectOnLoad(target);
return target;
};
const docView = this.DocumentView?.();
docView &&
DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
- dragComplete: e => {
- if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
- e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document;
- e.annoDragData.linkSourceDoc.followLinkZoom = false;
+ dragComplete: dragEv => {
+ if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) {
+ dragEv.annoDragData.linkSourceDoc.followLinkToggle = dragEv.annoDragData.dropDocument.annotationOn === this.Document;
+ dragEv.annoDragData.linkSourceDoc.followLinkZoom = false;
}
},
});
@@ -268,7 +271,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
sidebarDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
};
- sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
+ sidebarMove = (e: PointerEvent) => {
const bounds = this._ref.current!.getBoundingClientRect();
this.layoutDoc._layout_sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%';
this.layoutDoc._layout_showSidebar = this.layoutDoc._layout_sidebarWidthPercent !== '0%';
@@ -276,7 +279,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
return false;
};
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => {
+ this._setPreviewCursor = func;
+ };
addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey);
@@ -285,8 +290,8 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
- transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
- opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
+ transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter];
+ opaqueFilter = () => [...this._props.childFilters(), ClientUtils.OpaqueBackgroundFilter];
infoWidth = () => this._props.PanelWidth() / 5;
infoHeight = () => this._props.PanelHeight() / 5;
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
@@ -306,11 +311,11 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
// center: new this.MicrosoftMaps.Location(loc.latitude, loc.longitude),
// });
//
- bingGeocode = (map: any, query: string) => {
- return new Promise<{ latitude: number; longitude: number }>((res, reject) => {
- //If search manager is not defined, load the search module.
+ bingGeocode = (map: any, query: string) =>
+ new Promise<{ latitude: number; longitude: number }>((res, reject) => {
+ // If search manager is not defined, load the search module.
if (!this._bingSearchManager) {
- //Create an instance of the search manager and call the geocodeQuery function again.
+ // Create an instance of the search manager and call the geocodeQuery function again.
this.MicrosoftMaps.loadModule('Microsoft.Maps.Search', () => {
this._bingSearchManager = new this.MicrosoftMaps.Search.SearchManager(map.current);
res(this.bingGeocode(map, query));
@@ -319,11 +324,10 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
this._bingSearchManager.geocode({
where: query,
callback: action((r: any) => res(r.results[0].location)),
- errorCallback: (e: any) => reject(),
+ errorCallback: () => reject(),
});
}
});
- };
@observable
bingSearchBarContents: any = this.Document.map; // For Bing Maps: The contents of the Bing search bar (string)
@@ -368,7 +372,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp));
}
const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(temp.latitude, temp.longitude));
- this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(temp as Doc));
+ this.MicrosoftMaps.Events.addHandler(newpin, 'click', () => this.pushpinClicked(temp as Doc));
if (!this._unmounting) {
this._bingMap.current.entities.push(newpin);
}
@@ -383,7 +387,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
this.toggleSidebar();
options.didMove = true;
}
- return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ return new Promise<Opt<DocumentView>>(res => {
+ DocumentView.addViewRenderedCb(doc, dv => res(dv));
+ });
};
/*
* Pushpin onclick
@@ -418,7 +424,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
* Map OnClick
*/
@action
- mapOnClick = (e: { location: { latitude: any; longitude: any } }) => {
+ mapOnClick = (/* e: { location: { latitude: any; longitude: any } } */) => {
this._props.select(false);
this.deselectPin();
};
@@ -442,22 +448,23 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
* Updates maptype
*/
@action
- updateMapType = () => (this.dataDoc.map_type = this._bingMap.current.getMapTypeId());
+ updateMapType = () => {
+ this.dataDoc.map_type = this._bingMap.current.getMapTypeId();
+ };
/*
* For Bing Maps
* Called by search button's onClick
* Finds the geocode of the searched contents and sets location to that location
- **/
+ * */
@action
- bingSearch = () => {
- return this.bingGeocode(this._bingMap, this.bingSearchBarContents).then(location => {
+ bingSearch = () =>
+ this.bingGeocode(this._bingMap, this.bingSearchBarContents).then(location => {
this.dataDoc.latitude = location.latitude;
this.dataDoc.longitude = location.longitude;
this.dataDoc.map_zoom = this._bingMap.current.getZoom();
this.dataDoc.map = this.bingSearchBarContents;
});
- };
/*
* Returns doc w/ relevant info
@@ -479,7 +486,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
if (anchor) {
if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
addAsAnnotation && this.addDocument(anchor);
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.Document);
+ PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.Document);
return anchor;
}
return this.Document;
@@ -502,7 +509,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
this._bingMap.current.entities.push(pushPin);
- this.MicrosoftMaps.Events.addHandler(pushPin, 'click', (e: any) => this.pushpinClicked(pin));
+ this.MicrosoftMaps.Events.addHandler(pushPin, 'click', () => this.pushpinClicked(pin));
// this.MicrosoftMaps.Events.addHandler(pushPin, 'dblclick', (e: any) => this.pushpinDblClicked(pushPin, pin));
this.map_docToPinMap.set(pin, pushPin);
};
@@ -591,13 +598,15 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
};
@action
- searchbarOnEdit = (newText: string) => (this.bingSearchBarContents = newText);
+ searchbarOnEdit = (newText: string) => {
+ this.bingSearchBarContents = newText;
+ };
recolorPin = (pin: Doc, color?: string) => {
this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin));
this.map_docToPinMap.delete(pin);
const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(pin.latitude, pin.longitude), color ? { color } : {});
- this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(pin));
+ this.MicrosoftMaps.Events.addHandler(newpin, 'click', () => this.pushpinClicked(pin));
this._bingMap.current.entities.push(newpin);
this.map_docToPinMap.set(pin, newpin);
};
@@ -620,14 +629,16 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
this._disposers.mapLocation = reaction(
() => this.Document.map,
- mapLoc => (this.bingSearchBarContents = mapLoc),
+ mapLoc => {
+ this.bingSearchBarContents = mapLoc;
+ },
{ fireImmediately: true }
);
this._disposers.highlight = reaction(
() => this.allAnnotations.map(doc => doc[Highlight]),
() => {
const allConfigPins = this.allAnnotations.map(doc => ({ doc, pushpin: DocCast(doc.mapPin) })).filter(pair => pair.pushpin);
- allConfigPins.forEach(({ doc, pushpin }) => {
+ allConfigPins.forEach(({ pushpin }) => {
if (!pushpin[Highlight] && this.map_pinHighlighted.get(pushpin)) {
this.recolorPin(pushpin);
this.map_pinHighlighted.delete(pushpin);
@@ -668,23 +679,23 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
setupMoveUpEvents(
e,
e,
- e => {
+ moveEv => {
if (!dragClone) {
dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement;
dragClone.style.position = 'absolute';
dragClone.style.zIndex = '10000';
DragManager.Root().appendChild(dragClone);
}
- dragClone.style.transform = `translate(${e.clientX - 15}px, ${e.clientY - 15}px)`;
+ dragClone.style.transform = `translate(${moveEv.clientX - 15}px, ${moveEv.clientY - 15}px)`;
return false;
},
- e => {
+ upEv => {
if (!dragClone) return;
DragManager.Root().removeChild(dragClone);
- let target = document.elementFromPoint(e.x, e.y);
+ let target = document.elementFromPoint(upEv.x, upEv.y);
while (target) {
if (target === this._ref.current) {
- const cpt = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY);
+ const cpt = this.ScreenToLocalBoxXf().transformPoint(upEv.clientX, upEv.clientY);
const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2;
const y = cpt[1] - 32 /* height of search bar */ - this._props.PanelHeight() / 2;
const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
@@ -694,7 +705,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
target = target.parentElement;
}
},
- e => {
+ () => {
const createPin = () => this.createPushpin(this.Document.latitude, this.Document.longitude, this.Document.map);
if (this.bingSearchBarContents) {
this.bingSearch().then(createPin);
@@ -720,12 +731,12 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
MapBoxContainer._rerenderDelay = 0;
}
this._rerenderTimeout = undefined;
+ // eslint-disable-next-line operator-assignment
this.Document[DocCss] = this.Document[DocCss] + 1;
}), MapBoxContainer._rerenderDelay);
return null;
}
- const renderAnnotations = (childFilters?: () => string[]) => null;
return (
<div className="mapBox" ref={this._ref}>
<div
@@ -735,15 +746,11 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
e.button === 0 && !e.ctrlKey && e.stopPropagation();
}}
style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
- <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
- {renderAnnotations(this.opaqueFilter)}
- {SnappingManager.IsDragging ? null : renderAnnotations()}
-
<div className="mapBox-searchbar">
<EditableText
// editing
setVal={(newText: string | number) => typeof newText === 'string' && this.searchbarOnEdit(newText)}
- onEnter={e => this.bingSearch()}
+ onEnter={() => this.bingSearch()}
placeholder={this.bingSearchBarContents || 'enter city/zip/...'}
textAlign="center"
/>
@@ -752,18 +759,19 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="magnifying-glass" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" color="#DFDFDF">
<path
fill="currentColor"
- d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"></path>
+ d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"
+ />
</svg>
}
onClick={this.bingSearch}
type={Type.TERT}
/>
<div style={{ width: 30, height: 30 }} ref={this._dragRef} onPointerDown={this.dragToggle}>
- <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} />
+ <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size="lg" icon="bullseye" />} />
</div>
</div>
<MapProvider>
- <MapboxMap id="mabox-map" mapStyle={`mapbox://styles/mapbox/streets-v9`} mapboxAccessToken={mapboxApiKey} />
+ <MapboxMap id="mabox-map" mapStyle="mapbox://styles/mapbox/streets-v9" mapboxAccessToken={mapboxApiKey} />
</MapProvider>
{/*
@@ -780,9 +788,10 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
? null
: this.allAnnotations
.filter(anno => !anno.layout_unrendered)
- .map((pushpin, i) => (
+ .map(pushpin => (
<DocumentView
- key={i}
+ key={pushpin[Id]}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
renderDepth={this._props.renderDepth + 1}
Document={pushpin}
@@ -792,7 +801,6 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
NativeHeight={returnOne}
onKey={undefined}
onDoubleClickScript={undefined}
- onBrowseClickScript={undefined}
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
@@ -821,12 +829,13 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
<div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
<SidebarAnnos
ref={this._sidebarRef}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
fieldKey={this.fieldKey}
Document={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
- usePanelWidth={true}
+ usePanelWidth
showSidebar={this.SidebarShown}
nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
diff --git a/src/client/views/nodes/OpenWhere.ts b/src/client/views/nodes/OpenWhere.ts
new file mode 100644
index 000000000..e2a5f1f2a
--- /dev/null
+++ b/src/client/views/nodes/OpenWhere.ts
@@ -0,0 +1,25 @@
+export enum OpenWhereMod {
+ none = '',
+ left = 'left',
+ right = 'right',
+ top = 'top',
+ bottom = 'bottom',
+ keyvalue = 'keyValue',
+}
+export enum OpenWhere {
+ lightbox = 'lightbox',
+ add = 'add',
+ addLeft = 'add:left',
+ addRight = 'add:right',
+ addBottom = 'add:bottom',
+ close = 'close',
+ toggle = 'toggle',
+ toggleRight = 'toggle:right',
+ replace = 'replace',
+ replaceRight = 'replace:right',
+ replaceLeft = 'replace:left',
+ inParent = 'inParent',
+ inParentFromScreen = 'inParentFromScreen',
+ overlay = 'overlay',
+ addRightKeyvalue = 'add:right:keyValue',
+}
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 0f5e25a0c..7bca1230f 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -236,7 +236,7 @@
//pointer-events: none;
.pdfViewerDash-text {
.textLayer {
- display: none;
+ // display: none; // this makes search highlights not show up
span {
user-select: none;
}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 1274220b6..b8b9f63a9 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,41 +1,45 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/control-has-associated-label */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as Pdfjs from 'pdfjs-dist';
import 'pdfjs-dist/web/pdf_viewer.css';
import * as React from 'react';
+import { ClientUtils, returnFalse, setupMoveUpEvents, UpdateIcon } from '../../../ClientUtils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { ComputedField } from '../../../fields/ScriptField';
-import { Cast, FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, FieldValue, ImageCast, NumCast, StrCast, toList } from '../../../fields/Types';
import { ImageField, PdfField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
+import { emptyFunction } from '../../../Utils';
+import { Docs } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DocumentManager } from '../../util/DocumentManager';
+import { DocUtils } from '../../documents/DocUtils';
import { KeyCodes } from '../../util/KeyCodes';
-import { SelectionManager } from '../../util/SelectionManager';
import { undoBatch, UndoManager } from '../../util/UndoManager';
import { CollectionFreeFormView } from '../collections/collectionFreeForm';
import { CollectionStackingView } from '../collections/CollectionStackingView';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { Colors } from '../global/globalEnums';
-import { CreateImage } from '../nodes/WebBoxRenderer';
import { PDFViewer } from '../pdf/PDFViewer';
+import { PinDocView, PinProps } from '../PinFuncs';
import { SidebarAnnos } from '../SidebarAnnos';
-import { DocumentView, OpenWhere } from './DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
+import { DocumentView } from './DocumentView';
+import { FieldView, FieldViewProps } from './FieldView';
+import { FocusViewOptions } from './FocusViewOptions';
import { ImageBox } from './ImageBox';
+import { OpenWhere } from './OpenWhere';
import './PDFBox.scss';
-import { PinProps, PresBox } from './trails';
+import { CreateImage } from './WebBoxRenderer';
@observer
-export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
+export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(PDFBox, fieldKey);
}
@@ -66,8 +70,16 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const nh = Doc.NativeHeight(this.Document, this.dataDoc) || 1200;
!this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw));
if (this.pdfUrl) {
- if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => (this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href)));
- else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => (this._pdf = pdf)));
+ if (PDFBox.pdfcache.get(this.pdfUrl.url.href))
+ runInAction(() => {
+ this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href);
+ });
+ else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href))
+ PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(
+ action((pdf: any) => {
+ this._pdf = pdf;
+ })
+ );
}
}
@@ -85,7 +97,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
if (oldDiv instanceof HTMLCanvasElement) {
const canvas = oldDiv;
const img = document.createElement('img'); // create a Image Element
- img.src = canvas.toDataURL(); //image sourcez
+ img.src = canvas.toDataURL(); // image sourcez
img.style.width = canvas.style.width;
img.style.height = canvas.style.height;
const newCan = newDiv as HTMLCanvasElement;
@@ -96,8 +108,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
};
crop = (region: Doc | undefined, addCrop?: boolean) => {
- if (!region) return;
+ if (!region) return undefined;
const cropping = Doc.MakeCopy(region, true);
+ cropping.layout_unrendered = false; // text selection have this
+ cropping.text_inlineAnnotations = undefined; // text selections have this -- it causes them not to be rendered.
+ cropping.backgroundColor = undefined; // text selections have this -- it causes images to be fully transparent
+ cropping.opacity = undefined; // text selections have this -- it causes images to be fully transparent
const regionData = region[DocData];
regionData.lockedPosition = true;
regionData.title = 'region:' + this.Document.title;
@@ -111,11 +127,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
this.replaceCanvases(docViewContent, newDiv);
const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);
- const anchx = NumCast(cropping.x);
- const anchy = NumCast(cropping.y);
+ // const anchx = NumCast(cropping.x);
+ // const anchy = NumCast(cropping.y);
const anchw = NumCast(cropping._width) * (this._props.NativeDimScaling?.() || 1);
const anchh = NumCast(cropping._height) * (this._props.NativeDimScaling?.() || 1);
- const viewScale = 1;
+ // const viewScale = 1;
cropping.title = 'crop: ' + this.Document.title;
cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
cropping.y = NumCast(this.Document.y);
@@ -128,9 +144,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
croppingProto.proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
croppingProto.type = DocumentType.IMG;
croppingProto.layout = ImageBox.LayoutString('data');
- croppingProto.data = new ImageField(Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png'));
- croppingProto['data_nativeWidth'] = anchw;
- croppingProto['data_nativeHeight'] = anchh;
+ croppingProto.data = new ImageField(ClientUtils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png'));
+ croppingProto.data_nativeWidth = anchw;
+ croppingProto.data_nativeHeight = anchh;
if (addCrop) {
DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
}
@@ -146,8 +162,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
(NumCast(region.x) * this._props.PanelWidth()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']),
4
)
- .then((data_url: any) => {
- Utils.convertDataUri(data_url, region[Id]).then(returnedfilename =>
+ .then((dataUrl: any) => {
+ ClientUtils.convertDataUri(dataUrl, region[Id]).then(returnedfilename =>
setTimeout(
action(() => {
croppingProto.data = new ImageField(returnedfilename);
@@ -156,7 +172,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
)
);
})
- .catch(function (error: any) {
+ .catch((error: any) => {
console.error('oops, something went wrong!', error);
});
@@ -168,7 +184,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const docViewContent = this.DocumentView?.().ContentDiv!;
const filename = this.layoutDoc[Id] + '-icon' + new Date().getTime();
this._pdfViewer?._mainCont.current &&
- CollectionFreeFormView.UpdateIcon(
+ UpdateIcon(
filename,
docViewContent,
NumCast(this.layoutDoc._width),
@@ -182,8 +198,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
(iconFile: string, nativeWidth: number, nativeHeight: number) => {
setTimeout(() => {
this.dataDoc.icon = new ImageField(iconFile);
- this.dataDoc['icon_nativeWidth'] = nativeWidth;
- this.dataDoc['icon_nativeHeight'] = nativeHeight;
+ this.dataDoc.icon_nativeWidth = nativeWidth;
+ this.dataDoc.icon_nativeHeight = nativeHeight;
}, 500);
}
);
@@ -214,12 +230,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
);
}
- sidebarAddDocTab = (doc: Doc, where: OpenWhere) => {
- if (DocListCast(this.Document[this._props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) {
+ sidebarAddDocTab = (docIn: Doc | Doc[], where: OpenWhere) => {
+ const docs = toList(docIn);
+ if (docs.some(doc => DocListCast(this.Document[this._props.fieldKey + '_sidebar']).includes(doc)) && !this.SidebarShown) {
this.toggleSidebar(false);
return true;
}
- return this._props.addDocTab(doc, where);
+ return this._props.addDocTab(docs, where);
};
focus = (anchor: Doc, options: FocusViewOptions) => {
this._initialScrollTarget = anchor;
@@ -231,23 +248,25 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
options.didMove = true;
this.toggleSidebar(false);
}
- return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ return new Promise<Opt<DocumentView>>(res => {
+ DocumentView.addViewRenderedCb(doc, dv => res(dv));
+ });
};
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
- let ele: Opt<HTMLDivElement> = undefined;
+ let ele: Opt<HTMLDivElement>;
if (this._pdfViewer?.selectionContent()) {
ele = document.createElement('div');
ele.append(this._pdfViewer.selectionContent()!);
}
const docAnchor = () =>
Docs.Create.ConfigDocument({
- title: StrCast(this.Document.title + '@' + NumCast(this.layoutDoc._layout_scrollTop)?.toFixed(0)),
+ title: StrCast(this.Document.title + '@' + (NumCast(this.layoutDoc._layout_scrollTop) ?? 0).toFixed(0)),
annotationOn: this.Document,
});
const visibleAnchor = this._pdfViewer?._getAnchor?.(this._pdfViewer.savedAnnotations(), true);
const anchor = visibleAnchor ?? docAnchor();
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true, pannable: true } }, this.Document);
+ 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);
@@ -264,7 +283,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
!this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw));
};
- public search = action((searchString: string, bwd?: boolean, clear: boolean = false) => {
+ override search = action((searchString: string, bwd?: boolean, clear: boolean = false) => {
if (!this._searching && !clear) {
this._searching = true;
setTimeout(() => {
@@ -285,7 +304,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
this.Document._layout_curPage = Math.min(NumCast(this.dataDoc[this._props.fieldKey + '_numPages']), (NumCast(this.Document._layout_curPage) || 1) + 1);
return true;
};
- public gotoPage = (p: number) => (this.Document._layout_curPage = p);
+ public gotoPage = (p: number) => {
+ this.Document._layout_curPage = p;
+ };
@undoBatch
onKeyDown = action((e: KeyboardEvent) => {
@@ -297,6 +318,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
case 'PageUp':
processed = this.backPage();
break;
+ default:
}
if (processed) {
e.stopImmediatePropagation();
@@ -312,7 +334,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
this._initialScrollTarget = undefined;
}
};
- searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value);
+ searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._searchString = e.currentTarget.value;
+ };
// adding external documents; to sidebar key
// if (doc.Geolocation) this.addDocument(doc, this.fieldkey+"_annotation")
@@ -326,7 +350,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
setupMoveUpEvents(
this,
e,
- (e, down, delta) => {
+ (moveEv, down, delta) => {
const localDelta = this._props
.ScreenToLocalTransform()
.scale(this._props.NativeDimScaling?.() || 1)
@@ -341,7 +365,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
}
return false;
},
- (e, movement, isClick) => !isClick && batch.end(),
+ (clickEv, movement, isClick) => !isClick && batch.end(),
() => {
onButton && this.toggleSidebar();
batch.end();
@@ -368,11 +392,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
settingsPanel() {
const pageBtns = (
<>
- <button className="pdfBox-backBtn" key="back" title="Page Back" onPointerDown={e => e.stopPropagation()} onClick={this.backPage}>
- <FontAwesomeIcon style={{ color: 'white' }} icon={'arrow-left'} size="sm" />
+ <button type="button" className="pdfBox-backBtn" key="back" title="Page Back" onPointerDown={e => e.stopPropagation()} onClick={this.backPage}>
+ <FontAwesomeIcon style={{ color: 'white' }} icon="arrow-left" size="sm" />
</button>
- <button className="pdfBox-fwdBtn" key="fwd" title="Page Forward" onPointerDown={e => e.stopPropagation()} onClick={this.forwardPage}>
- <FontAwesomeIcon style={{ color: 'white' }} icon={'arrow-right'} size="sm" />
+ <button type="button" className="pdfBox-fwdBtn" key="fwd" title="Page Forward" onPointerDown={e => e.stopPropagation()} onClick={this.forwardPage}>
+ <FontAwesomeIcon style={{ color: 'white' }} icon="arrow-right" size="sm" />
</button>
</>
);
@@ -385,7 +409,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
onPointerDown={e => e.stopPropagation()}
style={{ display: this._props.isContentActive() ? 'flex' : 'none' }}>
<div className="pdfBox-overlayCont" onPointerDown={e => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
- <button className="pdfBox-overlayButton" title={searchTitle} />
+ <button type="button" className="pdfBox-overlayButton" title={searchTitle} />
<input
className="pdfBox-searchBar"
placeholder="Search"
@@ -396,17 +420,18 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey);
}}
/>
- <button className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
+ <button type="button" className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
<FontAwesomeIcon icon="search" size="sm" />
</button>
- <button className="pdfBox-prevIcon" title="Previous Annotation" onClick={this.prevAnnotation}>
- <FontAwesomeIcon icon={'arrow-up'} size="lg" />
+ <button type="button" className="pdfBox-prevIcon" title="Previous Annotation" onClick={this.prevAnnotation}>
+ <FontAwesomeIcon icon="arrow-up" size="lg" />
</button>
- <button className="pdfBox-nextIcon" title="Next Annotation" onClick={this.nextAnnotation}>
- <FontAwesomeIcon icon={'arrow-down'} size="lg" />
+ <button type="button" className="pdfBox-nextIcon" title="Next Annotation" onClick={this.nextAnnotation}>
+ <FontAwesomeIcon icon="arrow-down" size="lg" />
</button>
</div>
<button
+ type="button"
className="pdfBox-overlayButton"
title={searchTitle}
onClick={action(() => {
@@ -423,9 +448,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
<input
value={curPage}
style={{ width: `${curPage > 99 ? 4 : 3}ch`, pointerEvents: 'all' }}
- onChange={e => (this.Document._layout_curPage = Number(e.currentTarget.value))}
+ onChange={e => {
+ this.Document._layout_curPage = Number(e.currentTarget.value);
+ }}
onKeyDown={e => e.stopPropagation()}
- onClick={action(() => (this._pageControls = !this._pageControls))}
+ onClick={action(() => {
+ this._pageControls = !this._pageControls;
+ })}
/>
{this._pageControls ? pageBtns : null}
</div>
@@ -440,18 +469,20 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
return PDFBox.sidebarResizerWidth + nativeDiff * (this._props.NativeDimScaling?.() || 1);
};
@undoBatch
- toggleSidebarType = () => (this.dataDoc[this.SidebarKey + '_type_collection'] = this.dataDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform);
- specificContextMenu = (e: React.MouseEvent): void => {
+ toggleSidebarType = () => {
+ this.dataDoc[this.SidebarKey + '_type_collection'] = this.dataDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform;
+ };
+ specificContextMenu = (): void => {
const cm = ContextMenu.Instance;
const options = cm.findByDescription('Options...');
const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
!Doc.noviceMode && optionItems.push({ description: 'Toggle Sidebar Type', event: this.toggleSidebarType, icon: 'expand-arrows-alt' });
!Doc.noviceMode && optionItems.push({ description: 'update icon', event: () => this.pdfUrl && this.updateIcon(), icon: 'expand-arrows-alt' });
- //optionItems.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
+ // optionItems.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
!options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'asterisk' });
const help = cm.findByDescription('Help...');
const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
- helpItems.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' });
+ helpItems.push({ description: 'Copy path', event: () => this.pdfUrl && ClientUtils.CopyText(ClientUtils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' });
!help && ContextMenu.Instance.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'asterisk' });
};
@@ -469,7 +500,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
@observable _showSidebar = false;
@computed get SidebarShown() {
- return this._showSidebar || this.layoutDoc._show_sidebar ? true : false;
+ return !!(this._showSidebar || this.layoutDoc._show_sidebar);
}
@computed get sidebarHandle() {
return (
@@ -483,7 +514,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
}}
onPointerDown={e => this.sidebarBtnDown(e, true)}>
- <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
</div>
);
}
@@ -514,6 +545,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
return ComponentTag === CollectionStackingView ? (
<SidebarAnnos
ref={this._sidebarRef}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
Document={this.Document}
layoutDoc={this.layoutDoc}
@@ -527,8 +559,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
removeDocument={this.removeDocument}
/>
) : (
- <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}>
+ <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => DocumentView.SelectView(this.DocumentView?.()!, false), true)}>
<ComponentTag
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={emptyFunction} // override setContentView to do nothing
NativeWidth={this.sidebarNativeWidthFunc}
@@ -539,7 +572,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
yPadding={0}
viewField={this.SidebarKey}
isAnnotationOverlay={false}
- originTopLeft={true}
+ originTopLeft
isAnyChildContentActive={this.isAnyChildContentActive}
select={emptyFunction}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
@@ -548,7 +581,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
addDocument={this.sidebarAddDocument}
ScreenToLocalTransform={this.sidebarScreenToLocal}
renderDepth={this._props.renderDepth + 1}
- noSidebar={true}
+ noSidebar
fieldKey={this.SidebarKey}
/>
</div>
@@ -582,6 +615,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
top: 0,
}}>
<PDFViewer
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
pdfBox={this}
sidebarAddDoc={this.sidebarAddDocument}
@@ -614,12 +648,26 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const pdfView = !this._pdf ? null : this.renderPdfView;
const href = this.pdfUrl?.url.href;
if (!pdfView && href) {
- if (PDFBox.pdfcache.get(href)) setTimeout(action(() => (this._pdf = PDFBox.pdfcache.get(href))));
+ if (PDFBox.pdfcache.get(href))
+ setTimeout(
+ action(() => {
+ this._pdf = PDFBox.pdfcache.get(href);
+ })
+ );
else {
if (!PDFBox.pdfpromise.get(href)) PDFBox.pdfpromise.set(href, Pdfjs.getDocument(href).promise);
- PDFBox.pdfpromise.get(href)?.then(action((pdf: any) => PDFBox.pdfcache.set(href, (this._pdf = pdf))));
+ PDFBox.pdfpromise.get(href)?.then(
+ action((pdf: any) => {
+ PDFBox.pdfcache.set(href, (this._pdf = pdf));
+ })
+ );
}
}
return pdfView ?? this.renderTitleBox;
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.PDF, {
+ layout: { view: PDFBox, dataField: 'data' },
+ options: { acl: '', _layout_curPage: 1, _layout_fitWidth: true, _layout_nativeDimEditable: true, _layout_reflowVertical: true, systemIcon: 'BsFileEarmarkPdfFill' },
+});
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
index ae674d604..f88eb3bca 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
@@ -1,3 +1,11 @@
+/* eslint-disable camelcase */
+/* eslint-disable jsx-a11y/control-has-associated-label */
+/* eslint-disable @typescript-eslint/no-unused-vars */
+/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/no-array-index-key */
+/* eslint-disable react/jsx-props-no-spreading */
+/* eslint-disable no-return-assign */
import ArrowLeftIcon from '@mui/icons-material/ArrowLeft';
import ArrowRightIcon from '@mui/icons-material/ArrowRight';
import PauseIcon from '@mui/icons-material/Pause';
@@ -13,13 +21,15 @@ import { NumListCast } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { BoolCast, NumCast, StrCast } from '../../../../fields/Types';
import { ViewBoxAnnotatableComponent } from '../../DocComponent';
-import { FieldView, FieldViewProps } from './../FieldView';
+import { FieldView, FieldViewProps } from '../FieldView';
import './PhysicsSimulationBox.scss';
import InputField from './PhysicsSimulationInputField';
import questions from './PhysicsSimulationQuestions.json';
import tutorials from './PhysicsSimulationTutorial.json';
import Wall from './PhysicsSimulationWall';
import Weight from './PhysicsSimulationWeight';
+import { Docs } from '../../../documents/Documents';
+import { DocumentType } from '../../../documents/DocumentTypes';
interface IWallProps {
length: number;
@@ -204,7 +214,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
componentDidUpdate(prevProps: Readonly<FieldViewProps>) {
super.componentDidUpdate(prevProps);
- if (this.xMax !== this._props.PanelWidth() * 0.6 || this.yMax != this._props.PanelHeight()) {
+ 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();
this.setupSimulation();
@@ -219,16 +229,16 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
@action
setupSimulation = () => {
- const simulationType = this.simulationType;
+ const { simulationType } = this;
const mode = this.simulationMode;
this.dataDoc.simulation_paused = true;
- if (simulationType != 'Circular Motion') {
+ if (simulationType !== 'Circular Motion') {
this.dataDoc.mass1_velocityXstart = 0;
this.dataDoc.mass1_velocityYstart = 0;
this.dataDoc.mass1_velocityX = 0;
this.dataDoc.mass1_velocityY = 0;
}
- if (mode == 'Freeform') {
+ if (mode === 'Freeform') {
this.dataDoc.simulation_showForceMagnitudes = true;
// prettier-ignore
switch (simulationType) {
@@ -247,9 +257,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
case 'Circular Motion': this.setupCircular(20); break;
case 'Pulley': this.setupPulley(); break;
case 'Suspension': this.setupSuspension();break;
+ default:
}
this._simReset++;
- } else if (mode == 'Review') {
+ } else if (mode === 'Review') {
this.dataDoc.simulation_showComponentForces = false;
this.dataDoc.simulation_showForceMagnitudes = true;
this.dataDoc.simulation_showAcceleration = false;
@@ -265,12 +276,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
case 'Circular Motion': this.setupCircular(0); break; // TODO - circular motion review problems
case 'Pulley': this.setupPulley(); break; // TODO - pulley tutorial review problems
case 'Suspension': this.setupSuspension(); break; // TODO - suspension tutorial review problems
+ default:
}
- } else if (mode == 'Tutorial') {
+ } else if (mode === 'Tutorial') {
this.dataDoc.simulation_showComponentForces = false;
this.dataDoc.tutorial_stepNumber = 0;
this.dataDoc.simulation_showAcceleration = false;
- if (this.simulationType != 'Circular Motion') {
+ if (this.simulationType !== 'Circular Motion') {
this.dataDoc.mass1_velocityX = 0;
this.dataDoc.mass1_velocityY = 0;
this.dataDoc.simulation_showVelocity = false;
@@ -333,6 +345,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.suspension.steps[0].forces);
this.dataDoc.simulation_showForceMagnitudes = tutorials.suspension.steps[0].showMagnitude;
break;
+ default:
}
this._simReset++;
}
@@ -349,7 +362,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
magnitude: Math.abs(this.gravity) * Math.cos(Math.atan(height / width)) * this.mass1,
directionInDegrees: 180 - 90 - (Math.atan(height / width) * 180) / Math.PI,
};
- let frictionForce: IForce = {
+ const frictionForce: IForce = {
description: 'Static Friction Force',
magnitude: coefficient * Math.abs(this.gravity) * Math.cos(Math.atan(height / width)) * this.mass1,
directionInDegrees: 180 - (Math.atan(height / width) * 180) / Math.PI,
@@ -378,7 +391,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
directionInDegrees: 360 - (Math.atan(height / width) * 180) / Math.PI,
};
const gravityForce = this.gravityForce(this.mass1);
- if (coefficient != 0) {
+ if (coefficient !== 0) {
this.dataDoc.mass1_forcesStart = JSON.stringify([gravityForce, normalForce, frictionForce]);
this.dataDoc.mass1_forcesUpdated = JSON.stringify([gravityForce, normalForce, frictionForce]);
this.dataDoc.mass1_componentForces = JSON.stringify([frictionForce, normalForceComponent, gravityParallel, gravityPerpendicular]);
@@ -396,12 +409,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
this.dataDoc.wedge_height = Math.tan(radAng) * this.dataDoc.wedge_width;
// update weight position based on updated wedge width/height
- let yPos = this.yMax - this.dataDoc.wedge_height - this.mass1Radius * Math.cos(radAng) - this.mass1Radius;
- let xPos = this.xMax * 0.25 + this.mass1Radius * Math.sin(radAng) - this.mass1Radius;
+ const yPos = this.yMax - this.dataDoc.wedge_height - this.mass1Radius * Math.cos(radAng) - this.mass1Radius;
+ const xPos = this.xMax * 0.25 + this.mass1Radius * Math.sin(radAng) - this.mass1Radius;
this.dataDoc.mass1_positionXstart = xPos;
this.dataDoc.mass1_positionYstart = yPos;
- if (this.simulationMode == 'Freeform') {
+ if (this.simulationMode === 'Freeform') {
this.updateForcesWithFriction(NumCast(this.dataDoc.coefficientOfStaticFriction), this.dataDoc.wedge_width, Math.tan(radAng) * this.dataDoc.wedge_width);
}
};
@@ -409,7 +422,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
// In review mode, update forces when coefficient of static friction changed
updateReviewForcesBasedOnCoefficient = (coefficient: number) => {
let theta = this.wedgeAngle;
- let index = this.selectedQuestion.variablesForQuestionSetup.indexOf('theta - max 45');
+ const index = this.selectedQuestion.variablesForQuestionSetup.indexOf('theta - max 45');
if (index >= 0) {
theta = NumListCast(this.dataDoc.questionVariables)[index];
}
@@ -467,26 +480,26 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
const description = question.answerSolutionDescriptions[i];
if (!isNaN(NumCast(description))) {
solutions.push(NumCast(description));
- } else if (description == 'solve normal force angle from wedge angle') {
+ } else if (description === 'solve normal force angle from wedge angle') {
solutions.push(90 - theta);
- } else if (description == 'solve normal force magnitude from wedge angle') {
+ } else if (description === 'solve normal force magnitude from wedge angle') {
solutions.push(Math.abs(this.gravity) * Math.cos((theta / 180) * Math.PI));
- } else if (description == 'solve static force magnitude from wedge angle given equilibrium') {
- let normalForceMagnitude = Math.abs(this.gravity) * Math.cos((theta / 180) * Math.PI);
- let normalForceAngle = 90 - theta;
- let frictionForceAngle = 180 - theta;
- let frictionForceMagnitude = (-normalForceMagnitude * Math.sin((normalForceAngle * Math.PI) / 180) + Math.abs(this.gravity)) / Math.sin((frictionForceAngle * Math.PI) / 180);
+ } else if (description === 'solve static force magnitude from wedge angle given equilibrium') {
+ const normalForceMagnitude = Math.abs(this.gravity) * Math.cos((theta / 180) * Math.PI);
+ const normalForceAngle = 90 - theta;
+ const frictionForceAngle = 180 - theta;
+ const frictionForceMagnitude = (-normalForceMagnitude * Math.sin((normalForceAngle * Math.PI) / 180) + Math.abs(this.gravity)) / Math.sin((frictionForceAngle * Math.PI) / 180);
solutions.push(frictionForceMagnitude);
- } else if (description == 'solve static force angle from wedge angle given equilibrium') {
+ } else if (description === 'solve static force angle from wedge angle given equilibrium') {
solutions.push(180 - theta);
- } else if (description == 'solve minimum static coefficient from wedge angle given equilibrium') {
- let normalForceMagnitude = Math.abs(this.gravity) * Math.cos((theta / 180) * Math.PI);
- let normalForceAngle = 90 - theta;
- let frictionForceAngle = 180 - theta;
- let frictionForceMagnitude = (-normalForceMagnitude * Math.sin((normalForceAngle * Math.PI) / 180) + Math.abs(this.gravity)) / Math.sin((frictionForceAngle * Math.PI) / 180);
- let frictionCoefficient = frictionForceMagnitude / normalForceMagnitude;
+ } else if (description === 'solve minimum static coefficient from wedge angle given equilibrium') {
+ const normalForceMagnitude = Math.abs(this.gravity) * Math.cos((theta / 180) * Math.PI);
+ const normalForceAngle = 90 - theta;
+ const frictionForceAngle = 180 - theta;
+ const frictionForceMagnitude = (-normalForceMagnitude * Math.sin((normalForceAngle * Math.PI) / 180) + Math.abs(this.gravity)) / Math.sin((frictionForceAngle * Math.PI) / 180);
+ const frictionCoefficient = frictionForceMagnitude / normalForceMagnitude;
solutions.push(frictionCoefficient);
- } else if (description == 'solve maximum wedge angle from coefficient of static friction given equilibrium') {
+ } else if (description === 'solve maximum wedge angle from coefficient of static friction given equilibrium') {
solutions.push((Math.atan(muS) * 180) / Math.PI);
}
}
@@ -497,38 +510,38 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
// In review mode, check if input answers match correct answers and optionally generate alert
checkAnswers = (showAlert: boolean = true) => {
let error: boolean = false;
- let epsilon: number = 0.01;
+ const epsilon: number = 0.01;
if (this.selectedQuestion) {
for (let i = 0; i < this.selectedQuestion.answerParts.length; i++) {
- if (this.selectedQuestion.answerParts[i] == 'force of gravity') {
+ if (this.selectedQuestion.answerParts[i] === 'force of gravity') {
if (Math.abs(NumCast(this.dataDoc.review_GravityMagnitude) - this.selectedSolutions[i]) > epsilon) {
error = true;
}
- } else if (this.selectedQuestion.answerParts[i] == 'angle of gravity') {
+ } else if (this.selectedQuestion.answerParts[i] === 'angle of gravity') {
if (Math.abs(NumCast(this.dataDoc.review_GravityAngle) - this.selectedSolutions[i]) > epsilon) {
error = true;
}
- } else if (this.selectedQuestion.answerParts[i] == 'normal force') {
+ } else if (this.selectedQuestion.answerParts[i] === 'normal force') {
if (Math.abs(NumCast(this.dataDoc.review_NormalMagnitude) - this.selectedSolutions[i]) > epsilon) {
error = true;
}
- } else if (this.selectedQuestion.answerParts[i] == 'angle of normal force') {
+ } else if (this.selectedQuestion.answerParts[i] === 'angle of normal force') {
if (Math.abs(NumCast(this.dataDoc.review_NormalAngle) - this.selectedSolutions[i]) > epsilon) {
error = true;
}
- } else if (this.selectedQuestion.answerParts[i] == 'force of static friction') {
+ } else if (this.selectedQuestion.answerParts[i] === 'force of static friction') {
if (Math.abs(NumCast(this.dataDoc.review_StaticMagnitude) - this.selectedSolutions[i]) > epsilon) {
error = true;
}
- } else if (this.selectedQuestion.answerParts[i] == 'angle of static friction') {
+ } else if (this.selectedQuestion.answerParts[i] === 'angle of static friction') {
if (Math.abs(NumCast(this.dataDoc.review_StaticAngle) - this.selectedSolutions[i]) > epsilon) {
error = true;
}
- } else if (this.selectedQuestion.answerParts[i] == 'coefficient of static friction') {
+ } else if (this.selectedQuestion.answerParts[i] === 'coefficient of static friction') {
if (Math.abs(NumCast(this.dataDoc.coefficientOfStaticFriction) - this.selectedSolutions[i]) > epsilon) {
error = true;
}
- } else if (this.selectedQuestion.answerParts[i] == 'wedge angle') {
+ } else if (this.selectedQuestion.answerParts[i] === 'wedge angle') {
if (Math.abs(this.wedgeAngle - this.selectedSolutions[i]) > epsilon) {
error = true;
}
@@ -539,7 +552,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
this.dataDoc.simulation_paused = false;
setTimeout(() => (this.dataDoc.simulation_paused = true), 3000);
}
- if (this.selectedQuestion.goal == 'noMovement') {
+ if (this.selectedQuestion.goal === 'noMovement') {
this.dataDoc.noMovement = !error;
}
};
@@ -571,12 +584,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
let wedge_angle = 0;
for (let i = 0; i < question.variablesForQuestionSetup.length; i++) {
- if (question.variablesForQuestionSetup[i] == 'theta - max 45') {
- let randValue = Math.floor(Math.random() * 44 + 1);
+ if (question.variablesForQuestionSetup[i] === 'theta - max 45') {
+ const randValue = Math.floor(Math.random() * 44 + 1);
vars.push(randValue);
wedge_angle = randValue;
- } else if (question.variablesForQuestionSetup[i] == 'coefficient of static friction') {
- let randValue = Math.round(Math.random() * 1000) / 1000;
+ } else if (question.variablesForQuestionSetup[i] === 'coefficient of static friction') {
+ const randValue = Math.round(Math.random() * 1000) / 1000;
vars.push(randValue);
coefficient = randValue;
}
@@ -589,7 +602,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
let q = '';
for (let i = 0; i < question.questionSetup.length; i++) {
q += question.questionSetup[i];
- if (i != question.questionSetup.length - 1) {
+ if (i !== question.questionSetup.length - 1) {
q += vars[i];
if (question.variablesForQuestionSetup[i].includes('theta')) {
q += ' degree (≈' + Math.round((1000 * (vars[i] * Math.PI)) / 180) / 1000 + ' rad)';
@@ -601,7 +614,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
this.dataDoc.questionPartOne = q;
this.dataDoc.questionPartTwo = question.question;
this.dataDoc.answers = new List<number>(this.getAnswersToQuestion(question, vars));
- //this.dataDoc.simulation_reset = (!this.dataDoc.simulation_reset);
+ // this.dataDoc.simulation_reset = (!this.dataDoc.simulation_reset);
};
// Default setup for uniform circular motion simulation
@@ -610,8 +623,8 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
this.dataDoc.simulation_showComponentForces = false;
this.dataDoc.mass1_velocityYstart = 0;
this.dataDoc.mass1_velocityXstart = value;
- let xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius;
- let yPos = (this.yMax + this.yMin) / 2 + this.circularMotionRadius - this.mass1Radius;
+ const xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius;
+ const yPos = (this.yMax + this.yMin) / 2 + this.circularMotionRadius - this.mass1Radius;
this.dataDoc.mass1_positionYstart = yPos;
this.dataDoc.mass1_positionXstart = xPos;
const tensionForce: IForce = {
@@ -680,13 +693,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
// Default setup for suspension simulation
@action
setupSuspension = () => {
- let xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius;
- let yPos = this.yMin + 200;
+ const xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius;
+ const yPos = this.yMin + 200;
this.dataDoc.mass1_positionYstart = yPos;
this.dataDoc.mass1_positionXstart = xPos;
this.dataDoc.mass1_positionY = this.getDisplayYPos(yPos);
this.dataDoc.mass1_positionX = xPos;
- let tensionMag = (this.mass1 * Math.abs(this.gravity)) / (2 * Math.sin(Math.PI / 4));
+ const tensionMag = (this.mass1 * Math.abs(this.gravity)) / (2 * Math.sin(Math.PI / 4));
const tensionForce1: IForce = {
description: 'Tension',
magnitude: tensionMag,
@@ -891,7 +904,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
setVelocity={this.setVelocity1}
setAcceleration={this.setAcceleration1}
/>
- {this.simulationType == 'Pulley' && (
+ {this.simulationType === 'Pulley' && (
<Weight
{...commonWeightProps}
color="green"
@@ -916,7 +929,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
)}
</div>
<div style={{ position: 'absolute', transformOrigin: 'top left', top: 0, left: 0, width: '100%', height: '100%' }}>
- {(this.simulationType == 'One Weight' || this.simulationType == 'Inclined Plane') &&
+ {(this.simulationType === 'One Weight' || this.simulationType === 'Inclined Plane') &&
this.wallPositions?.map((element, index) => <Wall key={index} length={element.length} xPos={element.xPos} yPos={element.yPos} angleInDegrees={element.angleInDegrees} />)}
</div>
</div>
@@ -927,17 +940,17 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
style={{ overflow: 'auto', height: `${Math.max(1, 800 / this._props.PanelWidth()) * 100}%`, transform: `scale(${Math.min(1, this._props.PanelWidth() / 850)})` }}>
<div className="mechanicsSimulationControls">
<Stack direction="row" spacing={1}>
- {this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && (
+ {this.dataDoc.simulation_paused && this.simulationMode !== 'Tutorial' && (
<IconButton onClick={() => (this.dataDoc.simulation_paused = false)}>
<PlayArrowIcon />
</IconButton>
)}
- {!this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && (
+ {!this.dataDoc.simulation_paused && this.simulationMode !== 'Tutorial' && (
<IconButton onClick={() => (this.dataDoc.simulation_paused = true)}>
<PauseIcon />
</IconButton>
)}
- {this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && (
+ {this.dataDoc.simulation_paused && this.simulationMode !== 'Tutorial' && (
<IconButton onClick={action(() => this._simReset++)}>
<ReplayIcon />
</IconButton>
@@ -974,15 +987,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
</select>
</div>
</div>
- {this.simulationMode == 'Review' && this.simulationType != 'Inclined Plane' && (
+ {this.simulationMode === 'Review' && this.simulationType !== 'Inclined Plane' && (
<div className="wordProblemBox">
- <p>
- <>{this.simulationType} review problems in progress!</>
- </p>
+ <p>{this.simulationType} review problems in progress!</p>
<hr />
</div>
)}
- {this.simulationMode == 'Review' && this.simulationType == 'Inclined Plane' && (
+ {this.simulationMode === 'Review' && this.simulationType === 'Inclined Plane' && (
<div>
{!this.dataDoc.hintDialogueOpen && (
<IconButton
@@ -995,7 +1006,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
<QuestionMarkIcon />
</IconButton>
)}
- <Dialog maxWidth={'sm'} fullWidth={true} open={BoolCast(this.dataDoc.hintDialogueOpen)} onClose={() => (this.dataDoc.hintDialogueOpen = false)}>
+ <Dialog maxWidth="sm" fullWidth open={BoolCast(this.dataDoc.hintDialogueOpen)} onClose={() => (this.dataDoc.hintDialogueOpen = false)}>
<DialogTitle>Hints</DialogTitle>
<DialogContent>
{this.selectedQuestion.hints?.map((hint: any, index: number) => (
@@ -1030,12 +1041,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
dataDoc={this.dataDoc}
prop="review_GravityMagnitude"
step={0.1}
- unit={'N'}
+ unit="N"
upperBound={50}
value={NumCast(this.dataDoc.review_GravityMagnitude)}
showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('force of gravity')]}
- labelWidth={'7em'}
+ labelWidth="7em"
/>
)}
{this.selectedQuestion.answerParts.includes('angle of gravity') && (
@@ -1045,13 +1056,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
dataDoc={this.dataDoc}
prop="review_GravityAngle"
step={1}
- unit={'°'}
+ unit="°"
upperBound={360}
value={NumCast(this.dataDoc.review_GravityAngle)}
- radianEquivalent={true}
+ radianEquivalent
showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('angle of gravity')]}
- labelWidth={'7em'}
+ labelWidth="7em"
/>
)}
{this.selectedQuestion.answerParts.includes('normal force') && (
@@ -1061,12 +1072,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
dataDoc={this.dataDoc}
prop="review_NormalMagnitude"
step={0.1}
- unit={'N'}
+ unit="N"
upperBound={50}
value={NumCast(this.dataDoc.review_NormalMagnitude)}
showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('normal force')]}
- labelWidth={'7em'}
+ labelWidth="7em"
/>
)}
{this.selectedQuestion.answerParts.includes('angle of normal force') && (
@@ -1076,13 +1087,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
dataDoc={this.dataDoc}
prop="review_NormalAngle"
step={1}
- unit={'°'}
+ unit="°"
upperBound={360}
value={NumCast(this.dataDoc.review_NormalAngle)}
- radianEquivalent={true}
+ radianEquivalent
showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('angle of normal force')]}
- labelWidth={'7em'}
+ labelWidth="7em"
/>
)}
{this.selectedQuestion.answerParts.includes('force of static friction') && (
@@ -1092,12 +1103,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
dataDoc={this.dataDoc}
prop="review_StaticMagnitude"
step={0.1}
- unit={'N'}
+ unit="N"
upperBound={50}
value={NumCast(this.dataDoc.review_StaticMagnitude)}
showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('force of static friction')]}
- labelWidth={'7em'}
+ labelWidth="7em"
/>
)}
{this.selectedQuestion.answerParts.includes('angle of static friction') && (
@@ -1107,13 +1118,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
dataDoc={this.dataDoc}
prop="review_StaticAngle"
step={1}
- unit={'°'}
+ unit="°"
upperBound={360}
value={NumCast(this.dataDoc.review_StaticAngle)}
- radianEquivalent={true}
+ radianEquivalent
showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('angle of static friction')]}
- labelWidth={'7em'}
+ labelWidth="7em"
/>
)}
{this.selectedQuestion.answerParts.includes('coefficient of static friction') && (
@@ -1127,7 +1138,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
dataDoc={this.dataDoc}
prop="coefficientOfStaticFriction"
step={0.1}
- unit={''}
+ unit=""
upperBound={1}
value={NumCast(this.dataDoc.coefficientOfStaticFriction)}
effect={this.updateReviewForcesBasedOnCoefficient}
@@ -1142,14 +1153,14 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
dataDoc={this.dataDoc}
prop="wedge_angle"
step={1}
- unit={'°'}
+ unit="°"
upperBound={49}
value={this.wedgeAngle}
effect={(val: number) => {
this.changeWedgeBasedOnNewAngle(val);
this.updateReviewForcesBasedOnAngle(val);
}}
- radianEquivalent={true}
+ radianEquivalent
showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('wedge angle')]}
/>
@@ -1158,7 +1169,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
</div>
</div>
)}
- {this.simulationMode == 'Tutorial' && (
+ {this.simulationMode === 'Tutorial' && (
<div className="wordProblemBox">
<div className="question">
<h2>Problem</h2>
@@ -1180,7 +1191,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
this.dataDoc.mass1_forcesUpdated = JSON.stringify(this.tutorial.steps[step].forces);
this.dataDoc.simulation_showForceMagnitudes = this.tutorial.steps[step].showMagnitude;
}}
- disabled={this.dataDoc.tutorial_stepNumber == 0}>
+ disabled={this.dataDoc.tutorial_stepNumber === 0}>
<ArrowLeftIcon />
</IconButton>
<div>
@@ -1204,8 +1215,8 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
</IconButton>
</div>
<div>
- {(this.simulationType == 'One Weight' || this.simulationType == 'Inclined Plane' || this.simulationType == 'Pendulum') && <p>Resources</p>}
- {this.simulationType == 'One Weight' && (
+ {(this.simulationType === 'One Weight' || this.simulationType === 'Inclined Plane' || this.simulationType === 'Pendulum') && <p>Resources</p>}
+ {this.simulationType === 'One Weight' && (
<ul>
<li>
<a
@@ -1233,7 +1244,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
</li>
</ul>
)}
- {this.simulationType == 'Inclined Plane' && (
+ {this.simulationType === 'Inclined Plane' && (
<ul>
<li>
<a
@@ -1261,7 +1272,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
</li>
</ul>
)}
- {this.simulationType == 'Pendulum' && (
+ {this.simulationType === 'Pendulum' && (
<ul>
<li>
<a
@@ -1280,7 +1291,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
</div>
</div>
)}
- {this.simulationMode == 'Review' && this.simulationType == 'Inclined Plane' && (
+ {this.simulationMode === 'Review' && this.simulationType === 'Inclined Plane' && (
<div
style={{
display: 'flex',
@@ -1318,11 +1329,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
</div>
</div>
)}
- {this.simulationMode == 'Freeform' && (
+ {this.simulationMode === 'Freeform' && (
<div className="vars">
<FormControl component="fieldset">
<FormGroup>
- {this.simulationType == 'One Weight' && (
+ {this.simulationType === 'One Weight' && (
<FormControlLabel
control={<Checkbox checked={BoolCast(this.dataDoc.elasticCollisions)} onChange={() => (this.dataDoc.elasticCollisions = !this.dataDoc.elasticCollisions)} />}
label="Make collisions elastic"
@@ -1334,7 +1345,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
label="Show force vectors"
labelPlacement="start"
/>
- {(this.simulationType == 'Inclined Plane' || this.simulationType == 'Pendulum') && (
+ {(this.simulationType === 'Inclined Plane' || this.simulationType === 'Pendulum') && (
<FormControlLabel
control={<Checkbox checked={BoolCast(this.dataDoc.simulation_showComponentForces)} onChange={() => (this.dataDoc.simulation_showComponentForces = !this.dataDoc.simulation_showComponentForces)} />}
label="Show component force vectors"
@@ -1351,80 +1362,80 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
label="Show velocity vector"
labelPlacement="start"
/>
- <InputField label={<Box>Speed</Box>} lowerBound={1} dataDoc={this.dataDoc} prop="simulation_speed" step={1} unit={'x'} upperBound={10} value={NumCast(this.dataDoc.simulation_speed, 2)} labelWidth={'5em'} />
- {this.dataDoc.simulation_paused && this.simulationType != 'Circular Motion' && (
+ <InputField label={<Box>Speed</Box>} lowerBound={1} dataDoc={this.dataDoc} prop="simulation_speed" step={1} unit="x" upperBound={10} value={NumCast(this.dataDoc.simulation_speed, 2)} labelWidth="5em" />
+ {this.dataDoc.simulation_paused && this.simulationType !== 'Circular Motion' && (
<InputField
label={<Box>Gravity</Box>}
lowerBound={-30}
dataDoc={this.dataDoc}
prop="gravity"
step={0.01}
- unit={'m/s2'}
+ unit="m/s2"
upperBound={0}
value={NumCast(this.dataDoc.simulation_gravity, -9.81)}
effect={(val: number) => this.setupSimulation()}
- labelWidth={'5em'}
+ labelWidth="5em"
/>
)}
- {this.dataDoc.simulation_paused && this.simulationType != 'Pulley' && (
+ {this.dataDoc.simulation_paused && this.simulationType !== 'Pulley' && (
<InputField
label={<Box>Mass</Box>}
lowerBound={1}
dataDoc={this.dataDoc}
prop="mass1"
step={0.1}
- unit={'kg'}
+ unit="kg"
upperBound={5}
value={this.mass1 ?? 1}
effect={(val: number) => this.setupSimulation()}
- labelWidth={'5em'}
+ labelWidth="5em"
/>
)}
- {this.dataDoc.simulation_paused && this.simulationType == 'Pulley' && (
+ {this.dataDoc.simulation_paused && this.simulationType === 'Pulley' && (
<InputField
label={<Box>Red mass</Box>}
lowerBound={1}
dataDoc={this.dataDoc}
prop="mass1"
step={0.1}
- unit={'kg'}
+ unit="kg"
upperBound={5}
value={this.mass1 ?? 1}
effect={(val: number) => this.setupSimulation()}
- labelWidth={'5em'}
+ labelWidth="5em"
/>
)}
- {this.dataDoc.simulation_paused && this.simulationType == 'Pulley' && (
+ {this.dataDoc.simulation_paused && this.simulationType === 'Pulley' && (
<InputField
label={<Box>Blue mass</Box>}
lowerBound={1}
dataDoc={this.dataDoc}
prop="mass2"
step={0.1}
- unit={'kg'}
+ unit="kg"
upperBound={5}
value={this.mass2 ?? 1}
effect={(val: number) => this.setupSimulation()}
- labelWidth={'5em'}
+ labelWidth="5em"
/>
)}
- {this.dataDoc.simulation_paused && this.simulationType == 'Circular Motion' && (
+ {this.dataDoc.simulation_paused && this.simulationType === 'Circular Motion' && (
<InputField
label={<Box>Rod length</Box>}
lowerBound={100}
dataDoc={this.dataDoc}
prop="circularMotionRadius"
step={5}
- unit={'m'}
+ unit="m"
upperBound={250}
value={this.circularMotionRadius}
effect={(val: number) => this.setupSimulation()}
- labelWidth={'5em'}
+ labelWidth="5em"
/>
)}
</FormGroup>
</FormControl>
- {this.simulationType == 'Spring' && this.dataDoc.simulation_paused && (
+ {this.simulationType === 'Spring' && this.dataDoc.simulation_paused && (
<div>
<InputField
label={<Typography color="inherit">Spring stiffness</Typography>}
@@ -1432,13 +1443,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
dataDoc={this.dataDoc}
prop="spring_constant"
step={1}
- unit={'N/m'}
+ unit="N/m"
upperBound={500}
value={this.springConstant}
effect={action(() => this._simReset++)}
radianEquivalent={false}
- mode={'Freeform'}
- labelWidth={'7em'}
+ mode="Freeform"
+ labelWidth="7em"
/>
<InputField
label={<Typography color="inherit">Rest length</Typography>}
@@ -1452,7 +1463,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
effect={action(() => this._simReset++)}
radianEquivalent={false}
mode="Freeform"
- labelWidth={'7em'}
+ labelWidth="7em"
/>
<InputField
label={<Typography color="inherit">Starting displacement</Typography>}
@@ -1470,11 +1481,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
})}
radianEquivalent={false}
mode="Freeform"
- labelWidth={'7em'}
+ labelWidth="7em"
/>
</div>
)}
- {this.simulationType == 'Inclined Plane' && this.dataDoc.simulation_paused && (
+ {this.simulationType === 'Inclined Plane' && this.dataDoc.simulation_paused && (
<div>
<InputField
label={<Box>&theta;</Box>}
@@ -1482,16 +1493,16 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
dataDoc={this.dataDoc}
prop="wedge_angle"
step={1}
- unit={'°'}
+ unit="°"
upperBound={49}
value={this.wedgeAngle}
effect={action((val: number) => {
this.changeWedgeBasedOnNewAngle(val);
this._simReset++;
})}
- radianEquivalent={true}
- mode={'Freeform'}
- labelWidth={'2em'}
+ radianEquivalent
+ mode="Freeform"
+ labelWidth="2em"
/>
<InputField
label={
@@ -1503,7 +1514,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
dataDoc={this.dataDoc}
prop="coefficientOfStaticFriction"
step={0.1}
- unit={''}
+ unit=""
upperBound={1}
value={NumCast(this.dataDoc.coefficientOfStaticFriction) ?? 0}
effect={action((val: number) => {
@@ -1513,8 +1524,8 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
}
this._simReset++;
})}
- mode={'Freeform'}
- labelWidth={'2em'}
+ mode="Freeform"
+ labelWidth="2em"
/>
<InputField
label={
@@ -1526,16 +1537,16 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
dataDoc={this.dataDoc}
prop="coefficientOfKineticFriction"
step={0.1}
- unit={''}
+ unit=""
upperBound={NumCast(this.dataDoc.coefficientOfStaticFriction)}
value={NumCast(this.dataDoc.coefficientOfKineticFriction) ?? 0}
effect={action(() => this._simReset++)}
- mode={'Freeform'}
- labelWidth={'2em'}
+ mode="Freeform"
+ labelWidth="2em"
/>
</div>
)}
- {this.simulationType == 'Inclined Plane' && !this.dataDoc.simulation_paused && (
+ {this.simulationType === 'Inclined Plane' && !this.dataDoc.simulation_paused && (
<Typography>
<>
&theta;: {Math.round(this.wedgeAngle * 100) / 100}° ≈ {Math.round(((this.wedgeAngle * Math.PI) / 180) * 100) / 100} rad
@@ -1546,12 +1557,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
</>
</Typography>
)}
- {this.simulationType == 'Pendulum' && !this.dataDoc.simulation_paused && (
+ {this.simulationType === 'Pendulum' && !this.dataDoc.simulation_paused && (
<Typography>
&theta;: {Math.round(this.pendulumAngle * 100) / 100}° ≈ {Math.round(((this.pendulumAngle * Math.PI) / 180) * 100) / 100} rad
</Typography>
)}
- {this.simulationType == 'Pendulum' && this.dataDoc.simulation_paused && (
+ {this.simulationType === 'Pendulum' && this.dataDoc.simulation_paused && (
<div>
<InputField
label={<Box>Angle</Box>}
@@ -1559,13 +1570,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
dataDoc={this.dataDoc}
prop="pendulum_angle"
step={1}
- unit={'°'}
+ unit="°"
upperBound={59}
value={NumCast(this.dataDoc.pendulum_angle, 30)}
effect={action(value => {
this.dataDoc.pendulum_angleStart = value;
this.dataDoc.pendulum_lengthStart = this.dataDoc.pendulum_length;
- if (this.simulationType == 'Pendulum') {
+ if (this.simulationType === 'Pendulum') {
const mag = this.mass1 * Math.abs(this.gravity) * Math.cos((value * Math.PI) / 180);
const forceOfTension: IForce = {
@@ -1598,7 +1609,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
this._simReset++;
}
})}
- radianEquivalent={true}
+ radianEquivalent
mode="Freeform"
labelWidth="5em"
/>
@@ -1612,7 +1623,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
upperBound={400}
value={Math.round(this.pendulumLength)}
effect={action(value => {
- if (this.simulationType == 'Pendulum') {
+ if (this.simulationType === 'Pendulum') {
this.dataDoc.pendulum_angleStart = this.pendulumAngle;
this.dataDoc.pendulum_lengthStart = value;
this._simReset++;
@@ -1627,11 +1638,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
</div>
)}
<div className="mechanicsSimulationEquation">
- {this.simulationMode == 'Freeform' && (
+ {this.simulationMode === 'Freeform' && (
<table>
<tbody>
<tr>
- <td>{this.simulationType == 'Pulley' ? 'Red Weight' : ''}</td>
+ <td>{this.simulationType === 'Pulley' ? 'Red Weight' : ''}</td>
<td>X</td>
<td>Y</td>
</tr>
@@ -1646,36 +1657,34 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
>
<Box>Position</Box>
</td>
- {(!this.dataDoc.simulation_paused || this.simulationType == 'Inclined Plane' || this.simulationType == 'Circular Motion' || this.simulationType == 'Pulley') && (
- <td style={{ cursor: 'default' }}>
- <>{this.dataDoc.mass1_positionX} m</>
- </td>
+ {(!this.dataDoc.simulation_paused || this.simulationType === 'Inclined Plane' || this.simulationType === 'Circular Motion' || this.simulationType === 'Pulley') && (
+ <td style={{ cursor: 'default' }}>{this.dataDoc.mass1_positionX + ''} m</td>
)}{' '}
- {this.dataDoc.simulation_paused && this.simulationType != 'Inclined Plane' && this.simulationType != 'Circular Motion' && this.simulationType != 'Pulley' && (
+ {this.dataDoc.simulation_paused && this.simulationType !== 'Inclined Plane' && this.simulationType !== 'Circular Motion' && this.simulationType !== 'Pulley' && (
<td
style={{
cursor: 'default',
}}>
<InputField
- lowerBound={this.simulationType == 'Projectile' ? 1 : (this.xMax + this.xMin) / 4 - this.radius - 15}
+ lowerBound={this.simulationType === 'Projectile' ? 1 : (this.xMax + this.xMin) / 4 - this.radius - 15}
dataDoc={this.dataDoc}
prop="mass1_positionX"
step={1}
- unit={'m'}
- upperBound={this.simulationType == 'Projectile' ? this.xMax - 110 : (3 * (this.xMax + this.xMin)) / 4 - this.radius / 2 - 15}
+ unit="m"
+ upperBound={this.simulationType === 'Projectile' ? this.xMax - 110 : (3 * (this.xMax + this.xMin)) / 4 - this.radius / 2 - 15}
value={NumCast(this.dataDoc.mass1_positionX)}
effect={value => {
this.dataDoc.mass1_xChange = value;
- if (this.simulationType == 'Suspension') {
- let x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200;
- let x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius;
- let deltaX1 = value + this.radius - x1rod;
- let deltaX2 = x2rod - (value + this.radius);
- let deltaY = this.getYPosFromDisplay(NumCast(this.dataDoc.mass1_positionY)) + this.radius;
+ if (this.simulationType === 'Suspension') {
+ const x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200;
+ const x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius;
+ const deltaX1 = value + this.radius - x1rod;
+ const deltaX2 = x2rod - (value + this.radius);
+ const deltaY = this.getYPosFromDisplay(NumCast(this.dataDoc.mass1_positionY)) + this.radius;
let dir1T = Math.PI - Math.atan(deltaY / deltaX1);
let dir2T = Math.atan(deltaY / deltaX2);
- let tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T));
- let tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T);
+ const tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T));
+ const tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T);
dir1T = (dir1T * 180) / Math.PI;
dir2T = (dir2T * 180) / Math.PI;
const tensionForce1: IForce = {
@@ -1692,15 +1701,15 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]);
}
}}
- small={true}
+ small
mode="Freeform"
/>
</td>
)}{' '}
- {(!this.dataDoc.simulation_paused || this.simulationType == 'Inclined Plane' || this.simulationType == 'Circular Motion' || this.simulationType == 'Pulley') && (
+ {(!this.dataDoc.simulation_paused || this.simulationType === 'Inclined Plane' || this.simulationType === 'Circular Motion' || this.simulationType === 'Pulley') && (
<td style={{ cursor: 'default' }}>{`${NumCast(this.dataDoc.mass1_positionY)} m`}</td>
)}{' '}
- {this.dataDoc.simulation_paused && this.simulationType != 'Inclined Plane' && this.simulationType != 'Circular Motion' && this.simulationType != 'Pulley' && (
+ {this.dataDoc.simulation_paused && this.simulationType !== 'Inclined Plane' && this.simulationType !== 'Circular Motion' && this.simulationType !== 'Pulley' && (
<td
style={{
cursor: 'default',
@@ -1715,16 +1724,16 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
value={NumCast(this.dataDoc.mass1_positionY)}
effect={value => {
this.dataDoc.mass1_yChange = value;
- if (this.simulationType == 'Suspension') {
- let x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200;
- let x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius;
- let deltaX1 = NumCast(this.dataDoc.mass1_positionX) + this.radius - x1rod;
- let deltaX2 = x2rod - (NumCast(this.dataDoc.mass1_positionX) + this.radius);
- let deltaY = this.getYPosFromDisplay(value) + this.radius;
+ if (this.simulationType === 'Suspension') {
+ const x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200;
+ const x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius;
+ const deltaX1 = NumCast(this.dataDoc.mass1_positionX) + this.radius - x1rod;
+ const deltaX2 = x2rod - (NumCast(this.dataDoc.mass1_positionX) + this.radius);
+ const deltaY = this.getYPosFromDisplay(value) + this.radius;
let dir1T = Math.PI - Math.atan(deltaY / deltaX1);
let dir2T = Math.atan(deltaY / deltaX2);
- let tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T));
- let tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T);
+ const tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T));
+ const tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T);
dir1T = (dir1T * 180) / Math.PI;
dir2T = (dir2T * 180) / Math.PI;
const tensionForce1: IForce = {
@@ -1741,7 +1750,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]);
}
}}
- small={true}
+ small
mode="Freeform"
/>
</td>
@@ -1758,10 +1767,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
>
<Box>Velocity</Box>
</td>
- {(!this.dataDoc.simulation_paused || (this.simulationType != 'One Weight' && this.simulationType != 'Circular Motion')) && (
+ {(!this.dataDoc.simulation_paused || (this.simulationType !== 'One Weight' && this.simulationType !== 'Circular Motion')) && (
<td style={{ cursor: 'default' }}>{`${NumCast(this.dataDoc.mass1_velocityX)} m/s`}</td>
)}{' '}
- {this.dataDoc.simulation_paused && (this.simulationType == 'One Weight' || this.simulationType == 'Circular Motion') && (
+ {this.dataDoc.simulation_paused && (this.simulationType === 'One Weight' || this.simulationType === 'Circular Motion') && (
<td
style={{
cursor: 'default',
@@ -1771,24 +1780,20 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
dataDoc={this.dataDoc}
prop="mass1_velocityX"
step={1}
- unit={'m/s'}
+ unit="m/s"
upperBound={50}
value={NumCast(this.dataDoc.mass1_velocityX)}
effect={action(value => {
this.dataDoc.mass1_velocityXstart = value;
this._simReset++;
})}
- small={true}
+ small
mode="Freeform"
/>
</td>
)}{' '}
- {(!this.dataDoc.simulation_paused || this.simulationType != 'One Weight') && (
- <td style={{ cursor: 'default' }}>
- <>{this.dataDoc.mass1_velocityY} m/s</>
- </td>
- )}{' '}
- {this.dataDoc.simulation_paused && this.simulationType == 'One Weight' && (
+ {(!this.dataDoc.simulation_paused || this.simulationType !== 'One Weight') && <td style={{ cursor: 'default' }}>{this.dataDoc.mass1_velocityY + ''} m/s</td>}{' '}
+ {this.dataDoc.simulation_paused && this.simulationType === 'One Weight' && (
<td
style={{
cursor: 'default',
@@ -1804,7 +1809,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
effect={value => {
this.dataDoc.mass1_velocityYstart = -value;
}}
- small={true}
+ small
mode="Freeform"
/>
</td>
@@ -1822,14 +1827,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
<Box>Acceleration</Box>
</td>
<td style={{ cursor: 'default' }}>
- <>
- {this.dataDoc.mass1_accelerationX} m/s<sup>2</sup>
- </>
+ {this.dataDoc.mass1_accelerationX + ''} m/s<sup>2</sup>
</td>
<td style={{ cursor: 'default' }}>
- <>
- {this.dataDoc.mass1_accelerationY} m/s<sup>2</sup>
- </>
+ {this.dataDoc.mass1_accelerationY + ''} m/s<sup>2</sup>
</td>
</tr>
<tr>
@@ -1842,7 +1843,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
</tbody>
</table>
)}
- {this.simulationMode == 'Freeform' && this.simulationType == 'Pulley' && (
+ {this.simulationMode === 'Freeform' && this.simulationType === 'Pulley' && (
<table>
<tbody>
<tr>
@@ -1869,14 +1870,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
<Box>Acceleration</Box>
</td>
<td style={{ cursor: 'default' }}>
- <>
- {this.dataDoc.mass2_accelerationX} m/s<sup>2</sup>
- </>
+ {this.dataDoc.mass2_accelerationX + ''} m/s<sup>2</sup>
</td>
<td style={{ cursor: 'default' }}>
- <>
- {this.dataDoc.mass2_accelerationY} m/s<sup>2</sup>
- </>
+ {this.dataDoc.mass2_accelerationY + ''} m/s<sup>2</sup>
</td>
</tr>
<tr>
@@ -1890,7 +1887,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
</table>
)}
</div>
- {this.simulationType != 'Pendulum' && this.simulationType != 'Spring' && (
+ {this.simulationType !== 'Pendulum' && this.simulationType !== 'Spring' && (
<div>
<p>Kinematic Equations</p>
<ul>
@@ -1907,7 +1904,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
</ul>
</div>
)}
- {this.simulationType == 'Spring' && (
+ {this.simulationType === 'Spring' && (
<div>
<p>Harmonic Motion Equations: Spring</p>
<ul>
@@ -1936,7 +1933,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
</ul>
</div>
)}
- {this.simulationType == 'Pendulum' && (
+ {this.simulationType === 'Pendulum' && (
<div>
<p>Harmonic Motion Equations: Pendulum</p>
<ul>
@@ -1959,11 +1956,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
<svg width={100 + 'px'} height={100 + 'px'}>
<defs>
<marker id="miniArrow" markerWidth="20" markerHeight="20" refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
- <path d="M0,0 L0,6 L9,3 z" fill={'#000000'} />
+ <path d="M0,0 L0,6 L9,3 z" fill="#000000" />
</marker>
</defs>
- <line x1={20} y1={70} x2={70} y2={70} stroke={'#000000'} strokeWidth="2" markerEnd="url(#miniArrow)" />
- <line x1={20} y1={70} x2={20} y2={20} stroke={'#000000'} strokeWidth="2" markerEnd="url(#miniArrow)" />
+ <line x1={20} y1={70} x2={70} y2={70} stroke="#000000" strokeWidth="2" markerEnd="url(#miniArrow)" />
+ <line x1={20} y1={70} x2={20} y2={20} stroke="#000000" strokeWidth="2" markerEnd="url(#miniArrow)" />
</svg>
<p
style={{
@@ -1971,7 +1968,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
top: this.yMax - 120 + 40 + 'px',
left: this.xMin + 90 - 80 + 'px',
}}>
- {this.simulationType == 'Circular Motion' ? 'Z' : 'Y'}
+ {this.simulationType === 'Circular Motion' ? 'Z' : 'Y'}
</p>
<p
style={{
@@ -1986,3 +1983,9 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
);
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.SIMULATION, {
+ data: '',
+ layout: { view: PhysicsSimulationBox, dataField: 'data' },
+ options: { acl: '', _width: 1000, _height: 800, mass1: '', mass2: '', layout_nativeDimEditable: true, position: '', acceleration: '', pendulum: '', spring: '', wedge: '', simulation: '', review: '', systemIcon: 'BsShareFill' },
+});
diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx
index 16450c359..48da4937a 100644
--- a/src/client/views/nodes/RadialMenu.tsx
+++ b/src/client/views/nodes/RadialMenu.tsx
@@ -6,22 +6,57 @@ import { RadialMenuItem, RadialMenuProps } from './RadialMenuItem';
@observer
export class RadialMenu extends React.Component {
+ // eslint-disable-next-line no-use-before-define
static Instance: RadialMenu;
static readonly buffer = 20;
+ @observable private _mouseX: number = -1;
+ @observable private _mouseY: number = -1;
+ @observable private _shouldDisplay: boolean = false;
+ @observable private _mouseDown: boolean = false;
+ @observable private _closest: number = -1;
+ @observable private _pageX: number = 0;
+ @observable private _pageY: number = 0;
+ @observable _display: boolean = false;
+ @observable private _yRelativeToTop: boolean = true;
+ @observable private _items: Array<RadialMenuProps> = [];
+ private _reactionDisposer?: IReactionDisposer;
+
constructor(props: any) {
super(props);
makeObservable(this);
RadialMenu.Instance = this;
}
- @observable private _mouseX: number = -1;
- @observable private _mouseY: number = -1;
- @observable private _shouldDisplay: boolean = false;
- @observable private _mouseDown: boolean = false;
- private _reactionDisposer?: IReactionDisposer;
+ componentDidMount() {
+ document.addEventListener('pointerdown', this.onPointerDown);
+ document.addEventListener('pointerup', this.onPointerUp);
+ this.previewcircle();
+ this._reactionDisposer = reaction(
+ () => this._shouldDisplay,
+ () =>
+ this._shouldDisplay &&
+ !this._mouseDown &&
+ runInAction(() => {
+ this._display = true;
+ })
+ );
+ }
- public used: boolean = false;
+ componentDidUpdate() {
+ this.previewcircle();
+ }
+ componentWillUnmount() {
+ document.removeEventListener('pointerdown', this.onPointerDown);
+
+ document.removeEventListener('pointerup', this.onPointerUp);
+ this._reactionDisposer && this._reactionDisposer();
+ }
+
+ @computed get menuItems() {
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ return this._items.map((item, index) => <RadialMenuItem {...item} key={item.description} closeMenu={this.closeMenu} max={this._items.length} min={index} selected={this._closest} />);
+ }
catchTouch = (te: React.TouchEvent) => {
te.stopPropagation();
@@ -33,13 +68,9 @@ export class RadialMenu extends React.Component {
this._mouseDown = true;
this._mouseX = e.clientX;
this._mouseY = e.clientY;
- this.used = false;
document.addEventListener('pointermove', this.onPointerMove);
};
- @observable
- private _closest: number = -1;
-
@action
onPointerMove = (e: PointerEvent) => {
const curX = e.clientX;
@@ -65,7 +96,6 @@ export class RadialMenu extends React.Component {
};
@action
onPointerUp = (e: PointerEvent) => {
- this.used = true;
this._mouseDown = false;
const curX = e.clientX;
const curY = e.clientY;
@@ -78,86 +108,6 @@ export class RadialMenu extends React.Component {
this._items[this._closest].event();
}
};
- componentWillUnmount() {
- document.removeEventListener('pointerdown', this.onPointerDown);
-
- document.removeEventListener('pointerup', this.onPointerUp);
- this._reactionDisposer && this._reactionDisposer();
- }
-
- @action
- componentDidMount() {
- document.addEventListener('pointerdown', this.onPointerDown);
- document.addEventListener('pointerup', this.onPointerUp);
- this.previewcircle();
- this._reactionDisposer = reaction(
- () => this._shouldDisplay,
- () => this._shouldDisplay && !this._mouseDown && runInAction(() => (this._display = true))
- );
- }
-
- componentDidUpdate = () => {
- this.previewcircle();
- };
-
- @observable private _pageX: number = 0;
- @observable private _pageY: number = 0;
- @observable _display: boolean = false;
- @observable private _yRelativeToTop: boolean = true;
-
- @observable private _width: number = 0;
- @observable private _height: number = 0;
-
- getItems() {
- return this._items;
- }
-
- @action
- addItem(item: RadialMenuProps) {
- if (this._items.indexOf(item) === -1) {
- this._items.push(item);
- }
- }
-
- @observable
- private _items: Array<RadialMenuProps> = [];
-
- @action
- displayMenu = (x: number, y: number) => {
- //maxX and maxY will change if the UI/font size changes, but will work for any amount
- //of items added to the menu
- this._mouseX = x;
- this._mouseY = y;
- this._shouldDisplay = true;
- };
- // @computed
- // get pageX() {
- // const x = this._pageX;
- // if (x < 0) {
- // return 0;
- // }
- // const width = this._width;
- // if (x + width > window.innerWidth - RadialMenu.buffer) {
- // return window.innerWidth - RadialMenu.buffer - width;
- // }
- // return x;
- // }
- // @computed
- // get pageY() {
- // const y = this._pageY;
- // if (y < 0) {
- // return 0;
- // }
- // const height = this._height;
- // if (y + height > window.innerHeight - RadialMenu.buffer) {
- // return window.innerHeight - RadialMenu.buffer - height;
- // }
- // return y;
- // }
-
- @computed get menuItems() {
- return this._items.map((item, index) => <RadialMenuItem {...item} key={item.description} closeMenu={this.closeMenu} max={this._items.length} min={index} selected={this._closest} />);
- }
@action
closeMenu = () => {
@@ -167,14 +117,6 @@ export class RadialMenu extends React.Component {
};
@action
- openMenu = (x: number, y: number) => {
- this._pageX = x;
- this._pageY = y;
- this._shouldDisplay;
- this._display = true;
- };
-
- @action
clearItems() {
this._items = [];
}
diff --git a/src/client/views/nodes/RadialMenuItem.tsx b/src/client/views/nodes/RadialMenuItem.tsx
index 91dc37d34..6f10e7b65 100644
--- a/src/client/views/nodes/RadialMenuItem.tsx
+++ b/src/client/views/nodes/RadialMenuItem.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { observer } from 'mobx-react';
@@ -53,6 +54,7 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> {
case 2:
color = 'lightgray';
break;
+ default:
}
if (circlemax % 3 === 1 && circlemin === circlemax - 1) {
color = '#c2c2c5';
@@ -80,7 +82,6 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> {
const avg = (circlemin / circlemax + (circlemin + 1) / circlemax) / 2;
const degrees = 360 * avg;
const x = 100 * Math.cos((degrees * Math.PI) / 180);
- const y = -125 * Math.sin((degrees * Math.PI) / 180);
return x;
}
@@ -91,7 +92,6 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> {
this.props.max ? (circlemax = this.props.max) : null;
const avg = (circlemin / circlemax + (circlemin + 1) / circlemax) / 2;
const degrees = 360 * avg;
- const x = 125 * Math.cos((degrees * Math.PI) / 180);
const y = -100 * Math.sin((degrees * Math.PI) / 180);
return y;
}
diff --git a/src/client/views/nodes/RecordingBox/ProgressBar.tsx b/src/client/views/nodes/RecordingBox/ProgressBar.tsx
index 1bb2b7c84..62798bc2f 100644
--- a/src/client/views/nodes/RecordingBox/ProgressBar.tsx
+++ b/src/client/views/nodes/RecordingBox/ProgressBar.tsx
@@ -1,31 +1,33 @@
+/* eslint-disable react/no-array-index-key */
+/* eslint-disable react/require-default-props */
import * as React from 'react';
-import { useEffect, useState, useCallback, useRef } from "react"
-import "./ProgressBar.scss"
+import { useEffect, useState, useRef } from 'react';
+import './ProgressBar.scss';
import { MediaSegment } from './RecordingView';
interface ProgressBarProps {
- videos: MediaSegment[],
- setVideos: React.Dispatch<React.SetStateAction<MediaSegment[]>>,
- orderVideos: boolean,
- progress: number,
- recording: boolean,
- doUndo: boolean,
- setCanUndo?: React.Dispatch<React.SetStateAction<boolean>>,
+ videos: MediaSegment[];
+ setVideos: React.Dispatch<React.SetStateAction<MediaSegment[]>>;
+ orderVideos: boolean;
+ progress: number;
+ recording: boolean;
+ doUndo: boolean;
+ setCanUndo?: React.Dispatch<React.SetStateAction<boolean>>;
}
interface SegmentBox {
- endTime: number,
- startTime: number,
- order: number,
+ endTime: number;
+ startTime: number;
+ order: number;
}
interface CurrentHover {
- index: number,
- minX: number,
- maxX: number
+ index: number;
+ minX: number;
+ maxX: number;
}
export function ProgressBar(props: ProgressBarProps) {
- const progressBarRef = useRef<HTMLDivElement | null>(null)
+ const progressBarRef = useRef<HTMLDivElement | null>(null);
// the actual list of JSX elements rendered as segments
const [segments, setSegments] = useState<JSX.Element[]>([]);
@@ -47,8 +49,6 @@ export function ProgressBar(props: ProgressBarProps) {
// update the canUndo props based on undo stack
useEffect(() => props.setCanUndo?.(undoStack.length > 0), [undoStack.length]);
- // useEffect for undo - brings back the most recently deleted segment
- useEffect(() => handleUndo(), [props.doUndo])
const handleUndo = () => {
// get the last element from the undo if it exists
if (undoStack.length === 0) return;
@@ -59,27 +59,36 @@ export function ProgressBar(props: ProgressBarProps) {
// update the removed time and place element back into ordered
setTotalRemovedTime(prevRemoved => prevRemoved - (last.endTime - last.startTime));
setOrdered(prevOrdered => [...prevOrdered, last]);
- }
+ };
+ // useEffect for undo - brings back the most recently deleted segment
+ useEffect(() => handleUndo(), [props.doUndo]);
// useEffect for recording changes - changes style to disabled and adds the "expanding-segment"
useEffect(() => {
// get segments segment's html using it's id -> make them appeared disabled (or enabled)
- segments.forEach((seg) => document.getElementById(seg.props.id)?.classList.toggle('segment-disabled', props.recording));
+ segments.forEach(seg => document.getElementById(seg.props.id)?.classList.toggle('segment-disabled', props.recording));
progressBarRef.current?.classList.toggle('progressbar-disabled', props.recording);
if (props.recording)
- setSegments(prevSegments => [...prevSegments, <div key='segment-expanding' id='segment-expanding' className='segment segment-expanding blink' style={{ width: 'fit-content' }}>{props.videos.length + 1}</div>]);
- }, [props.recording])
-
+ setSegments(prevSegments => [
+ ...prevSegments,
+ <div key="segment-expanding" id="segment-expanding" className="segment segment-expanding blink" style={{ width: 'fit-content' }}>
+ {props.videos.length + 1}
+ </div>,
+ ]);
+ }, [props.recording]);
// useEffect that updates the segmentsJSX, which is rendered
// only updated when ordered is updated or if the user is dragging around a segment
useEffect(() => {
const totalTime = props.progress * 1000 - totalRemovedTime;
- const segmentsJSX = ordered.map((seg, i) =>
- <div key={`segment-${i}`} id={`segment-${i}`} className={dragged === i ? 'segment-hide' : 'segment'} style={{ width: `${((seg.endTime - seg.startTime) / totalTime) * 100}%` }}>{seg.order + 1}</div>);
+ const segmentsJSX = ordered.map((seg, i) => (
+ <div key={`segment-${i}`} id={`segment-${i}`} className={dragged === i ? 'segment-hide' : 'segment'} style={{ width: `${((seg.endTime - seg.startTime) / totalTime) * 100}%` }}>
+ {seg.order + 1}
+ </div>
+ ));
- setSegments(segmentsJSX)
+ setSegments(segmentsJSX);
}, [dragged, ordered]);
// useEffect for dragged - update the cursor to be grabbing while grabbing
@@ -89,14 +98,14 @@ export function ProgressBar(props: ProgressBarProps) {
// to imporve performance, only want to update the CSS width, not re-render the whole JSXList
useEffect(() => {
- if (!props.recording) return
+ if (!props.recording) return;
const totalTime = props.progress * 1000 - totalRemovedTime;
let remainingTime = totalTime;
segments.forEach((seg, i) => {
// for the last segment, we need to set that directly
if (i === segments.length - 1) return;
// update remaining time
- remainingTime -= (ordered[i].endTime - ordered[i].startTime);
+ remainingTime -= ordered[i].endTime - ordered[i].startTime;
// update the width for this segment
const htmlId = seg.props.id;
@@ -106,8 +115,7 @@ export function ProgressBar(props: ProgressBarProps) {
// update the width of the expanding segment using the remaining time
const segExapandHtml = document.getElementById('segment-expanding');
- if (segExapandHtml)
- segExapandHtml.style.width = ordered.length === 0 ? '100%' : `${(remainingTime / totalTime) * 100}%`;
+ if (segExapandHtml) segExapandHtml.style.width = ordered.length === 0 ? '100%' : `${(remainingTime / totalTime) * 100}%`;
}, [props.progress]);
// useEffect for props.videos - update the ordered array when a new video is added
@@ -120,9 +128,7 @@ export function ProgressBar(props: ProgressBarProps) {
// in this case, a new video is added -> push it onto ordered
if (order >= ordered.length) {
const { endTime, startTime } = props.videos.lastElement();
- setOrdered(prevOrdered => {
- return [...prevOrdered, { endTime, startTime, order }];
- });
+ setOrdered(prevOrdered => [...prevOrdered, { endTime, startTime, order }]);
}
// in this case, a video is removed
@@ -132,7 +138,7 @@ export function ProgressBar(props: ProgressBarProps) {
}, [props.videos]);
// useEffect for props.orderVideos - matched the order array with the videos array before the export
- useEffect(() => props.setVideos(vids => ordered.map((seg) => vids[seg.order])), [props.orderVideos]);
+ useEffect(() => props.setVideos(vids => ordered.map(seg => vids[seg.order])), [props.orderVideos]);
// useEffect for removed - handles logic for removing a segment
useEffect(() => {
@@ -151,36 +157,68 @@ export function ProgressBar(props: ProgressBarProps) {
// returns the new currentHover based on the new index
const updateCurrentHover = (segId: number): CurrentHover | null => {
// get the segId of the segment that will become the new bounding area
- const rect = progressBarRef.current?.children[segId].getBoundingClientRect()
- if (rect == null) return null
+ const rect = progressBarRef.current?.children[segId].getBoundingClientRect();
+ if (rect == null) return null;
return {
index: segId,
minX: rect.x,
maxX: rect.x + rect.width,
- }
- }
+ };
+ };
+
+ const swapSegments = (oldIndex: number, newIndex: number) => {
+ if (newIndex == null) return;
+ setOrdered(prevOrdered => {
+ const temp = { ...prevOrdered[oldIndex] };
+ prevOrdered[oldIndex] = prevOrdered[newIndex];
+ prevOrdered[newIndex] = temp;
+ return prevOrdered;
+ });
+ // update visually where the segment is hovering over
+ setDragged(newIndex);
+ };
+
+ // functions for the floating segment that tracks the cursor while grabbing it
+ const initDetachSegment = (dot: HTMLDivElement, rect: DOMRect) => {
+ dot.classList.add('segment-selected');
+ dot.style.transitionDuration = '0s';
+ dot.style.position = 'absolute';
+ dot.style.zIndex = '999';
+ dot.style.width = `${rect.width}px`;
+ dot.style.height = `${rect.height}px`;
+ dot.style.left = `${rect.x}px`;
+ dot.style.top = `${rect.y}px`;
+ dot.draggable = false;
+ document.body.append(dot);
+ };
+ const followCursor = (event: PointerEvent, dot: HTMLDivElement): void => {
+ // event.stopPropagation()
+ const { width, height } = dot.getBoundingClientRect();
+ dot.style.left = `${event.clientX - width / 2}px`;
+ dot.style.top = `${event.clientY - height / 2}px`;
+ };
// pointerdown event for the progress bar
- const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
- // don't move the videobox element
- e.stopPropagation();
+ const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
+ // don't move the videobox element
+ e.stopPropagation();
// if recording, do nothing
- if (props.recording) return;
+ if (props.recording) return;
// get the segment the user clicked on to be dragged
- const clickedSegment = e.target as HTMLDivElement & EventTarget
+ const clickedSegment = e.target as HTMLDivElement & EventTarget;
// get the profess bar ro add event listeners
// don't do anything if null
- const progressBar = progressBarRef.current
- if (progressBar == null || clickedSegment.id === progressBar.id) return
+ const progressBar = progressBarRef.current;
+ if (progressBar == null || clickedSegment.id === progressBar.id) return;
// if holding shift key, let's remove that segment
if (e.shiftKey) {
const segId = parseInt(clickedSegment.id.split('-')[1]);
setRemoved(segId);
- return
+ return;
}
// if holding ctrl key and click, let's undo that segment #hiddenfeature lol
@@ -192,26 +230,26 @@ export function ProgressBar(props: ProgressBarProps) {
// if we're here, the user is dragging a segment around
// let the progress bar capture all the pointer events until the user releases (pointerUp)
const ptrId = e.pointerId;
- progressBar.setPointerCapture(ptrId)
+ progressBar.setPointerCapture(ptrId);
- const rect = clickedSegment.getBoundingClientRect()
- // id for segment is like 'segment-1' or 'segment-10',
+ const rect = clickedSegment.getBoundingClientRect();
+ // id for segment is like 'segment-1' or 'segment-10',
// so this works to get the id
- const segId = parseInt(clickedSegment.id.split('-')[1])
+ const segId = parseInt(clickedSegment.id.split('-')[1]);
// set the selected segment to be the one dragged
- setDragged(segId)
+ setDragged(segId);
- // this is the logic for storing the lower X bound and upper X bound to know
+ // this is the logic for storing the lower X bound and upper X bound to know
// whether a swap is needed between two segments
let currentHover: CurrentHover = {
index: segId,
minX: rect.x,
maxX: rect.x + rect.width,
- }
+ };
// create the floating segment that tracks the cursor
- const detchedSegment = document.createElement("div")
- initDeatchSegment(detchedSegment, rect);
+ const detchedSegment = document.createElement('div');
+ initDetachSegment(detchedSegment, rect);
const updateSegmentOrder = (event: PointerEvent): void => {
event.stopPropagation();
@@ -219,6 +257,7 @@ export function ProgressBar(props: ProgressBarProps) {
// this fixes a bug where pointerup doesn't fire while cursor is upped while being dragged
if (!progressBar.hasPointerCapture(ptrId)) {
+ // eslint-disable-next-line no-use-before-define
placeSegmentandCleanup();
return;
}
@@ -228,24 +267,23 @@ export function ProgressBar(props: ProgressBarProps) {
const curX = event.clientX;
// handle the left bound
if (curX < currentHover.minX && currentHover.index > 0) {
- swapSegments(currentHover.index, currentHover.index - 1)
- currentHover = updateCurrentHover(currentHover.index - 1) ?? currentHover
+ swapSegments(currentHover.index, currentHover.index - 1);
+ currentHover = updateCurrentHover(currentHover.index - 1) ?? currentHover;
}
// handle the right bound
else if (curX > currentHover.maxX && currentHover.index < segments.length - 1) {
- swapSegments(currentHover.index, currentHover.index + 1)
- currentHover = updateCurrentHover(currentHover.index + 1) ?? currentHover
+ swapSegments(currentHover.index, currentHover.index + 1);
+ currentHover = updateCurrentHover(currentHover.index + 1) ?? currentHover;
}
- }
+ };
// handles when the user is done dragging the segment (pointerUp)
const placeSegmentandCleanup = (event?: PointerEvent): void => {
event?.stopPropagation();
event?.preventDefault();
// if they put the segment outside of the bounds, remove it
- if (event && (event.clientX < 0 || event.clientX > document.body.clientWidth || event.clientY < 0 || event.clientY > document.body.clientHeight))
- setRemoved(currentHover.index);
-
+ if (event && (event.clientX < 0 || event.clientX > document.body.clientWidth || event.clientY < 0 || event.clientY > document.body.clientHeight)) setRemoved(currentHover.index);
+
// remove the update event listener for pointermove
progressBar.removeEventListener('pointermove', updateSegmentOrder);
// remove the floating segment from the DOM
@@ -253,49 +291,16 @@ export function ProgressBar(props: ProgressBarProps) {
// dragged is -1 is equiv to nothing being dragged, so the normal state
// so this will place the segment in it's location and update the segment bar
setDragged(-1);
- }
+ };
// event listeners that allow the user to drag and release the floating segment
progressBar.addEventListener('pointermove', updateSegmentOrder);
progressBar.addEventListener('pointerup', placeSegmentandCleanup, { once: true });
- }
-
- const swapSegments = (oldIndex: number, newIndex: number) => {
- if (newIndex == null) return;
- setOrdered(prevOrdered => {
- const temp = { ...prevOrdered[oldIndex] }
- prevOrdered[oldIndex] = prevOrdered[newIndex]
- prevOrdered[newIndex] = temp
- return prevOrdered
- });
- // update visually where the segment is hovering over
- setDragged(newIndex);
- }
-
- // functions for the floating segment that tracks the cursor while grabbing it
- const initDeatchSegment = (dot: HTMLDivElement, rect: DOMRect) => {
- dot.classList.add("segment-selected");
- dot.style.transitionDuration = '0s';
- dot.style.position = 'absolute';
- dot.style.zIndex = '999';
- dot.style.width = `${rect.width}px`;
- dot.style.height = `${rect.height}px`;
- dot.style.left = `${rect.x}px`;
- dot.style.top = `${rect.y}px`;
- dot.draggable = false;
- document.body.append(dot);
- }
- const followCursor = (event: PointerEvent, dot: HTMLDivElement): void => {
- // event.stopPropagation()
- const { width, height } = dot.getBoundingClientRect();
- dot.style.left = `${event.clientX - width / 2}px`;
- dot.style.top = `${event.clientY - height / 2}px`;
- }
-
+ };
return (
<div className="progressbar" id="progressbar" onPointerDown={onPointerDown} ref={progressBarRef}>
{segments}
</div>
- )
-} \ No newline at end of file
+ );
+}
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 1f976f926..e46e40bfe 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -3,6 +3,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { DateField } from '../../../../fields/DateField';
import { Doc, DocListCast } from '../../../../fields/Doc';
+import { DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
import { BoolCast, DocCast } from '../../../../fields/Types';
@@ -10,18 +11,18 @@ import { VideoField } from '../../../../fields/URLField';
import { Upload } from '../../../../server/SharedMediaTypes';
import { DocumentType } from '../../../documents/DocumentTypes';
import { Docs } from '../../../documents/Documents';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
import { Presentation } from '../../../util/TrackMovements';
import { undoBatch } from '../../../util/UndoManager';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
-import { media_state } from '../AudioBox';
+import { mediaState } from '../AudioBox';
+import { DocumentView } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
import { VideoBox } from '../VideoBox';
import { RecordingView } from './RecordingView';
-import { DocData } from '../../../../fields/DocSymbols';
@observer
export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -43,16 +44,11 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
@observable result: Upload.AccessPathInfo | undefined = undefined;
- @observable videoDuration: number | undefined = undefined;
-
- @action
- setVideoDuration = (duration: number) => (this.videoDuration = duration);
@action
setResult = (info: Upload.AccessPathInfo, presentation?: Presentation) => {
this.result = info;
this.dataDoc.type = DocumentType.VID;
- this.dataDoc[this.fieldKey + '_duration'] = this.videoDuration;
this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey);
this.dataDoc[this._props.fieldKey] = new VideoField(this.result.accessPaths.client);
@@ -68,17 +64,17 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static WorkspaceStopRecording() {
const remDoc = RecordingBox.screengrabber?.Document;
if (remDoc) {
- //if recordingbox is true; when we press the stop button. changed vals temporarily to see if changes happening
+ // if recordingbox is true; when we press the stop button. changed vals temporarily to see if changes happening
RecordingBox.screengrabber?.Pause?.();
setTimeout(() => {
RecordingBox.screengrabber?.Finish?.();
- remDoc.overlayX = 70; //was 100
+ remDoc.overlayX = 70; // was 100
remDoc.overlayY = 590;
RecordingBox.screengrabber = undefined;
}, 100);
- //could break if recording takes too long to turn into videobox. If so, either increase time on setTimeout below or find diff place to do this
+ // could break if recording takes too long to turn into videobox. If so, either increase time on setTimeout below or find diff place to do this
setTimeout(() => Doc.RemFromMyOverlay(remDoc), 1000);
- Doc.UserDoc().workspaceRecordingState = media_state.Paused;
+ Doc.UserDoc().workspaceRecordingState = mediaState.Paused;
Doc.AddDocToList(Doc.UserDoc(), 'workspaceRecordings', remDoc);
}
}
@@ -102,15 +98,15 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
_width: 205,
_height: 115,
});
- screengrabber.overlayX = 70; //was -400
- screengrabber.overlayY = 590; //was 0
+ screengrabber.overlayX = 70; // was -400
+ screengrabber.overlayY = 590; // was 0
screengrabber[DocData][Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
- Doc.AddToMyOverlay(screengrabber); //just adds doc to overlay
- DocumentManager.Instance.AddViewRenderedCb(screengrabber, docView => {
+ Doc.AddToMyOverlay(screengrabber); // just adds doc to overlay
+ DocumentView.addViewRenderedCb(screengrabber, docView => {
RecordingBox.screengrabber = docView.ComponentView as RecordingBox;
RecordingBox.screengrabber.Record?.();
});
- Doc.UserDoc().workspaceRecordingState = media_state.Recording;
+ Doc.UserDoc().workspaceRecordingState = mediaState.Recording;
}
/**
@@ -123,7 +119,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
value.overlayX = 70;
value.overlayY = window.innerHeight - 180;
Doc.AddToMyOverlay(value);
- DocumentManager.Instance.AddViewRenderedCb(value, docView => {
+ DocumentView.addViewRenderedCb(value, docView => {
Doc.UserDoc().currentRecording = docView.Document;
docView.select(false);
RecordingBox.resumeWorkspaceReplaying(value);
@@ -136,7 +132,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
*/
@undoBatch
public static addRecToWorkspace(value: RecordingBox) {
- let ffView = Array.from(DocumentManager.Instance.DocumentViews).find(view => view.ComponentView instanceof CollectionFreeFormView);
+ const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
(ffView?.ComponentView as CollectionFreeFormView)._props.addDocument?.(value.Document);
Doc.RemoveDocFromList(Doc.UserDoc(), 'workspaceRecordings', value.Document);
Doc.RemFromMyOverlay(value.Document);
@@ -146,20 +142,20 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
public static resumeWorkspaceReplaying(doc: Doc) {
- const docView = DocumentManager.Instance.getDocumentView(doc);
+ const docView = DocumentView.getDocumentView(doc);
if (docView?.ComponentView instanceof VideoBox) {
docView.ComponentView.Play();
}
- Doc.UserDoc().workspaceReplayingState = media_state.Playing;
+ Doc.UserDoc().workspaceReplayingState = mediaState.Playing;
}
public static pauseWorkspaceReplaying(doc: Doc) {
- const docView = DocumentManager.Instance.getDocumentView(doc);
+ const docView = DocumentView.getDocumentView(doc);
const videoBox = docView?.ComponentView as VideoBox;
if (videoBox) {
videoBox.Pause();
}
- Doc.UserDoc().workspaceReplayingState = media_state.Paused;
+ Doc.UserDoc().workspaceReplayingState = mediaState.Paused;
}
public static stopWorkspaceReplaying(value: Doc) {
@@ -191,66 +187,78 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
render() {
return (
<div className="recordingBox" style={{ width: '100%' }} ref={this._ref}>
- {!this.result && (
- <RecordingView
- forceTrackScreen={BoolCast(this.layoutDoc[this.fieldKey + '_trackScreen'])}
- getControls={this.getControls}
- setResult={this.setResult}
- setDuration={this.setVideoDuration}
- id={DocCast(this.Document.proto)?.[Id] || ''}
- />
- )}
+ {!this.result && <RecordingView forceTrackScreen={BoolCast(this.layoutDoc[this.fieldKey + '_trackScreen'])} getControls={this.getControls} setResult={this.setResult} id={DocCast(this.Document.proto)?.[Id] || ''} />}
</div>
);
}
+ // eslint-disable-next-line no-use-before-define
static screengrabber: RecordingBox | undefined;
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function stopWorkspaceRecording() {
RecordingBox.WorkspaceStopRecording();
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function stopWorkspaceReplaying(value: Doc) {
RecordingBox.stopWorkspaceReplaying(value);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function removeWorkspaceReplaying(value: Doc) {
RecordingBox.removeWorkspaceReplaying(value);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function getCurrentRecording() {
return Doc.UserDoc().currentRecording;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function getWorkspaceRecordings() {
return new List<any>(['Record Workspace', `Record Webcam`, ...DocListCast(Doc.UserDoc().workspaceRecordings)]);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function isWorkspaceRecording() {
- return Doc.UserDoc().workspaceRecordingState === media_state.Recording;
+ return Doc.UserDoc().workspaceRecordingState === mediaState.Recording;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function isWorkspaceReplaying() {
return Doc.UserDoc().workspaceReplayingState;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function replayWorkspace(value: Doc | string, _readOnly_: boolean) {
if (_readOnly_) return DocCast(Doc.UserDoc().currentRecording) ?? 'Record Workspace';
if (typeof value === 'string') RecordingBox.WorkspaceStartRecording(value);
else RecordingBox.replayWorkspace(value);
+ return undefined;
});
-ScriptingGlobals.add(function pauseWorkspaceReplaying(value: Doc, _readOnly_: boolean) {
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function pauseWorkspaceReplaying(value: Doc) {
RecordingBox.pauseWorkspaceReplaying(value);
});
-ScriptingGlobals.add(function resumeWorkspaceReplaying(value: Doc, _readOnly_: boolean) {
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function resumeWorkspaceReplaying(value: Doc) {
RecordingBox.resumeWorkspaceReplaying(value);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function startRecordingDrag(value: { doc: Doc | string; e: React.PointerEvent }) {
if (DocCast(value.doc)) {
DragManager.StartDocumentDrag([value.e.target as HTMLElement], new DragManager.DocumentDragData([DocCast(value.doc)], dropActionType.embed), value.e.clientX, value.e.clientY);
value.e.preventDefault();
return true;
}
+ return undefined;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function renderDropdown() {
if (!Doc.UserDoc().workspaceRecordings || DocListCast(Doc.UserDoc().workspaceRecordings).length === 0) {
return true;
}
return false;
});
+
+Docs.Prototypes.TemplateMap.set(DocumentType.WEBCAM, {
+ layout: { view: RecordingBox, dataField: 'data' },
+ options: { acl: '', systemIcon: 'BsFillCameraVideoFill' },
+});
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx
index f7ed82643..b8451fe60 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx
@@ -1,10 +1,13 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable react/button-has-type */
+/* eslint-disable jsx-a11y/control-has-associated-label */
import * as React from 'react';
import { useEffect, useRef, useState } from 'react';
import { IconContext } from 'react-icons';
import { FaCheckCircle } from 'react-icons/fa';
import { MdBackspace } from 'react-icons/md';
import { Upload } from '../../../../server/SharedMediaTypes';
-import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
+import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
import { Networking } from '../../../Network';
import { Presentation, TrackMovements } from '../../../util/TrackMovements';
import { ProgressBar } from './ProgressBar';
@@ -19,19 +22,18 @@ export interface MediaSegment {
interface IRecordingViewProps {
setResult: (info: Upload.AccessPathInfo, presentation?: Presentation) => void;
- setDuration: (seconds: number) => void;
id: string;
getControls: (record: () => void, pause: () => void, finish: () => void) => void;
forceTrackScreen: boolean;
}
const MAXTIME = 100000;
+const iconVals = { color: '#cc1c08', className: 'video-edit-buttons' };
export function RecordingView(props: IRecordingViewProps) {
const [recording, setRecording] = useState(false);
const recordingTimerRef = useRef<number>(0);
const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second
- const [playing, setPlaying] = useState(false);
const [progress, setProgress] = useState(0);
// acts as a "refresh state" to tell progressBar when to undo
@@ -62,7 +64,7 @@ export function RecordingView(props: IRecordingViewProps) {
useEffect(() => {
if (finished) {
// make the total presentation that'll match the concatted video
- let concatPres = (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.concatPresentations(videos.map(v => v.presentation as Presentation));
+ const concatPres = (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.concatPresentations(videos.map(v => v.presentation as Presentation));
// this async function uses the server to create the concatted video and then sets the result to it's accessPaths
(async () => {
@@ -100,16 +102,16 @@ export function RecordingView(props: IRecordingViewProps) {
return () => clearInterval(interval);
}, [recording]);
+ const setVideoProgressHelper = (curProgrss: number) => {
+ const newProgress = (curProgrss / MAXTIME) * 100;
+ setProgress(newProgress);
+ };
+
useEffect(() => {
setVideoProgressHelper(recordingTimer);
recordingTimerRef.current = recordingTimer;
}, [recordingTimer]);
- const setVideoProgressHelper = (progress: number) => {
- const newProgress = (progress / MAXTIME) * 100;
- setProgress(newProgress);
- };
-
const startShowingStream = async (mediaConstraints = DEFAULT_MEDIA_CONSTRAINTS) => {
const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
@@ -131,7 +133,7 @@ export function RecordingView(props: IRecordingViewProps) {
if (event.data.size > 0) videoChunks.push(event.data);
};
- videoRecorder.current.onstart = (event: any) => {
+ videoRecorder.current.onstart = () => {
setRecording(true);
// start the recording api when the video recorder starts
(trackScreen || props.forceTrackScreen) && TrackMovements.Instance.start();
@@ -149,7 +151,7 @@ export function RecordingView(props: IRecordingViewProps) {
// depending on if a presenation exists, add it to the video
const presentation = TrackMovements.Instance.yieldPresentation();
- setVideos(videos => [...videos, presentation != null && (trackScreen || props.forceTrackScreen) ? { ...nextVideo, presentation } : nextVideo]);
+ setVideos(theVideos => [...theVideos, presentation != null && (trackScreen || props.forceTrackScreen) ? { ...nextVideo, presentation } : nextVideo]);
}
// reset the temporary chunks
@@ -186,7 +188,7 @@ export function RecordingView(props: IRecordingViewProps) {
e,
returnTrue,
returnFalse,
- e => {
+ () => {
// start recording if not already recording
if (!videoRecorder.current || videoRecorder.current.state === 'inactive') record();
@@ -202,14 +204,8 @@ export function RecordingView(props: IRecordingViewProps) {
setDoUndo(prev => !prev);
};
- const handleOnTimeUpdate = () => {
- playing && setVideoProgressHelper(videoElementRef.current!.currentTime);
- };
-
const millisecondToMinuteSecond = (milliseconds: number) => {
- const toTwoDigit = (digit: number) => {
- return String(digit).length == 1 ? '0' + digit : digit;
- };
+ const toTwoDigit = (digit: number) => (String(digit).length === 1 ? '0' + digit : digit);
const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000);
return toTwoDigit(minutes) + ' : ' + toTwoDigit(seconds);
@@ -219,10 +215,11 @@ export function RecordingView(props: IRecordingViewProps) {
props.getControls(record, pause, finish);
}, []);
+ const iconUndoVals = React.useMemo(() => ({ color: 'grey', className: 'video-edit-buttons', style: { display: canUndo ? 'inherit' : 'none' } }), []);
return (
<div className="recording-container">
<div className="video-wrapper">
- <video id={`video-${props.id}`} autoPlay muted onTimeUpdate={() => handleOnTimeUpdate()} ref={videoElementRef} />
+ <video id={`video-${props.id}`} autoPlay muted ref={videoElementRef} />
<div className="recording-sign">
<span className="dot" />
<p className="timer">{millisecondToMinuteSecond(recordingTimer * 10)}</p>
@@ -246,10 +243,10 @@ export function RecordingView(props: IRecordingViewProps) {
{!recording &&
(videos.length > 0 ? (
<div className="options-wrapper video-edit-wrapper">
- <IconContext.Provider value={{ color: 'grey', className: 'video-edit-buttons', style: { display: canUndo ? 'inherit' : 'none' } }}>
+ <IconContext.Provider value={iconUndoVals}>
<MdBackspace onPointerDown={undoPrevious} />
</IconContext.Provider>
- <IconContext.Provider value={{ color: '#cc1c08', className: 'video-edit-buttons' }}>
+ <IconContext.Provider value={iconVals}>
<FaCheckCircle
onPointerDown={e => {
e.stopPropagation();
@@ -268,7 +265,7 @@ export function RecordingView(props: IRecordingViewProps) {
setTrackScreen(e.target.checked);
}}
/>
- <span className="checkmark"></span>
+ <span className="checkmark" />
Track Screen
</label>
</div>
diff --git a/src/client/views/nodes/RecordingBox/index.ts b/src/client/views/nodes/RecordingBox/index.ts
index ff21eaed6..e4f9b5e55 100644
--- a/src/client/views/nodes/RecordingBox/index.ts
+++ b/src/client/views/nodes/RecordingBox/index.ts
@@ -1,2 +1,2 @@
-export * from './RecordingView'
-export * from './RecordingBox' \ No newline at end of file
+export * from './RecordingView';
+export * from './RecordingBox';
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 1e3933ac3..3be50f5e6 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -1,33 +1,37 @@
+/* eslint-disable jsx-a11y/media-has-caption */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import * as React from 'react';
// import { Canvas } from '@react-three/fiber';
import { computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
// import { BufferAttribute, Camera, Vector2, Vector3 } from 'three';
+import { returnFalse, returnOne, returnZero } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
import { DateField } from '../../../fields/DateField';
import { Doc } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { ComputedField } from '../../../fields/ScriptField';
import { Cast, DocCast, NumCast } from '../../../fields/Types';
import { AudioField, VideoField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils';
-import { DocUtils } from '../../documents/Documents';
-import { DocumentType } from '../../documents/DocumentTypes';
import { Networking } from '../../Network';
+import { DocUtils } from '../../documents/DocUtils';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents';
import { CaptureManager } from '../../util/CaptureManager';
import { SettingsManager } from '../../util/SettingsManager';
import { TrackMovements } from '../../util/TrackMovements';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
-import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
import { ContextMenu } from '../ContextMenu';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
-import { media_state } from './AudioBox';
+import { DocViewUtils } from '../DocViewUtils';
+import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { mediaState } from './AudioBox';
import { FieldView, FieldViewProps } from './FieldView';
-import { FormattedTextBox } from './formattedText/FormattedTextBox';
import './ScreenshotBox.scss';
import { VideoBox } from './VideoBox';
-import { DocData } from '../../../fields/DocSymbols';
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
declare class MediaRecorder {
constructor(e: any, options?: any); // whatever MediaRecorder has
@@ -158,11 +162,11 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
// });
}
componentWillUnmount() {
- const ind = DocUtils.ActiveRecordings.indexOf(this);
- ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
+ const ind = DocViewUtils.ActiveRecordings.indexOf(this);
+ ind !== -1 && DocViewUtils.ActiveRecordings.splice(ind, 1);
}
- specificContextMenu = (e: React.MouseEvent): void => {
+ specificContextMenu = (): void => {
const subitems = [{ description: 'Screen Capture', event: this.toggleRecording, icon: 'expand-arrows-alt' as any }];
ContextMenu.Instance.addItem({ description: 'Options...', subitems, icon: 'video' });
};
@@ -170,12 +174,12 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
@computed get content() {
return (
<video
- className={'videoBox-content'}
+ className="videoBox-content"
key="video"
ref={r => {
this._videoRef = r;
setTimeout(() => {
- if (this.layoutDoc.mediaState === media_state.PendingRecording && this._videoRef) {
+ if (this.layoutDoc.mediaState === mediaState.PendingRecording && this._videoRef) {
this.toggleRecording();
}
}, 100);
@@ -183,7 +187,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
autoPlay={this._screenCapture}
style={{ width: this._screenCapture ? '100%' : undefined, height: this._screenCapture ? '100%' : undefined }}
onCanPlay={this.videoLoad}
- controls={true}
+ controls
onClick={e => e.preventDefault()}>
<source type="video/mp4" />
Not supported.
@@ -220,23 +224,23 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
toggleRecording = async () => {
if (!this._screenCapture) {
this._audioRec = new MediaRecorder(await navigator.mediaDevices.getUserMedia({ audio: true }));
- const aud_chunks: any = [];
- this._audioRec.ondataavailable = (e: any) => aud_chunks.push(e.data);
- this._audioRec.onstop = async (e: any) => {
- const [{ result }] = await Networking.UploadFilesToServer(aud_chunks.map((file: any) => ({ file })));
+ const audChunks: any = [];
+ this._audioRec.ondataavailable = (e: any) => audChunks.push(e.data);
+ this._audioRec.onstop = async () => {
+ const [{ result }] = await Networking.UploadFilesToServer(audChunks.map((file: any) => ({ file })));
if (!(result instanceof Error)) {
this.dataDoc[this._props.fieldKey + '_audio'] = new AudioField(result.accessPaths.agnostic.client);
}
};
this._videoRef!.srcObject = await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
this._videoRec = new MediaRecorder(this._videoRef!.srcObject);
- const vid_chunks: any = [];
+ const vidChunks: any = [];
this._videoRec.onstart = () => {
if (this.dataDoc[this._props.fieldKey + '_trackScreen']) TrackMovements.Instance.start();
this.dataDoc[this._props.fieldKey + '_recordingStart'] = new DateField(new Date());
};
- this._videoRec.ondataavailable = (e: any) => vid_chunks.push(e.data);
- this._videoRec.onstop = async (e: any) => {
+ this._videoRec.ondataavailable = (e: any) => vidChunks.push(e.data);
+ this._videoRec.onstop = async () => {
const presentation = TrackMovements.Instance.yieldPresentation();
if (presentation?.movements) {
const presCopy = { ...presentation };
@@ -244,7 +248,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
this.dataDoc[this.fieldKey + '_presentation'] = JSON.stringify(presCopy);
}
TrackMovements.Instance.finish();
- const file = new File(vid_chunks, `${this.Document[Id]}.mkv`, { type: vid_chunks[0].type, lastModified: Date.now() });
+ const file = new File(vidChunks, `${this.Document[Id]}.mkv`, { type: vidChunks[0].type, lastModified: Date.now() });
const [{ result }] = await Networking.UploadFilesToServer({ file });
this.dataDoc[this.fieldKey + '_duration'] = (new Date().getTime() - this.recordingStart!) / 1000;
if (!(result instanceof Error)) {
@@ -262,7 +266,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
this._screenCapture = true;
this.dataDoc.mediaState = 'recording';
});
- DocUtils.ActiveRecordings.push(this);
+ DocViewUtils.ActiveRecordings.push(this);
} else {
this._audioRec?.stop();
this._videoRec?.stop();
@@ -270,8 +274,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
this._screenCapture = false;
this.dataDoc.mediaState = 'paused';
});
- const ind = DocUtils.ActiveRecordings.indexOf(this);
- ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
+ const ind = DocViewUtils.ActiveRecordings.indexOf(this);
+ ind !== -1 && DocViewUtils.ActiveRecordings.splice(ind, 1);
CaptureManager.Instance.open(this.Document);
}
@@ -297,6 +301,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
<div className="videoBox-viewer">
<div style={{ position: 'relative', height: this.videoPanelHeight() }}>
<CollectionFreeFormView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={emptyFunction}
NativeWidth={returnZero}
@@ -305,7 +310,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
PanelWidth={this._props.PanelWidth}
focus={this._props.focus}
isSelected={this._props.isSelected}
- isAnnotationOverlay={true}
+ isAnnotationOverlay
select={emptyFunction}
isContentActive={returnFalse}
NativeDimScaling={returnOne}
@@ -324,9 +329,10 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
<div style={{ background: SettingsManager.userColor, position: 'relative', height: this.formattedPanelHeight() }}>
{!(this.dataDoc[this.fieldKey + '_dictation'] instanceof Doc) ? null : (
<FormattedTextBox
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
Document={DocCast(this.dataDoc[this.fieldKey + '_dictation'])}
- fieldKey={'text'}
+ fieldKey="text"
PanelHeight={this.formattedPanelHeight}
select={emptyFunction}
isContentActive={emptyFunction}
@@ -353,3 +359,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
);
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.SCREENSHOT, {
+ layout: { view: ScreenshotBox, dataField: 'data' },
+ options: { acl: '', _layout_nativeDimEditable: true, systemIcon: 'BsCameraFill' },
+});
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 8c65fd34e..bc19d7ad1 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -1,8 +1,9 @@
-let ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default;
+/* eslint-disable react/button-has-type */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { returnAlways, returnEmptyString } from '../../../Utils';
+import { returnAlways, returnEmptyString } from '../../../ClientUtils';
import { Doc } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
@@ -17,10 +18,14 @@ import { ContextMenu } from '../ContextMenu';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { EditableView } from '../EditableView';
import { OverlayView } from '../OverlayView';
-import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import { FieldView, FieldViewProps } from './FieldView';
import { DocumentIconContainer } from './DocumentIcon';
import './ScriptingBox.scss';
+import { Docs } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
+
const _global = (window /* browser */ || global) /* node */ as any;
+const ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default;
@observer
export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@@ -79,26 +84,24 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
@computed({ keepAlive: true }) get rawScript() {
return ScriptCast(this.dataDoc[this.fieldKey])?.script.originalScript ?? '';
}
- @computed({ keepAlive: true }) get functionName() {
- return StrCast(this.dataDoc[this.fieldKey + '-functionName'], '');
- }
- @computed({ keepAlive: true }) get functionDescription() {
- return StrCast(this.dataDoc[this.fieldKey + '-functionDescription'], '');
- }
- @computed({ keepAlive: true }) get compileParams() {
- return Cast(this.dataDoc[this.fieldKey + '-params'], listSpec('string'), []);
- }
-
set rawScript(value) {
this.dataDoc[this.fieldKey] = new ScriptField(undefined, undefined, value);
}
+ @computed({ keepAlive: true }) get functionName() {
+ return StrCast(this.dataDoc[this.fieldKey + '-functionName'], '');
+ }
set functionName(value) {
this.dataDoc[this.fieldKey + '-functionName'] = value;
}
+ @computed({ keepAlive: true }) get functionDescription() {
+ return StrCast(this.dataDoc[this.fieldKey + '-functionDescription'], '');
+ }
set functionDescription(value) {
this.dataDoc[this.fieldKey + '-functionDescription'] = value;
}
-
+ @computed({ keepAlive: true }) get compileParams() {
+ return Cast(this.dataDoc[this.fieldKey + '-params'], listSpec('string'), []);
+ }
set compileParams(value) {
this.dataDoc[this.fieldKey + '-params'] = new List<string>(value);
}
@@ -107,9 +110,8 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
if (typeof result === 'object') {
const text = descrip ? result[1] : result[2];
return text !== undefined ? text : '';
- } else {
- return '';
}
+ return '';
}
onClickScriptDisable = returnAlways;
@@ -118,19 +120,18 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
componentDidMount() {
this._props.setContentViewBox?.(this);
this.rawText = this.rawScript;
- const observer = new _global.ResizeObserver(
- action((entries: any) => {
+ const resizeObserver = new _global.ResizeObserver(
+ action(() => {
const area = document.querySelector('textarea');
if (area) {
- for (const {} of entries) {
- const getCaretCoordinates = require('textarea-caret');
- const caret = getCaretCoordinates(area, this._selection);
- this.resetSuggestionPos(caret);
- }
+ // eslint-disable-next-line global-require
+ const getCaretCoordinates = require('textarea-caret');
+ const caret = getCaretCoordinates(area, this._selection);
+ this.resetSuggestionPos(caret);
}
})
);
- observer.observe(document.getElementsByClassName('scriptingBox-outerDiv')[0]);
+ resizeObserver.observe(document.getElementsByClassName('scriptingBox-outerDiv')[0]);
}
@action
@@ -138,12 +139,12 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
if (!this._suggestionRef.current || !this._scriptTextRef.current) return;
const suggestionWidth = this._suggestionRef.current.offsetWidth;
const scriptWidth = this._scriptTextRef.current.offsetWidth;
- const top = caret.top;
- const x = this.dataDoc.x;
- let left = caret.left;
+ const { top } = caret;
+ const { x } = this.dataDoc;
+ let { left } = caret;
if (left + suggestionWidth > x + scriptWidth) {
const diff = left + suggestionWidth - (x + scriptWidth);
- left = left - diff;
+ left -= diff;
}
this._suggestionBoxX = left;
@@ -155,7 +156,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
}
protected createDashEventsTarget = (ele: HTMLDivElement, dropFunc: (e: Event, de: DragManager.DropEvent) => void) => {
- //used for stacking and masonry view
+ // used for stacking and masonry view
if (ele) {
this.dropDisposer?.();
this.dropDisposer = DragManager.MakeDropTarget(ele, dropFunc, this.layoutDoc);
@@ -164,7 +165,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
// only included in buttons, transforms scripting UI to a button
@action
- onFinish = () => (this.layoutDoc.layout_fieldKey = 'layout');
+ onFinish = () => {
+ this.layoutDoc.layout_fieldKey = 'layout';
+ };
// displays error message
@action
@@ -176,7 +179,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
@action
onCompile = () => {
const params: ScriptParam = {};
- this.compileParams.forEach(p => (params[p.split(':')[0].trim()] = p.split(':')[1].trim()));
+ this.compileParams.forEach(p => {
+ params[p.split(':')[0].trim()] = p.split(':')[1].trim();
+ });
const result = !this.rawText.trim()
? ({ compiled: false, errors: undefined } as any)
@@ -196,7 +201,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
onRun = () => {
if (this.onCompile()) {
const bindings: { [name: string]: any } = {};
- this.paramsNames.forEach(key => (bindings[key] = this.dataDoc[key]));
+ this.paramsNames.forEach(key => {
+ bindings[key] = this.dataDoc[key];
+ });
// binds vars so user doesnt have to refer to everything as this.<var>
ScriptCast(this.dataDoc[this.fieldKey], null)?.script.run({ ...bindings, this: this.Document }, this.onError);
}
@@ -247,7 +254,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
this.dataDoc.name = this.functionName;
this.dataDoc.description = this.functionDescription;
- //this.dataDoc.parameters = this.compileParams;
+ // this.dataDoc.parameters = this.compileParams;
this.dataDoc.script = this.rawScript;
ScriptManager.Instance.addScript(this.dataDoc);
@@ -255,6 +262,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
this._scriptKeys = ScriptingGlobals.getGlobals();
this._scriptingDescriptions = ScriptingGlobals.getDescriptions();
this._scriptingParams = ScriptingGlobals.getParameters();
+ return undefined;
};
// overlays document numbers (ex. d32) over all documents when clicked on
@@ -267,7 +275,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
@action
onDrop = (e: Event, de: DragManager.DropEvent, fieldKey: string) => {
if (de.complete.docDragData) {
- de.complete.docDragData.droppedDocuments.forEach(doc => (this.dataDoc[fieldKey] = doc));
+ de.complete.docDragData.droppedDocuments.forEach(doc => {
+ this.dataDoc[fieldKey] = doc;
+ });
e.stopPropagation();
return true;
}
@@ -285,8 +295,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
// sets field of the param name to the selected value in drop down box
@action
viewChanged = (e: React.ChangeEvent, name: string) => {
- //@ts-ignore
- const val = e.target.selectedOptions[0].value;
+ const val = (e.target as any).selectedOptions[0].value;
this.dataDoc[name] = val[0] === 'S' ? val.substring(1) : val[0] === 'N' ? parseInt(val.substring(1)) : val.substring(1) === 'true';
};
@@ -306,8 +315,26 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
};
renderFunctionInputs() {
- const descriptionInput = <textarea className="scriptingBox-textarea-inputs" onChange={e => (this.functionDescription = e.target.value)} placeholder="enter description here" value={this.functionDescription} />;
- const nameInput = <textarea className="scriptingBox-textarea-inputs" onChange={e => (this.functionName = e.target.value)} placeholder="enter name here" value={this.functionName} />;
+ const descriptionInput = (
+ <textarea
+ className="scriptingBox-textarea-inputs"
+ onChange={e => {
+ this.functionDescription = e.target.value;
+ }}
+ placeholder="enter description here"
+ value={this.functionDescription}
+ />
+ );
+ const nameInput = (
+ <textarea
+ className="scriptingBox-textarea-inputs"
+ onChange={e => {
+ this.functionName = e.target.value;
+ }}
+ placeholder="enter name here"
+ value={this.functionName}
+ />
+ );
return (
<div className="scriptingBox-inputDiv" onPointerDown={e => this._props.isSelected() && e.stopPropagation()}>
@@ -339,7 +366,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
return (
<div className="scriptingBox-paramInputs" onFocus={this.onFocus} onBlur={() => this._overlayDisposer?.()} ref={ele => ele && this.createDashEventsTarget(ele, (e, de) => this.onDrop(e, de, parameter))}>
<EditableView
- display={'block'}
+ display="block"
maxHeight={72}
height={35}
fontSize={14}
@@ -371,7 +398,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
return (
<div className="scriptingBox-paramInputs" style={{ overflowY: 'hidden' }}>
<EditableView
- display={'block'}
+ display="block"
maxHeight={72}
height={35}
fontSize={14}
@@ -404,6 +431,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
onChange={e => this.viewChanged(e, parameter)}
value={typeof this.dataDoc[parameter] === 'string' ? 'S' + StrCast(this.dataDoc[parameter]) : typeof this.dataDoc[parameter] === 'number' ? 'N' + NumCast(this.dataDoc[parameter]) : 'B' + BoolCast(this.dataDoc[parameter])}>
{types.map((type, i) => (
+ // eslint-disable-next-line react/no-array-index-key
<option key={i} className="scriptingBox-viewOption" value={(typeof type === 'string' ? 'S' : typeof type === 'number' ? 'N' : 'B') + type}>
{' '}
{type.toString()}{' '}
@@ -496,6 +524,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
@action
suggestionPos = () => {
+ // eslint-disable-next-line global-require
const getCaretCoordinates = require('textarea-caret');
const This = this;
document.querySelector('textarea')?.addEventListener('input', function () {
@@ -509,7 +538,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
keyHandler(e: any, pos: number) {
e.stopPropagation();
if (this._lastChar === 'Enter') {
- this.rawText = this.rawText + ' ';
+ this.rawText += ' ';
}
if (e.key === '(') {
this.suggestionPos();
@@ -524,20 +553,18 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
}
} else if (e.key === ')') {
this._paramSuggestion = false;
- } else {
- if (e.key === 'Backspace') {
- if (this._lastChar === '(') {
- this._paramSuggestion = false;
- } else if (this._lastChar === ')') {
- if (this.rawText.slice(0, this.rawText.length - 1).split('(').length - 1 > this.rawText.slice(0, this.rawText.length - 1).split(')').length - 1) {
- if (this._scriptParamsText.length > 0) {
- this._paramSuggestion = true;
- }
+ } else if (e.key === 'Backspace') {
+ if (this._lastChar === '(') {
+ this._paramSuggestion = false;
+ } else if (this._lastChar === ')') {
+ if (this.rawText.slice(0, this.rawText.length - 1).split('(').length - 1 > this.rawText.slice(0, this.rawText.length - 1).split(')').length - 1) {
+ if (this._scriptParamsText.length > 0) {
+ this._paramSuggestion = true;
}
}
- } else if (this.rawText.split('(').length - 1 <= this.rawText.split(')').length - 1) {
- this._paramSuggestion = false;
}
+ } else if (this.rawText.split('(').length - 1 <= this.rawText.split(')').length - 1) {
+ this._paramSuggestion = false;
}
this._lastChar = e.key === 'Backspace' ? this.rawText[this.rawText.length - 2] : e.key;
@@ -559,9 +586,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
parameters.forEach((element: string, i: number) => {
if (i < numEntered - 1) {
- first = first + element;
+ first += element;
} else if (i > numEntered - 1) {
- last = last + element;
+ last += element;
}
});
@@ -598,16 +625,16 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
placeholder="write your script here"
onFocus={this.onFocus}
onBlur={() => this._overlayDisposer?.()}
- onChange={action((e: any) => (this.rawText = e.target.value))}
+ onChange={action((e: any) => {
+ this.rawText = e.target.value;
+ })}
value={this.rawText}
- movePopupAsYouType={true}
+ movePopupAsYouType
loadingComponent={() => <span>Loading</span>}
trigger={{
' ': {
dataProvider: (token: any) => this.handleToken(token),
- component: (blob: any) => {
- return this.renderFuncListElement(blob.entity);
- },
+ component: (blob: any) => this.renderFuncListElement(blob.entity),
output: (item: any, trigger: any) => {
this._spaced = true;
return trigger + item.trim();
@@ -615,9 +642,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
},
'.': {
dataProvider: (token: any) => this.handleToken(token),
- component: (blob: any) => {
- return this.renderFuncListElement(blob.entity);
- },
+ component: (blob: any) => this.renderFuncListElement(blob.entity),
output: (item: any, trigger: any) => {
this._spaced = true;
return trigger + item.trim();
@@ -653,16 +678,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
// params box on bottom
const parameterInput = (
<div className="scriptingBox-params">
- <EditableView
- display={'block'}
- maxHeight={72}
- height={35}
- fontSize={22}
- contents={''}
- GetValue={returnEmptyString}
- SetValue={value => (value && value !== ' ' ? this.compileParam(value) : false)}
- placeholder={'enter parameters here'}
- />
+ <EditableView display="block" maxHeight={72} height={35} fontSize={22} contents="" GetValue={returnEmptyString} SetValue={value => (value && value !== ' ' ? this.compileParam(value) : false)} placeholder="enter parameters here" />
</div>
);
@@ -670,13 +686,14 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
const definedParameters = !this.compileParams.length ? null : (
<div className="scriptingBox-plist" style={{ width: '30%' }}>
{this.compileParams.map((parameter, i) => (
+ // eslint-disable-next-line react/no-array-index-key
<div key={i} className="scriptingBox-pborder" onKeyDown={e => e.key === 'Enter' && this._overlayDisposer?.()}>
<EditableView
- display={'block'}
+ display="block"
maxHeight={72}
height={35}
fontSize={12}
- background-color={'beige'}
+ background-color="beige"
contents={parameter}
GetValue={() => parameter}
SetValue={value => (value && value !== ' ' ? this.compileParam(value, i) : this.onDelete(i))}
@@ -749,6 +766,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
{!this.compileParams.length || !this.paramsNames ? null : (
<div className="scriptingBox-plist">
{this.paramsNames.map((parameter: string, i: number) => (
+ // eslint-disable-next-line react/no-array-index-key
<div key={i} className="scriptingBox-pborder" onKeyDown={e => e.key === 'Enter' && this._overlayDisposer?.()}>
<div className="scriptingBox-wrapper" style={{ maxHeight: '40px' }}>
<div className="scriptingBox-paramNames"> {`${parameter}:${this.paramsTypes[i]} = `} </div>
@@ -829,3 +847,8 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
);
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.SCRIPTING, {
+ layout: { view: ScriptingBox, dataField: 'data' },
+ options: { acl: '', systemIcon: 'BsFileEarmarkCodeFill' },
+});
diff --git a/src/client/views/nodes/SliderBox-components.tsx b/src/client/views/nodes/SliderBox-components.tsx
index e19f28f08..04efaac8e 100644
--- a/src/client/views/nodes/SliderBox-components.tsx
+++ b/src/client/views/nodes/SliderBox-components.tsx
@@ -1,6 +1,6 @@
-import * as React from "react";
-import { SliderItem } from "react-compound-slider";
-import "./SliderBox-tooltip.css";
+import * as React from 'react';
+import { SliderItem } from 'react-compound-slider';
+import './SliderBox-tooltip.css';
const { Component, Fragment } = React;
@@ -8,24 +8,24 @@ const { Component, Fragment } = React;
// TOOLTIP RAIL
// *******************************************************
const railStyle: React.CSSProperties = {
- position: "absolute",
- width: "100%",
+ position: 'absolute',
+ width: '100%',
height: 40,
borderRadius: 7,
- cursor: "pointer",
+ cursor: 'pointer',
opacity: 0.3,
zIndex: 300,
- border: "1px solid grey"
+ border: '1px solid grey',
};
const railCenterStyle: React.CSSProperties = {
- position: "absolute",
- width: "100%",
+ position: 'absolute',
+ width: '100%',
height: 14,
borderRadius: 7,
- cursor: "pointer",
- pointerEvents: "none",
- backgroundColor: "rgb(155,155,155)"
+ cursor: 'pointer',
+ pointerEvents: 'none',
+ backgroundColor: 'rgb(155,155,155)',
};
interface TooltipRailProps {
@@ -37,21 +37,21 @@ interface TooltipRailProps {
export class TooltipRail extends Component<TooltipRailProps> {
state = {
value: null,
- percent: null
+ percent: null,
};
static defaultProps = {
- disabled: false
+ disabled: false,
};
onMouseEnter = () => {
- document.addEventListener("mousemove", this.onMouseMove);
- }
+ document.addEventListener('mousemove', this.onMouseMove);
+ };
onMouseLeave = () => {
this.setState({ value: null, percent: null });
- document.removeEventListener("mousemove", this.onMouseMove);
- }
+ document.removeEventListener('mousemove', this.onMouseMove);
+ };
onMouseMove = (e: Event) => {
const { activeHandleID, getEventData } = this.props;
@@ -61,7 +61,7 @@ export class TooltipRail extends Component<TooltipRailProps> {
} else {
this.setState(getEventData(e));
}
- }
+ };
render() {
const { value, percent } = this.state;
@@ -73,11 +73,10 @@ export class TooltipRail extends Component<TooltipRailProps> {
<div
style={{
left: `${percent}%`,
- position: "absolute",
- marginLeft: "-11px",
- marginTop: "-35px"
- }}
- >
+ position: 'absolute',
+ marginLeft: '-11px',
+ marginTop: '-35px',
+ }}>
<div className="tooltip">
<span className="tooltiptext">Value: {value}</span>
</div>
@@ -87,7 +86,7 @@ export class TooltipRail extends Component<TooltipRailProps> {
style={railStyle}
{...getRailProps({
onMouseEnter: this.onMouseEnter,
- onMouseLeave: this.onMouseLeave
+ onMouseLeave: this.onMouseLeave,
})}
/>
<div style={railCenterStyle} />
@@ -102,28 +101,28 @@ export class TooltipRail extends Component<TooltipRailProps> {
interface HandleProps {
key: string;
handle: SliderItem;
- isActive: Boolean;
- disabled?: Boolean;
+ isActive: boolean;
+ disabled?: boolean;
domain: number[];
getHandleProps: (id: string, config: object) => object;
}
export class Handle extends Component<HandleProps> {
static defaultProps = {
- disabled: false
+ disabled: false,
};
state = {
- mouseOver: false
+ mouseOver: false,
};
onMouseEnter = () => {
this.setState({ mouseOver: true });
- }
+ };
onMouseLeave = () => {
this.setState({ mouseOver: false });
- }
+ };
render() {
const {
@@ -131,7 +130,7 @@ export class Handle extends Component<HandleProps> {
handle: { id, value, percent },
isActive,
disabled,
- getHandleProps
+ getHandleProps,
} = this.props;
const { mouseOver } = this.state;
@@ -141,11 +140,10 @@ export class Handle extends Component<HandleProps> {
<div
style={{
left: `${percent}%`,
- position: "absolute",
- marginLeft: "-11px",
- marginTop: "-35px"
- }}
- >
+ position: 'absolute',
+ marginLeft: '-11px',
+ marginTop: '-35px',
+ }}>
<div className="tooltip">
<span className="tooltiptext">Value: {value}</span>
</div>
@@ -158,21 +156,21 @@ export class Handle extends Component<HandleProps> {
aria-valuenow={value}
style={{
left: `${percent}%`,
- position: "absolute",
- marginLeft: "-11px",
- marginTop: "-6px",
+ position: 'absolute',
+ marginLeft: '-11px',
+ marginTop: '-6px',
zIndex: 400,
width: 24,
height: 24,
- cursor: "pointer",
+ cursor: 'pointer',
border: 0,
- borderRadius: "50%",
- boxShadow: "1px 1px 1px 1px rgba(0, 0, 0, 0.4)",
- backgroundColor: disabled ? "#666" : "#3e1db3"
+ borderRadius: '50%',
+ boxShadow: '1px 1px 1px 1px rgba(0, 0, 0, 0.4)',
+ backgroundColor: disabled ? '#666' : '#3e1db3',
}}
{...getHandleProps(id, {
onMouseEnter: this.onMouseEnter,
- onMouseLeave: this.onMouseLeave
+ onMouseLeave: this.onMouseLeave,
})}
/>
</Fragment>
@@ -186,27 +184,22 @@ export class Handle extends Component<HandleProps> {
interface TrackProps {
source: SliderItem;
target: SliderItem;
- disabled: Boolean;
+ disabled: boolean;
getTrackProps: () => object;
}
-export function Track({
- source,
- target,
- getTrackProps,
- disabled = false
-}: TrackProps) {
+export function Track({ source, target, getTrackProps, disabled = false }: TrackProps) {
return (
<div
style={{
- position: "absolute",
+ position: 'absolute',
height: 14,
zIndex: 1,
- backgroundColor: disabled ? "#999" : "#3e1db3",
+ backgroundColor: disabled ? '#999' : '#3e1db3',
borderRadius: 7,
- cursor: "pointer",
+ cursor: 'pointer',
left: `${source.percent}%`,
- width: `${target.percent - source.percent}%`
+ width: `${target.percent - source.percent}%`,
}}
{...getTrackProps()}
/>
@@ -222,32 +215,31 @@ interface TickProps {
format: (val: number) => string;
}
-const defaultFormat = (d: number) => `d`;
+const defaultFormat = () => `d`;
export function Tick({ tick, count, format = defaultFormat }: TickProps) {
return (
<div>
<div
style={{
- position: "absolute",
+ position: 'absolute',
marginTop: 20,
width: 1,
height: 5,
- backgroundColor: "rgb(200,200,200)",
- left: `${tick.percent}%`
+ backgroundColor: 'rgb(200,200,200)',
+ left: `${tick.percent}%`,
}}
/>
<div
style={{
- position: "absolute",
+ position: 'absolute',
marginTop: 25,
fontSize: 10,
- textAlign: "center",
+ textAlign: 'center',
marginLeft: `${-(100 / count) / 2}%`,
width: `${100 / count}%`,
- left: `${tick.percent}%`
- }}
- >
+ left: `${tick.percent}%`,
+ }}>
{format(tick.value)}
</div>
</div>
diff --git a/src/client/views/nodes/TaskCompletedBox.tsx b/src/client/views/nodes/TaskCompletedBox.tsx
index c9e15d314..6f11cd73a 100644
--- a/src/client/views/nodes/TaskCompletedBox.tsx
+++ b/src/client/views/nodes/TaskCompletedBox.tsx
@@ -5,7 +5,7 @@ import * as React from 'react';
import './TaskCompletedBox.scss';
@observer
-export class TaskCompletionBox extends React.Component<{}> {
+export class TaskCompletionBox extends React.Component {
@observable public static taskCompleted: boolean = false;
@observable public static popupX: number = 500;
@observable public static popupY: number = 150;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 4773a21c9..afd73cfe8 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,22 +1,22 @@
+/* eslint-disable jsx-a11y/media-has-caption */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { basename } from 'path';
import * as React from 'react';
+import { ClientUtils, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
import { Doc, Opt, StrListCast } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, NumCast, StrCast, toList } from '../../../fields/Types';
import { AudioField, ImageField, VideoField } from '../../../fields/URLField';
-import { emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
+import { emptyFunction, formatTime } from '../../../Utils';
+import { Docs } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
-import { DocumentManager } from '../../util/DocumentManager';
-import { dropActionType } from '../../util/DragManager';
-import { FollowLinkScript } from '../../util/LinkFollower';
-import { LinkManager } from '../../util/LinkManager';
+import { DocUtils, FollowLinkScript } from '../../documents/DocUtils';
+import { dropActionType } from '../../util/DropActionTypes';
import { ReplayMovements } from '../../util/ReplayMovements';
import { undoBatch } from '../../util/UndoManager';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
@@ -24,13 +24,15 @@ import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionS
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
+import { VideoThumbnails } from '../global/globalEnums';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { AnchorMenu } from '../pdf/AnchorMenu';
-import { StyleProp } from '../StyleProvider';
+import { PinDocView, PinProps } from '../PinFuncs';
+import { StyleProp } from '../StyleProp';
import { DocumentView } from './DocumentView';
-import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
+import { FieldView, FieldViewProps } from './FieldView';
+import { FocusViewOptions } from './FocusViewOptions';
import { RecordingBox } from './RecordingBox';
-import { PinProps, PresBox } from './trails';
import './VideoBox.scss';
/**
@@ -51,7 +53,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
return FieldView.LayoutString(VideoBox, fieldKey);
}
static heightPercent = 80; // height of video relative to videoBox when timeline is open
- static numThumbnails = 20;
private unmounting = false;
private _disposers: { [name: string]: IReactionDisposer } = {};
private _videoRef: HTMLVideoElement | null = null; // <video> ref
@@ -62,6 +63,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _playRegionTimer: any = null; // timeout for playback
private _controlsFadeTimer: any = null; // timeout for controls fade
+ private _ffref = React.createRef<CollectionFreeFormView>();
constructor(props: FieldViewProps) {
super(props);
@@ -84,7 +86,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
@observable _scrubbing: boolean = false;
@computed get links() {
- return LinkManager.Links(this.dataDoc);
+ return Doc.Links(this.dataDoc);
}
@computed get heightPercent() {
return NumCast(this.layoutDoc._layout_timelineHeightPercent, 100);
@@ -152,11 +154,14 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
clearTimeout(this._controlsFadeTimer);
this._scrubbing = true;
this._controlsFadeTimer = setTimeout(
- action(() => (this._scrubbing = false)),
+ action(() => {
+ this._scrubbing = false;
+ }),
500
);
e.stopPropagation();
break;
+ default:
}
}
};
@@ -203,7 +208,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
else {
this._keepCurrentlyPlaying = true;
this.pause();
- setTimeout(() => (this._keepCurrentlyPlaying = false));
+ setTimeout(() => {
+ this._keepCurrentlyPlaying = false;
+ });
}
};
@@ -246,7 +253,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
clearTimeout(this._controlsFadeTimer);
this._controlsVisible = true;
this._controlsFadeTimer = setTimeout(
- action(() => (this._controlsVisible = false)),
+ action(() => {
+ this._controlsVisible = false;
+ }),
3000
);
}
@@ -262,7 +271,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
setupMoveUpEvents(
e.target,
e,
- action((e, down, delta) => {
+ action((moveEv, down, delta) => {
if (this._controlsTransform) {
this._controlsTransform.X = Math.max(0, Math.min(delta[0] + this._controlsTransform.X, window.innerWidth));
this._controlsTransform.Y = Math.max(0, Math.min(delta[1] + this._controlsTransform.Y, window.innerHeight));
@@ -280,7 +289,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
const canvas = document.createElement('canvas');
canvas.width = 640;
canvas.height = (640 * Doc.NativeHeight(this.layoutDoc)) / (Doc.NativeWidth(this.layoutDoc) || 1);
- const ctx = canvas.getContext('2d'); //draw image to canvas. scale to target dimensions
+ const ctx = canvas.getContext('2d'); // draw image to canvas. scale to target dimensions
if (ctx) {
this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height);
}
@@ -297,13 +306,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
this._props.addDocument?.(b);
DocUtils.MakeLink(b, this.Document, { link_relationship: 'video snapshot' });
} else {
- //convert to desired file format
+ // convert to desired file format
const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
// if you want to preview the captured image,
- const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, '');
- const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[\.\/\?\=]/g, '_'));
+ const retitled = StrCast(this.Document.title).replace(/[ -.:]/g, '');
+ const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[./?=]/g, '_'));
const filename = basename(encodedFilename);
- Utils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY));
+ ClientUtils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY));
}
};
@@ -318,7 +327,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
// creates link for snapshot
createSnapshotLink = (imagePath: string, downX?: number, downY?: number) => {
- const url = !imagePath.startsWith('/') ? Utils.CorsProxy(imagePath) : imagePath;
+ const url = !imagePath.startsWith('/') ? ClientUtils.CorsProxy(imagePath) : imagePath;
const width = NumCast(this.layoutDoc._width) || 1;
const height = NumCast(this.layoutDoc._height);
const imageSnapshot = Docs.Create.ImageDocument(url, {
@@ -334,9 +343,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
Doc.SetNativeWidth(imageSnapshot[DocData], Doc.NativeWidth(this.layoutDoc));
Doc.SetNativeHeight(imageSnapshot[DocData], Doc.NativeHeight(this.layoutDoc));
this._props.addDocument?.(imageSnapshot);
- const link = DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
+ DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
// link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3); // do we need to set an end time? should default to +0.1
- setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true));
+ setTimeout(() => downX !== undefined && downY !== undefined && DocumentView.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true));
};
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
@@ -345,9 +354,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
if (!addAsAnnotation && marquee) marquee.backgroundColor = 'transparent';
const anchor =
addAsAnnotation && marquee
- ? CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.annotationKey, timecode ? timecode : undefined, undefined, marquee, addAsAnnotation) || this.Document
+ ? CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.annotationKey, timecode || undefined, undefined, marquee, addAsAnnotation) || this.Document
: Docs.Create.ConfigDocument({ title: '#' + timecode, _timecodeToShow: timecode, annotationOn: this.Document });
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true, pannable: true } }, this.Document);
+ PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true, pannable: true } }, this.Document);
return anchor;
};
@@ -375,12 +384,14 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
if (this._stackedTimeline?.makeDocUnfiltered(doc)) {
if (this.heightPercent === 100) {
// do we want to always open up the timeline when followin a link? kind of clunky visually
- //this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
+ // this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
options.didMove = true;
}
return this._stackedTimeline.getView(doc, options);
}
- return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ return new Promise<Opt<DocumentView>>(res => {
+ DocumentView.addViewRenderedCb(doc, dv => res(dv));
+ });
};
// extracts video thumbnails and saves them as field of doc
@@ -390,21 +401,25 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
const thumbnailPromises: Promise<any>[] = [];
const video = document.createElement('video');
- video.onloadedmetadata = () => (video.currentTime = 0);
+ video.onloadedmetadata = () => {
+ video.currentTime = 0;
+ };
video.onseeked = () => {
const canvas = document.createElement('canvas');
canvas.height = 100;
canvas.width = 100;
canvas.getContext('2d')?.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, 100, 100);
- const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, '');
+ const retitled = StrCast(this.Document.title).replace(/[ -.:]/g, '');
const encodedFilename = encodeURIComponent('thumbnail' + retitled + '_' + video.currentTime.toString().replace(/\./, '_'));
- thumbnailPromises?.push(Utils.convertDataUri(canvas.toDataURL(), basename(encodedFilename), true));
- const newTime = video.currentTime + video.duration / (VideoBox.numThumbnails - 1);
+ thumbnailPromises?.push(ClientUtils.convertDataUri(canvas.toDataURL(), basename(encodedFilename), true));
+ const newTime = video.currentTime + video.duration / (VideoThumbnails.DENSE - 1);
if (newTime < video.duration) {
video.currentTime = newTime;
} else {
- Promise.all(thumbnailPromises).then(thumbnails => (this.dataDoc[this.fieldKey + '_thumbnails'] = new List<string>(thumbnails)));
+ Promise.all(thumbnailPromises).then(thumbnails => {
+ this.dataDoc[this.fieldKey + '_thumbnails'] = new List<string>(thumbnails);
+ });
}
};
@@ -423,11 +438,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
this._disposers.reactionDisposer?.();
this._disposers.reactionDisposer = reaction(
() => NumCast(this.layoutDoc._layout_currentTimecode),
- time => !this._playing && (vref.currentTime = time),
+ time => {
+ !this._playing && (vref.currentTime = time);
+ },
{ fireImmediately: true }
);
- (!this.dataDoc[this.fieldKey + '_thumbnails'] || StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']).length != VideoBox.numThumbnails) && this.getVideoThumbnails();
+ (!this.dataDoc[this.fieldKey + '_thumbnails'] || StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']).length !== VideoThumbnails.DENSE) && this.getVideoThumbnails();
}
};
@@ -436,7 +453,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
setContentRef = (cref: HTMLDivElement | null) => {
this._contentRef = cref;
if (cref) {
- cref.onfullscreenchange = action(e => {
+ cref.onfullscreenchange = action(() => {
this._fullScreen = document.fullscreenElement === cref;
this._controlsVisible = true;
this._scrubbing = false;
@@ -451,7 +468,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
};
// context menu
- specificContextMenu = (e: React.MouseEvent): void => {
+ specificContextMenu = (): void => {
const field = Cast(this.dataDoc[this._props.fieldKey], VideoField);
if (field) {
const url = field.url.href;
@@ -462,25 +479,41 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
subitems.push({
description: 'Screen Capture',
event: async () => {
- runInAction(() => (this._screenCapture = !this._screenCapture));
+ runInAction(() => {
+ this._screenCapture = !this._screenCapture;
+ });
this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
},
icon: 'expand-arrows-alt',
});
- subitems.push({ description: (this.layoutDoc.dontAutoFollowLinks ? '' : "Don't") + ' follow links when encountered', event: () => (this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks), icon: 'expand-arrows-alt' });
+ subitems.push({
+ description: (this.layoutDoc.dontAutoFollowLinks ? '' : "Don't") + ' follow links when encountered',
+ event: () => {
+ this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks;
+ },
+ icon: 'expand-arrows-alt',
+ });
subitems.push({
description: (this.layoutDoc.dontAutoPlayFollowedLinks ? '' : "Don't") + ' play when link is selected',
- event: () => (this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks),
+ event: () => {
+ this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks;
+ },
+ icon: 'expand-arrows-alt',
+ });
+ subitems.push({
+ description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : 'Auto play') + ' anchors onClick',
+ event: () => {
+ this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors;
+ },
icon: 'expand-arrows-alt',
});
- subitems.push({ description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : 'Auto play') + ' anchors onClick', event: () => (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors), icon: 'expand-arrows-alt' });
// subitems.push({ description: "Start Trim All", event: () => this.startTrim(TrimScope.All), icon: "expand-arrows-alt" });
// subitems.push({ description: "Start Trim Clip", event: () => this.startTrim(TrimScope.Clip), icon: "expand-arrows-alt" });
// subitems.push({ description: "Stop Trim", event: () => this.finishTrim(), icon: "expand-arrows-alt" });
subitems.push({
description: 'Copy path',
event: () => {
- Utils.CopyText(url);
+ ClientUtils.CopyText(url);
},
icon: 'expand-arrows-alt',
});
@@ -504,7 +537,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
};
// ref for updating time
- setAudioRef = (e: HTMLAudioElement | null) => (this._audioPlayer = e);
+ setAudioRef = (e: HTMLAudioElement | null) => {
+ this._audioPlayer = e;
+ };
// renders the video and audio
@computed get content() {
@@ -570,8 +605,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
setupMoveUpEvents(
this,
e,
- e => {
- this.Snapshot(e.clientX, e.clientY);
+ moveEv => {
+ this.Snapshot(moveEv.clientX, moveEv.clientY);
return true;
},
emptyFunction,
@@ -586,7 +621,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
setupMoveUpEvents(
this,
e,
- action(encodeURIComponent => {
+ action(() => {
this._clicking = false;
if (this._props.isContentActive()) {
// const local = this.ScreenToLocalTransform().scale(this._props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
@@ -600,7 +635,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
() => {
this.layoutDoc._layout_timelineHeightPercent = this.heightPercent !== 100 ? 100 : VideoBox.heightPercent;
setTimeout(
- action(() => (this._clicking = false)),
+ action(() => {
+ this._clicking = false;
+ }),
500
);
},
@@ -613,30 +650,32 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
@action
removeCurrentlyPlaying = () => {
const docView = this.DocumentView?.();
- if (CollectionStackedTimeline.CurrentlyPlaying && docView) {
- const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView);
- index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
+ if (DocumentView.CurrentlyPlaying && docView) {
+ const index = DocumentView.CurrentlyPlaying.indexOf(docView);
+ index !== -1 && DocumentView.CurrentlyPlaying.splice(index, 1);
}
};
// adds doc to currently playing display
@action
addCurrentlyPlaying = () => {
const docView = this.DocumentView?.();
- if (!CollectionStackedTimeline.CurrentlyPlaying) {
- CollectionStackedTimeline.CurrentlyPlaying = [];
+ if (!DocumentView.CurrentlyPlaying) {
+ DocumentView.CurrentlyPlaying = [];
}
- if (docView && CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView) === -1) {
- CollectionStackedTimeline.CurrentlyPlaying.push(docView);
+ if (docView && DocumentView.CurrentlyPlaying.indexOf(docView) === -1) {
+ DocumentView.CurrentlyPlaying.push(docView);
}
};
// for annotating, adds doc with time info
@action.bound
- addDocWithTimecode(doc: Doc | Doc[]): boolean {
- const docs = doc instanceof Doc ? [doc] : doc;
+ addDocWithTimecode(docIn: Doc | Doc[]): boolean {
+ const docs = toList(docIn);
const curTime = NumCast(this.layoutDoc._layout_currentTimecode);
- docs.forEach(doc => (doc._timecodeToHide = (doc._timecodeToShow = curTime) + 1));
- return this.addDocument(doc);
+ docs.forEach(doc => {
+ doc._timecodeToHide = (doc._timecodeToShow = curTime) + 1;
+ });
+ return this.addDocument(docs);
}
// play back the audio from seekTimeInSeconds, fullPlay tells whether clip is being played to end vs link range
@@ -644,7 +683,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false) => {
clearTimeout(this._playRegionTimer);
this._playRegionTimer = undefined;
- if (Number.isNaN(this.player?.duration)) {
+ if (this.player?.duration === undefined || isNaN(this.player.duration)) {
setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
} else if (this.player) {
// trimBounds override requested playback bounds
@@ -696,7 +735,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
e,
returnFalse,
returnFalse,
- action((e: PointerEvent, doubleTap?: boolean) => {
+ action((clickEv: PointerEvent, doubleTap?: boolean) => {
if (doubleTap) {
this.startTrim(TrimScope.All);
} else if (this.timeline) {
@@ -731,11 +770,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
// stretches vertically or horizontally depending on video orientation so video fits full screen
fullScreenSize() {
if (this._videoRef && this._videoRef.videoHeight / this._videoRef.videoWidth > 1) {
- //prettier-ignore
- return ({ height: '100%' });
+ return { height: '100%' };
}
- //prettier-ignore
- return ({ width: '100%' });
+ return ({ width: '100%' }); // prettier-ignore
}
// for zoom slider, sets timeline waveform zoom
@@ -757,9 +794,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
setupMoveUpEvents(
this,
e,
- action(e => {
+ action(moveEv => {
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
- this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
+ this._marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]);
return true;
}),
returnFalse,
@@ -777,7 +814,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
this._props.select(true);
};
- timelineWhenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
+ timelineWhenChildContentsActiveChanged = action((isActive: boolean) => {
+ this._isAnyChildContentActive = isActive;
+ this._props.whenChildContentsActiveChanged(isActive);
+ });
timelineScreenToLocal = () =>
this._props
@@ -785,7 +825,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
.scale(this.scaling())
.translate(0, (-this.heightPercent / 100) * this._props.PanelHeight());
- setPlayheadTime = (time: number) => (this.player!.currentTime = this.layoutDoc._layout_currentTimecode = time);
+ setPlayheadTime = (time: number) => {
+ this.player!.currentTime = this.layoutDoc._layout_currentTimecode = time;
+ };
timelineHeight = () => (this._props.PanelHeight() * (100 - this.heightPercent)) / 100;
@@ -806,7 +848,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
marqueeOffset = () => [((this.panelWidth() / 2) * (1 - this.heightPercent / 100)) / (this.heightPercent / 100), 0];
- timelineDocFilter = () => [`_isTimelineLabel:true,${Utils.noRecursionHack}:x`];
+ timelineDocFilter = () => [`_isTimelineLabel:true,${ClientUtils.noRecursionHack}:x`];
// renders video controls
componentUI = (boundsLeft: number, boundsTop: number) => {
@@ -848,7 +890,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
return (
<div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%`, display: this.heightPercent === 100 ? 'none' : '' }}>
<CollectionStackedTimeline
- ref={action((r: any) => (this._stackedTimeline = r))}
+ ref={action((r: any) => {
+ this._stackedTimeline = r;
+ })}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
dataFieldKey={this.fieldKey}
fieldKey={this.annotationKey}
@@ -886,7 +931,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
}
crop = (region: Doc | undefined, addCrop?: boolean) => {
- if (!region) return;
+ if (!region) return undefined;
const cropping = Doc.MakeCopy(region, true);
const regionData = region[DocData];
regionData.backgroundColor = 'transparent';
@@ -915,8 +960,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
croppingProto.type = DocumentType.VID;
croppingProto.layout = VideoBox.LayoutString('data');
croppingProto.data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField);
- croppingProto['data_nativeWidth'] = anchw;
- croppingProto['data_nativeHeight'] = anchh;
+ croppingProto.data_nativeWidth = anchw;
+ croppingProto.data_nativeHeight = anchh;
croppingProto.videoCrop = true;
croppingProto.layout_currentTimecode = this.layoutDoc._layout_currentTimecode;
croppingProto.freeform_scale = viewScale;
@@ -933,6 +978,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
this._props.bringToFront?.(cropping);
return cropping;
};
+ focus = (anchor: Doc, options: FocusViewOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options));
savedAnnotations = () => this._savedAnnotations;
render() {
const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding);
@@ -958,14 +1004,16 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
left: (this._props.PanelWidth() - this.panelWidth()) / 2,
}}>
<CollectionFreeFormView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
+ ref={this._ffref}
setContentViewBox={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
renderDepth={this._props.renderDepth + 1}
fieldKey={this.annotationKey}
- isAnnotationOverlay={true}
- annotationLayerHostsContent={true}
+ isAnnotationOverlay
+ annotationLayerHostsContent
PanelWidth={this._props.PanelWidth}
PanelHeight={this._props.PanelHeight}
isAnyChildContentActive={returnFalse}
@@ -1049,12 +1097,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
</div>
)}
- <div className="videobox-button" title={'full screen'} onPointerDown={this.onFullDown}>
+ <div className="videobox-button" title="full screen" onPointerDown={this.onFullDown}>
<FontAwesomeIcon icon="expand" />
</div>
{!this._fullScreen && width > 300 && (
- <div className="videobox-button" title={'show timeline'} onPointerDown={this.onTimelineHdlDown}>
+ <div className="videobox-button" title="show timeline" onPointerDown={this.onTimelineHdlDown}>
<FontAwesomeIcon icon="eye" />
</div>
)}
@@ -1113,3 +1161,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
);
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.VID, {
+ layout: { view: VideoBox, dataField: 'data' },
+ options: { acl: '', _layout_currentTimecode: 0, systemIcon: 'BsFileEarmarkPlayFill' },
+});
+Docs.Prototypes.TemplateMap.set(DocumentType.REC, {
+ layout: { view: VideoBox, dataField: 'data' },
+ options: { acl: '', _height: 100, backgroundColor: 'pink', systemIcon: 'BsFillMicFill' },
+});
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 033b01d24..198652310 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,22 +1,26 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { htmlToText } from 'html-to-text';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import * as WebRequest from 'web-request';
-import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivHeight, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils';
+import { Doc, DocListCast, Field, FieldType, Opt } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { HtmlField } from '../../../fields/HtmlField';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
import { RefField } from '../../../fields/RefField';
import { listSpec } from '../../../fields/Schema';
-import { Cast, NumCast, StrCast, WebCast } from '../../../fields/Types';
+import { Cast, NumCast, StrCast, toList, WebCast } from '../../../fields/Types';
import { ImageField, WebField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, emptyFunction, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, stringHash, Utils } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
-import { DocumentManager } from '../../util/DocumentManager';
+import { emptyFunction, stringHash } from '../../../Utils';
+import { Docs } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DocUtils } from '../../documents/DocUtils';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SnappingManager } from '../../util/SnappingManager';
import { undoBatch, UndoManager } from '../../util/UndoManager';
@@ -31,17 +35,20 @@ import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { AnchorMenu } from '../pdf/AnchorMenu';
import { Annotation } from '../pdf/Annotation';
import { GPTPopup } from '../pdf/GPTPopup/GPTPopup';
+import { PinDocView, PinProps } from '../PinFuncs';
import { SidebarAnnos } from '../SidebarAnnos';
-import { StyleProp } from '../StyleProvider';
-import { DocumentView, OpenWhere } from './DocumentView';
-import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
+import { StyleProp } from '../StyleProp';
+import { DocumentView } from './DocumentView';
+import { FieldView, FieldViewProps } from './FieldView';
+import { FocusViewOptions } from './FocusViewOptions';
import { LinkInfo } from './LinkDocPreview';
-import { PinProps, PresBox } from './trails';
+import { OpenWhere } from './OpenWhere';
import './WebBox.scss';
+
const { CreateImage } = require('./WebBoxRenderer');
-const _global = (window /* browser */ || global) /* node */ as any;
+
@observer
-export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
+export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(WebBox, fieldKey);
}
@@ -105,7 +112,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
}
@action
- search = (searchString: string, bwd?: boolean, clear: boolean = false) => {
+ override search = (searchString: string, bwd?: boolean, clear: boolean = false) => {
if (!this._searching && !clear) {
this._searching = true;
setTimeout(() => {
@@ -141,19 +148,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const scrollTop = NumCast(this.layoutDoc._layout_scrollTop);
const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
const nativeHeight = (nativeWidth * this._props.PanelHeight()) / this._props.PanelWidth();
- var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
+ let htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
if (!htmlString) {
- htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text();
+ htmlString = await (await fetch(ClientUtils.CorsProxy(this.webField!.href))).text();
}
this.layoutDoc.thumb = undefined;
this.Document.thumbLockout = true; // lock to prevent multiple thumb updates.
CreateImage(this._webUrl.endsWith('/') ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl, this._iframe.contentDocument?.styleSheets ?? [], htmlString, nativeWidth, nativeHeight, scrollTop)
- .then((data_url: any) => {
- if (data_url.includes('<!DOCTYPE')) {
+ .then((dataUrl: any) => {
+ if (dataUrl.includes('<!DOCTYPE')) {
console.log('BAD DATA IN THUMB CREATION');
return;
}
- Utils.convertDataUri(data_url, this.layoutDoc[Id] + '-icon' + new Date().getTime(), true, this.layoutDoc[Id] + '-icon').then(returnedfilename =>
+ ClientUtils.convertDataUri(dataUrl, this.layoutDoc[Id] + '-icon' + new Date().getTime(), true, this.layoutDoc[Id] + '-icon').then(returnedfilename =>
setTimeout(
action(() => {
this.Document.thumbLockout = false;
@@ -166,7 +173,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
)
);
})
- .catch(function (error: any) {
+ .catch((error: any) => {
console.error('oops, something went wrong!', error);
});
};
@@ -187,7 +194,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
});
this._disposers.urlchange = reaction(
() => WebCast(this.dataDoc.data),
- url => this.submitURL(false, false)
+ () => this.submitURL(false, false)
);
this._disposers.titling = reaction(
() => StrCast(this.Document.title),
@@ -199,8 +206,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
this._disposers.layout_autoHeight = reaction(
() => this.layoutDoc._layout_autoHeight,
- layout_autoHeight => {
- if (layout_autoHeight) {
+ layoutAutoHeight => {
+ if (layoutAutoHeight) {
this.layoutDoc._nativeHeight = NumCast(this.Document[this._props.fieldKey + '_nativeHeight']);
this._props.setHeight?.(NumCast(this.Document[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
}
@@ -219,8 +226,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
}
} // else it's an HTMLfield
} else if (this.webField && !this.dataDoc.text) {
- WebRequest.get(Utils.CorsProxy(this.webField.href)) //
- .then(result => result && (this.dataDoc.text = htmlToText(result.content)));
+ WebRequest.get(ClientUtils.CorsProxy(this.webField.href)) //
+ .then(result => {
+ result && (this.dataDoc.text = htmlToText(result.content));
+ });
}
this._disposers.scrollReaction = reaction(
@@ -254,7 +263,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const clientRects = selRange.getClientRects();
for (let i = 0; i < clientRects.length; i++) {
const rect = clientRects.item(i);
- const mainrect = this._url ? { translateX: 0, translateY: 0, scale: 1 } : Utils.GetScreenTransform(this._mainCont.current);
+ const mainrect = this._url ? { translateX: 0, translateY: 0, scale: 1 } : ClientUtils.GetScreenTransform(this._mainCont.current);
if (rect && rect.width !== this._mainCont.current.clientWidth) {
const annoBox = document.createElement('div');
annoBox.className = 'marqueeAnnotator-annotationBox';
@@ -283,27 +292,39 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
focus = (anchor: Doc, options: FocusViewOptions) => {
if (anchor !== this.Document && this._outerRef.current) {
const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
- const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), NumCast(anchor._height), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + NumCast(anchor._height), this._scrollHeight));
+ const scrollTo = ClientUtils.scrollIntoView(
+ NumCast(anchor.y),
+ NumCast(anchor._height),
+ NumCast(this.layoutDoc._layout_scrollTop),
+ windowHeight,
+ windowHeight * 0.1,
+ Math.max(NumCast(anchor.y) + NumCast(anchor._height), this._scrollHeight)
+ );
if (scrollTo !== undefined) {
if (this._initialScroll === undefined) {
const focusTime = options.zoomTime ?? 500;
this.goTo(scrollTo, focusTime, options.easeFunc);
return focusTime;
- } else {
- this._initialScroll = scrollTo;
}
+ this._initialScroll = scrollTo;
}
}
+ return undefined;
};
@action
- getView = (doc: Doc, options: FocusViewOptions) => {
- if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this.DocumentView?.()));
+ getView = (doc: Doc /* , options: FocusViewOptions */) => {
+ if (Doc.AreProtosEqual(doc, this.Document))
+ return new Promise<Opt<DocumentView>>(res => {
+ res(this.DocumentView?.());
+ });
if (this.Document.layout_fieldKey === 'layout_icon') this.DocumentView?.().iconify();
const webUrl = WebCast(doc.config_data)?.url;
if (this._url && webUrl && webUrl.href !== this._url) this.setData(webUrl.href);
if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false);
- return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ return new Promise<Opt<DocumentView>>(res => {
+ DocumentView.addViewRenderedCb(doc, dv => res(dv));
+ });
};
sidebarAddDocTab = (doc: Doc, where: OpenWhere) => {
@@ -314,14 +335,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
return this._props.addDocTab(doc, where);
};
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
- let ele: Opt<HTMLDivElement> = undefined;
+ let ele: Opt<HTMLDivElement>;
try {
const contents = this._iframe?.contentWindow?.getSelection()?.getRangeAt(0).cloneContents();
if (contents) {
ele = document.createElement('div');
ele.append(contents);
}
- } catch (e) {}
+ } catch (e) {
+ /* empty */
+ }
const visibleAnchor = this._getAnchor(this._savedAnnotations, true);
const anchor =
visibleAnchor ??
@@ -330,7 +353,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
y: NumCast(this.layoutDoc._layout_scrollTop),
annotationOn: this.Document,
});
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: pinProps?.pinData ? true : false, pannable: true } }, this.Document);
+ PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: !!pinProps?.pinData, pannable: true } }, this.Document);
anchor.text = ele?.textContent ?? '';
anchor.text_html = ele?.innerHTML ?? this._selectionText;
addAsAnnotation && this.addDocumentWrapper(anchor);
@@ -356,7 +379,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
this._textAnnotationCreator = undefined;
this.DocumentView?.()?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
- const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
+ const mainContBounds = ClientUtils.GetScreenTransform(this._mainCont.current!);
const scale = (this._props.NativeDimScaling?.() || 1) * mainContBounds.scale;
const sel = this._iframe.contentWindow.getSelection();
if (sel) {
@@ -387,7 +410,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
e?.stopPropagation();
setTimeout(() => {
// if menu comes up right away, the down event can still be active causing a menu item to be selected
- this.specificContextMenu(undefined as any);
+ this.specificContextMenu();
this.DocumentView?.().onContextMenu(undefined, theclick[0], theclick[1]);
});
}
@@ -462,6 +485,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const sheets = document.head.appendChild(style);
return (sheets as any).sheet;
}
+ return undefined;
}
addWebStyleSheetRule(sheet: any, selector: any, css: any, selectorPrefix = '.') {
const propText =
@@ -476,7 +500,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
_iframetimeout: any = undefined;
@observable _warning = 0;
@action
- iframeLoaded = (e: any) => {
+ iframeLoaded = () => {
const iframe = this._iframe;
if (this._initialScroll !== undefined) {
this.setScrollPos(this._initialScroll);
@@ -491,7 +515,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
runInAction(() => this._warning++);
href = undefined;
}
- let requrlraw = decodeURIComponent(href?.replace(Utils.prepend('') + '/corsProxy/', '') ?? this._url.toString());
+ let requrlraw = decodeURIComponent(href?.replace(ClientUtils.prepend('') + '/corsProxy/', '') ?? this._url.toString());
if (requrlraw !== this._url.toString()) {
if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) {
const matches = requrlraw.match(/[^a-zA-z]q=[^&]*/g);
@@ -544,16 +568,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
'click',
undoBatch(
action((e: MouseEvent) => {
- let href = '';
+ let eleHref = '';
for (let ele = e.target as any; ele; ele = ele.parentElement) {
- href = (typeof ele.href === 'string' ? ele.href : ele.href?.baseVal) || ele.parentElement?.href || href;
+ eleHref = (typeof ele.href === 'string' ? ele.href : ele.href?.baseVal) || ele.parentElement?.href || eleHref;
}
const origin = this.webField?.origin;
- if (href && origin) {
+ if (eleHref && origin) {
const batch = UndoManager.StartBatch('webclick');
e.stopPropagation();
setTimeout(() => {
- this.setData(href.replace(Utils.prepend(''), origin));
+ this.setData(eleHref.replace(ClientUtils.prepend(''), origin));
batch.end();
});
if (this._outerRef.current) {
@@ -632,12 +656,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
this._scrollHeight = 0;
if (this._webUrl === this._url) {
this._webUrl = curUrl;
- setTimeout(action(() => (this._webUrl = this._url)));
+ setTimeout(
+ action(() => {
+ this._webUrl = this._url;
+ })
+ );
} else {
this._webUrl = this._url;
}
return true;
}
+ return undefined;
});
return false;
};
@@ -655,12 +684,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
this._scrollHeight = 0;
if (this._webUrl === this._url) {
this._webUrl = curUrl;
- setTimeout(action(() => (this._webUrl = this._url)));
+ setTimeout(
+ action(() => {
+ this._webUrl = this._url;
+ })
+ );
} else {
this._webUrl = this._url;
}
return true;
}
+ return undefined;
});
return false;
};
@@ -692,13 +726,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
const html = dataTransfer.getData('text/html');
const uri = dataTransfer.getData('text/uri-list');
const url = uri || html || this._url || '';
- const newurl = url.startsWith(window.location.origin) ? url.replace(window.location.origin, this._url?.match(/http[s]?:\/\/[^\/]*/)?.[0] || '') : url;
+ const newurl = url.startsWith(window.location.origin) ? url.replace(window.location.origin, this._url?.match(/http[s]?:\/\/[^/]*/)?.[0] || '') : url;
this.setData(newurl);
e.stopPropagation();
};
@action
- setData = (data: Field | Promise<RefField | undefined>) => {
+ setData = (data: FieldType | Promise<RefField | undefined>) => {
if (!(typeof data === 'string') && !(data instanceof WebField)) return false;
if (Field.toString(data) === this._url) return false;
this._scrollHeight = 0;
@@ -715,19 +749,31 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
e.stopPropagation();
};
- specificContextMenu = (e: React.MouseEvent | PointerEvent): void => {
+ specificContextMenu = (): void => {
const cm = ContextMenu.Instance;
const funcs: ContextMenuProps[] = [];
if (!cm.findByDescription('Options...')) {
!Doc.noviceMode &&
- funcs.push({ description: (this.layoutDoc[this.fieldKey + '_useCors'] ? "Don't Use" : 'Use') + ' Cors', event: () => (this.layoutDoc[this.fieldKey + '_useCors'] = !this.layoutDoc[this.fieldKey + '_useCors']), icon: 'snowflake' });
+ funcs.push({
+ description: (this.layoutDoc[this.fieldKey + '_useCors'] ? "Don't Use" : 'Use') + ' Cors',
+ event: () => {
+ this.layoutDoc[this.fieldKey + '_useCors'] = !this.layoutDoc[this.fieldKey + '_useCors'];
+ },
+ icon: 'snowflake',
+ });
funcs.push({
description: (this.dataDoc[this.fieldKey + '_allowScripts'] ? 'Prevent' : 'Allow') + ' Scripts',
event: () => {
this.dataDoc[this.fieldKey + '_allowScripts'] = !this.dataDoc[this.fieldKey + '_allowScripts'];
if (this._iframe) {
- runInAction(() => (this._hackHide = true));
- setTimeout(action(() => (this._hackHide = false)));
+ runInAction(() => {
+ this._hackHide = true;
+ });
+ setTimeout(
+ action(() => {
+ this._hackHide = false;
+ })
+ );
}
},
icon: 'snowflake',
@@ -765,7 +811,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
setupMoveUpEvents(
this,
e,
- action(e => {
+ action(() => {
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
return true;
}),
@@ -789,7 +835,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
@observable lighttext = false;
@computed get urlContent() {
- if (this.ScreenToLocalBoxXf().Scale > 25) return <div></div>;
+ if (this.ScreenToLocalBoxXf().Scale > 25) return <div />;
setTimeout(
action(() => {
if (this._initialScroll === undefined && !this._webPageHasBeenRendered) {
@@ -811,19 +857,23 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
})}
contentEditable
onPointerDown={this.webClipDown}
+ // eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: field.html }}
/>
);
}
if (field instanceof WebField) {
- const url = this.layoutDoc[this.fieldKey + '_useCors'] ? Utils.CorsProxy(this._webUrl) : this._webUrl;
+ const url = this.layoutDoc[this.fieldKey + '_useCors'] ? ClientUtils.CorsProxy(this._webUrl) : this._webUrl;
const scripts = this.dataDoc[this.fieldKey + '_allowScripts'] || this._webUrl.includes('wikipedia.org') || this._webUrl.includes('google.com') || this._webUrl.startsWith('https://bing');
- //if (!scripts) console.log('No scripts for: ' + url);
+ // if (!scripts) console.log('No scripts for: ' + url);
return (
<iframe
+ title="web iframe"
key={this._warning}
className="webBox-iframe"
- ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))}
+ ref={action((r: HTMLIFrameElement | null) => {
+ this._iframe = r;
+ })}
style={{ pointerEvents: SnappingManager.IsResizing ? 'none' : undefined }}
src={url}
onLoad={this.iframeLoaded}
@@ -834,12 +884,24 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
/>
);
}
- return <iframe className="webBox-iframe" ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))} src={'https://crossorigin.me/https://cs.brown.edu'} />;
+ return (
+ <iframe
+ title="web frame"
+ className="webBox-iframe"
+ ref={action((r: HTMLIFrameElement | null) => {
+ this._iframe = r;
+ })}
+ src="https://crossorigin.me/https://cs.brown.edu"
+ />
+ );
}
- addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => {
- this._url && (doc instanceof Doc ? [doc] : doc).forEach(doc => (doc.config_data = new WebField(this._url)));
- return this.addDocument(doc, annotationKey);
+ addDocumentWrapper = (docs: Doc | Doc[], annotationKey?: string) => {
+ this._url &&
+ toList(docs).forEach(doc => {
+ doc.config_data = new WebField(this._url);
+ });
+ return this.addDocument(docs, annotationKey);
};
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
@@ -853,7 +915,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
setupMoveUpEvents(
this,
e,
- action((e, down, delta) => {
+ action((moveEv, down, delta) => {
this._draggingSidebar = true;
const localDelta = this._props
.ScreenToLocalTransform()
@@ -871,7 +933,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
}
return false;
}),
- action((e, movement, isClick) => {
+ action((upEv, movement, isClick) => {
this._draggingSidebar = false;
!isClick && batch.end();
}),
@@ -892,14 +954,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
}}
onPointerDown={e => this.sidebarBtnDown(e, true)}>
- <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
</div>
);
}
@observable _previewNativeWidth: Opt<number> = undefined;
@observable _previewWidth: Opt<number> = undefined;
toggleSidebar = action((preview: boolean = false) => {
- var nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
+ let nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
if (!nativeWidth) {
const defaultNativeWidth = NumCast(this.Document.nativeWidth, this.dataDoc[this.fieldKey] instanceof WebField ? 850 : NumCast(this.Document._width));
Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || defaultNativeWidth);
@@ -938,7 +1000,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
};
_innerCollectionView: CollectionFreeFormView | undefined;
zoomScaling = () => this._innerCollectionView?.zoomScaling() ?? 1;
- setInnerContent = (component: ViewBoxInterface) => (this._innerCollectionView = component as CollectionFreeFormView);
+ setInnerContent = (component: ViewBoxInterface<any>) => {
+ this._innerCollectionView = component as CollectionFreeFormView;
+ };
@computed get content() {
const interactive = this._props.isContentActive() && this._props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None;
@@ -969,24 +1033,26 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
{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`} />
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ <Annotation {...this._props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} containerDataDoc={this.dataDoc} annoDoc={anno} key={`${anno[Id]}-annotation`} />
))}
</div>
);
}
@computed get SidebarShown() {
- return this._showSidebar || this.layoutDoc._layout_showSidebar ? true : false;
+ return !!(this._showSidebar || this.layoutDoc._layout_showSidebar);
}
renderAnnotations = (childFilters: () => string[]) => (
<CollectionFreeFormView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={this.setInnerContent}
NativeWidth={returnZero}
NativeHeight={returnZero}
originTopLeft={false}
- isAnnotationOverlayScrollable={true}
+ isAnnotationOverlayScrollable
renderDepth={this._props.renderDepth + 1}
- isAnnotationOverlay={true}
+ isAnnotationOverlay
fieldKey={this.annotationKey}
setPreviewCursor={this.setPreviewCursor}
PanelWidth={this.panelWidth}
@@ -1029,7 +1095,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
}}
// when active, block wheel events from propagating since they're handled by the iframe
onWheel={this.onZoomWheel}
- onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
+ onScroll={() => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
onPointerDown={this.onMarqueeDown}>
<div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight > this._props.PanelHeight() && this._scrollHeight) || '100%', pointerEvents }}>
{this.content}
@@ -1045,7 +1111,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
return (
<div className="webBox-ui" onPointerDown={e => e.stopPropagation()} style={{ display: this._props.isContentActive() ? 'flex' : 'none' }}>
<div className="webBox-overlayCont" onPointerDown={e => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
- <button className="webBox-overlayButton" title={'search'} />
+ <button type="button" className="webBox-overlayButton" title="search" />
<input
className="webBox-searchBar"
placeholder="Search"
@@ -1056,13 +1122,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
e.stopPropagation();
}}
/>
- <button className="webBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
+ <button type="button" className="webBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
<FontAwesomeIcon icon="search" size="sm" />
</button>
</div>
<button
+ type="button"
className="webBox-overlayButton"
- title={'search'}
+ title="search"
onClick={action(() => {
this._searching = !this._searching;
this.search('', false, true);
@@ -1075,14 +1142,18 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
</div>
);
}
- searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value);
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
+ searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._searchString = e.currentTarget.value;
+ };
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => {
+ this._setPreviewCursor = func;
+ };
panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth;
panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
- transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
- opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed ? [] : [Utils.OpaqueBackgroundFilter])];
+ transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter];
+ opaqueFilter = () => [...this._props.childFilters(), ClientUtils.noDragDocsFilter, ...(SnappingManager.CanEmbed ? [] : [ClientUtils.OpaqueBackgroundFilter])];
childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
if (doc instanceof Doc && property === StyleProp.PointerEvents) {
if (this.inlineTextAnnotations.includes(doc)) return 'none';
@@ -1149,6 +1220,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
<div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this._props.PanelWidth()}%` }}>
<SidebarAnnos
ref={this._sidebarRef}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
fieldKey={this.fieldKey + '_' + this._urlHash}
@@ -1169,6 +1241,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
);
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function urlHash(url: string) {
return stringHash(url);
});
+
+Docs.Prototypes.TemplateMap.set(DocumentType.WEB, {
+ layout: { view: WebBox, dataField: 'data' },
+ options: { acl: '', _height: 300, _layout_fitWidth: true, _layout_nativeDimEditable: true, _layout_reflowVertical: true, waitForDoubleClickToClick: 'always', systemIcon: 'BsGlobe' },
+});
diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js
index 914adb404..6fb8f4957 100644
--- a/src/client/views/nodes/WebBoxRenderer.js
+++ b/src/client/views/nodes/WebBoxRenderer.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-undef */
/**
*
* @param {StyleSheetList} styleSheets
@@ -9,15 +10,14 @@ const ForeignHtmlRenderer = function (styleSheets) {
*
* @param {String} binStr
*/
- const binaryStringToBase64 = function (binStr) {
- return new Promise(resolve => {
+ const binaryStringToBase64 = binStr =>
+ new Promise(resolve => {
const reader = new FileReader();
reader.readAsDataURL(binStr);
reader.onloadend = function () {
resolve(reader.result);
};
});
- };
function prepend(extension) {
return window.location.origin + extension;
@@ -30,8 +30,8 @@ const ForeignHtmlRenderer = function (styleSheets) {
* @param {String} url
* @returns {Promise}
*/
- const getResourceAsBase64 = function (webUrl, inurl) {
- return new Promise((resolve, reject) => {
+ const getResourceAsBase64 = (webUrl, inurl) =>
+ new Promise(resolve => {
const xhr = new XMLHttpRequest();
// const url = inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl;
// const url = CorsProxy(inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl);// inurl.startsWith("http") ? CorsProxy(inurl) : inurl;
@@ -67,16 +67,15 @@ const ForeignHtmlRenderer = function (styleSheets) {
xhr.send(null);
});
- };
/**
*
* @param {String[]} urls
* @returns {Promise}
*/
- const getMultipleResourcesAsBase64 = function (webUrl, urls) {
+ const getMultipleResourcesAsBase64 = (webUrl, urls) => {
const promises = [];
- for (let i = 0; i < urls.length; i += 1) {
+ for (let i = 0; webUrl && i < urls.length; i += 1) {
promises.push(getResourceAsBase64(webUrl, urls[i]));
}
return Promise.all(promises);
@@ -130,6 +129,7 @@ const ForeignHtmlRenderer = function (styleSheets) {
const urlsFound = [];
let searchStartIndex = 0;
+ // eslint-disable-next-line no-constant-condition
while (true) {
const url = parseValue(cssRuleStr, searchStartIndex, selector, delimiters);
if (url === null) {
@@ -155,9 +155,6 @@ const ForeignHtmlRenderer = function (styleSheets) {
const getImageUrlsFromFromHtml = function (html) {
return getUrlsFromCssString(html, 'src=', [' ', '>', '\t'], true);
};
- const getSourceUrlsFromFromHtml = function (html) {
- return getUrlsFromCssString(html, 'source=', [' ', '>', '\t'], true);
- };
const escapeRegExp = function (string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
@@ -171,46 +168,45 @@ const ForeignHtmlRenderer = function (styleSheets) {
*
* @returns {Promise<String>}
*/
- const buildSvgDataUri = async function (webUrl, inputContentHtml, width, height, scroll, xoff) {
- return new Promise(async (resolve, reject) => {
- /* !! The problems !!
- * 1. CORS (not really an issue, expect perhaps for images, as this is a general security consideration to begin with)
- * 2. Platform won't wait for external assets to load (fonts, images, etc.)
- */
-
- // copy styles
- let cssStyles = '';
- const urlsFoundInCss = [];
-
- for (let i = 0; i < styleSheets.length; i += 1) {
- try {
- const rules = styleSheets[i].cssRules;
- for (let j = 0; j < rules.length; j += 1) {
- const cssRuleStr = rules[j].cssText;
- urlsFoundInCss.push(...getUrlsFromCssString(cssRuleStr));
- cssStyles += cssRuleStr;
- }
- } catch (e) {
- /* empty */
+ const buildSvgDataUri = (webUrl, inputContentHtml, width, height, scroll, xoff) => {
+ /* !! The problems !!
+ * 1. CORS (not really an issue, expect perhaps for images, as this is a general security consideration to begin with)
+ * 2. Platform won't wait for external assets to load (fonts, images, etc.)
+ */
+
+ // copy styles
+ let cssStyles = '';
+ const urlsFoundInCss = [];
+
+ for (let i = 0; i < styleSheets.length; i += 1) {
+ try {
+ const rules = styleSheets[i].cssRules;
+ for (let j = 0; j < rules.length; j += 1) {
+ const cssRuleStr = rules[j].cssText;
+ urlsFoundInCss.push(...getUrlsFromCssString(cssRuleStr));
+ cssStyles += cssRuleStr;
}
+ } catch (e) {
+ /* empty */
}
+ }
- // const fetchedResourcesFromStylesheets = await getMultipleResourcesAsBase64(webUrl, urlsFoundInCss);
- // for (let i = 0; i < fetchedResourcesFromStylesheets.length; i++) {
- // const r = fetchedResourcesFromStylesheets[i];
- // if (r.resourceUrl) {
- // cssStyles = cssStyles.replace(new RegExp(escapeRegExp(r.resourceUrl), "g"), r.resourceBase64);
- // }
- // }
-
- let contentHtml = inputContentHtml
- .replace(/<source[^>]*>/g, '') // <picture> tags have a <source> which has a srcset field of image refs. instead of converting each, just use the default <img> of the picture
- .replace(/noscript/g, 'div')
- .replace(/<div class="mediaset"><\/div>/g, '') // when scripting isn't available (ie, rendering web pages here), <noscript> tags should become <div>'s. But for Brown CS, there's a layout problem if you leave the empty <mediaset> tag
- .replace(/<link[^>]*>/g, '') // don't need to keep any linked style sheets because we've already processed all style sheets above
- .replace(/srcset="([^ "]*)[^"]*"/g, 'src="$1"'); // instead of converting each item in the srcset to a data url, just convert the first one and use that
- const urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml).filter(url => !url.startsWith('data:'));
- const fetchedResources = webUrl ? await getMultipleResourcesAsBase64(webUrl, urlsFoundInHtml) : [];
+ // const fetchedResourcesFromStylesheets = await getMultipleResourcesAsBase64(webUrl, urlsFoundInCss);
+ // for (let i = 0; i < fetchedResourcesFromStylesheets.length; i++) {
+ // const r = fetchedResourcesFromStylesheets[i];
+ // if (r.resourceUrl) {
+ // cssStyles = cssStyles.replace(new RegExp(escapeRegExp(r.resourceUrl), "g"), r.resourceBase64);
+ // }
+ // }
+
+ let contentHtml = inputContentHtml
+ .replace(/<source[^>]*>/g, '') // <picture> tags have a <source> which has a srcset field of image refs. instead of converting each, just use the default <img> of the picture
+ .replace(/noscript/g, 'div')
+ .replace(/<div class="mediaset"><\/div>/g, '') // when scripting isn't available (ie, rendering web pages here), <noscript> tags should become <div>'s. But for Brown CS, there's a layout problem if you leave the empty <mediaset> tag
+ .replace(/<link[^>]*>/g, '') // don't need to keep any linked style sheets because we've already processed all style sheets above
+ .replace(/srcset="([^ "]*)[^"]*"/g, 'src="$1"'); // instead of converting each item in the srcset to a data url, just convert the first one and use that
+ const urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml).filter(url => !url.startsWith('data:'));
+ return getMultipleResourcesAsBase64(webUrl, urlsFoundInHtml).then(fetchedResources => {
for (let i = 0; i < fetchedResources.length; i += 1) {
const r = fetchedResources[i];
if (r.resourceUrl) {
@@ -243,9 +239,7 @@ const ForeignHtmlRenderer = function (styleSheets) {
</svg>`;
// convert SVG to data-uri
- const dataUri = `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svg)))}`;
-
- resolve(dataUri);
+ return `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svg)))}`;
});
};
@@ -256,18 +250,19 @@ const ForeignHtmlRenderer = function (styleSheets) {
*
* @return {Promise<Image>}
*/
- this.renderToImage = async function (webUrl, html, width, height, scroll, xoff) {
- return new Promise(async (resolve, reject) => {
+ this.renderToImage = (webUrl, html, width, height, scroll, xoff) =>
+ new Promise(resolve => {
const img = new Image();
- console.log(`BUILDING SVG for: ${webUrl}`);
- img.src = await buildSvgDataUri(webUrl, html, width, height, scroll, xoff);
-
img.onload = function () {
console.log(`IMAGE SVG created: ${webUrl}`);
resolve(img);
};
+ console.log(`BUILDING SVG for: ${webUrl}`);
+ buildSvgDataUri(webUrl, html, width, height, scroll, xoff).then(uri => {
+ img.src = uri;
+ return img;
+ });
});
- };
/**
* @param {String} html
@@ -276,20 +271,16 @@ const ForeignHtmlRenderer = function (styleSheets) {
*
* @return {Promise<Image>}
*/
- this.renderToCanvas = async function (webUrl, html, width, height, scroll, xoff, oversample) {
- return new Promise(async (resolve, reject) => {
- const img = await self.renderToImage(webUrl, html, width, height, scroll, xoff);
-
+ this.renderToCanvas = (webUrl, html, width, height, scroll, xoff, oversample) =>
+ self.renderToImage(webUrl, html, width, height, scroll, xoff).then(img => {
const canvas = document.createElement('canvas');
canvas.width = img.width * oversample;
canvas.height = img.height * oversample;
const canvasCtx = canvas.getContext('2d');
canvasCtx.drawImage(img, 0, 0, img.width * oversample, img.height * oversample);
-
- resolve(canvas);
+ return canvas;
});
- };
/**
* @param {String} html
@@ -298,12 +289,10 @@ const ForeignHtmlRenderer = function (styleSheets) {
*
* @return {Promise<String>}
*/
- this.renderToBase64Png = async function (webUrl, html, width, height, scroll, xoff, oversample) {
- return new Promise(async (resolve, reject) => {
- const canvas = await self.renderToCanvas(webUrl, html, width, height, scroll, xoff, oversample);
- resolve(canvas.toDataURL('image/png'));
- });
- };
+ this.renderToBase64Png = (webUrl, html, width, height, scroll, xoff, oversample) =>
+ self
+ .renderToCanvas(webUrl, html, width, height, scroll, xoff, oversample) //
+ .then(canvas => canvas.toDataURL('image/png'));
};
export function CreateImage(webUrl, styleSheets, html, width, height, scroll, xoff = 0, oversample = 1) {
@@ -379,11 +368,11 @@ const ClipboardUtils = new (function () {
.then(result => {
loadFile(result, callback);
})
- .catch(error => {
+ .catch(() => {
callback(null, 'Reading clipboard error.');
});
})
- .catch(error => {
+ .catch(() => {
callback(null, 'Reading clipboard error.');
});
} else {
diff --git a/src/client/views/nodes/audio/AudioWaveform.tsx b/src/client/views/nodes/audio/AudioWaveform.tsx
index 7fd799952..2d1d3d7db 100644
--- a/src/client/views/nodes/audio/AudioWaveform.tsx
+++ b/src/client/views/nodes/audio/AudioWaveform.tsx
@@ -7,8 +7,8 @@ import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
import { Cast } from '../../../../fields/Types';
import { numberRange } from '../../../../Utils';
+import { Colors } from '../../global/globalEnums';
import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { Colors } from './../../global/globalEnums';
import './AudioWaveform.scss';
import { WaveCanvas } from './WaveCanvas';
@@ -62,7 +62,7 @@ export class AudioWaveform extends ObservableReactComponent<AudioWaveformProps>
return NumListCast(this._props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)]);
}
- audioBucketField = (start: number, end: number, zoomFactor: number) => this._props.fieldKey + '_audioBuckets/' + '/' + start.toFixed(2).replace('.', '_') + '/' + end.toFixed(2).replace('.', '_') + '/' + zoomFactor * 10;
+ audioBucketField = (start: number, end: number, zoomFactor: number) => this._props.fieldKey + '_audioBuckets//' + start.toFixed(2).replace('.', '_') + '/' + end.toFixed(2).replace('.', '_') + '/' + zoomFactor * 10;
componentWillUnmount() {
this._disposer?.();
@@ -72,7 +72,7 @@ export class AudioWaveform extends ObservableReactComponent<AudioWaveformProps>
this._disposer = reaction(
() => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this._props.zoomFactor }),
({ clipStart, clipEnd, fieldKey, zoomFactor }) => {
- if (!this._props.layoutDoc[fieldKey] && this._props.layoutDoc.layout_fieldKey != 'layout_icon') {
+ if (!this._props.layoutDoc[fieldKey] && this._props.layoutDoc.layout_fieldKey !== 'layout_icon') {
// setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time.
const waveform = Cast(this._props.layoutDoc[this.audioBucketField(0, this._props.rawDuration, 1)], listSpec('number'));
this._props.layoutDoc[fieldKey] = waveform && new List<number>(waveform.slice((clipStart / this._props.rawDuration) * waveform.length, (clipEnd / this._props.rawDuration) * waveform.length));
@@ -109,7 +109,7 @@ export class AudioWaveform extends ObservableReactComponent<AudioWaveformProps>
progressColor={Colors.MEDIUM_BLUE_ALT}
progress={this._props.progress ?? 1}
barWidth={200 / this.audioBuckets.length}
- //gradientColors={this._props.gradientColors}
+ // gradientColors={this._props.gradientColors}
peaks={this.audioBuckets}
width={(this._props.PanelWidth ?? 0) * window.devicePixelRatio}
height={this.waveHeight * window.devicePixelRatio}
diff --git a/src/client/views/nodes/audio/WaveCanvas.tsx b/src/client/views/nodes/audio/WaveCanvas.tsx
index d3f5669a2..eacda2d42 100644
--- a/src/client/views/nodes/audio/WaveCanvas.tsx
+++ b/src/client/views/nodes/audio/WaveCanvas.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
import React from 'react';
interface WaveCanvasProps {
@@ -14,7 +15,7 @@ interface WaveCanvasProps {
export class WaveCanvas extends React.Component<WaveCanvasProps> {
// If the first value of peaks is negative, addToIndices will be 1
- posPeaks = (peaks: number[], addToIndices: number) => peaks.filter((_, index) => (index + addToIndices) % 2 == 0);
+ posPeaks = (peaks: number[], addToIndices: number) => peaks.filter((_, index) => (index + addToIndices) % 2 === 0);
drawBars = (waveCanvasCtx: CanvasRenderingContext2D, width: number, halfH: number, peaks: number[]) => {
// Bar wave draws the bottom only as a reflection of the top,
@@ -47,6 +48,7 @@ export class WaveCanvas extends React.Component<WaveCanvasProps> {
// A half-pixel offset makes lines crisp
const $ = 0.5 / this.props.pixelRatio;
+ // eslint-disable-next-line no-bitwise
const length = ~~(allPeaks.length / 2); // ~~ is Math.floor for positive numbers.
const scale = width / length;
@@ -55,14 +57,14 @@ export class WaveCanvas extends React.Component<WaveCanvasProps> {
waveCanvasCtx.beginPath();
waveCanvasCtx.moveTo($, halfH);
- for (var i = 0; i < length; i++) {
- var h = Math.round((allPeaks[2 * i] / absmax) * halfH);
+ for (let i = 0; i < length; i++) {
+ const h = Math.round((allPeaks[2 * i] / absmax) * halfH);
waveCanvasCtx.lineTo(i * scale + $, halfH - h);
}
// Draw the bottom edge going backwards, to make a single closed hull to fill.
- for (var i = length - 1; i >= 0; i--) {
- var h = Math.round((allPeaks[2 * i + 1] / absmax) * halfH);
+ for (let i = length - 1; i >= 0; i--) {
+ const h = Math.round((allPeaks[2 * i + 1] / absmax) * halfH);
waveCanvasCtx.lineTo(i * scale + $, halfH - h);
}
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index 748c3322e..bd66941c3 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -1,12 +1,14 @@
-import { Calendar, EventClickArg, EventSourceInput } from '@fullcalendar/core';
+import { Calendar, EventSourceInput } from '@fullcalendar/core';
import dayGridPlugin from '@fullcalendar/daygrid';
import multiMonthPlugin from '@fullcalendar/multimonth';
import { makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { dateRangeStrToDates } from '../../../../Utils';
+import { dateRangeStrToDates } from '../../../../ClientUtils';
import { Doc } from '../../../../fields/Doc';
import { StrCast } from '../../../../fields/Types';
+import { DocumentType } from '../../../documents/DocumentTypes';
+import { Docs } from '../../../documents/Documents';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { FieldView, FieldViewProps } from '../FieldView';
@@ -57,12 +59,13 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
docBackgroundColor(type: string): string {
// TODO: Return a different color based on the event type
+ console.log(type);
return 'blue';
}
get calendarEvents(): EventSourceInput | undefined {
if (this.childDocs.length === 0) return undefined;
- return this.childDocs.map((doc, idx) => {
+ return this.childDocs.map(doc => {
const docTitle = StrCast(doc.title);
const docDateRange = StrCast(doc.date_range);
const [startDate, endDate] = dateRangeStrToDates(docDateRange);
@@ -85,7 +88,7 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
}
- handleEventClick = (arg: EventClickArg) => {
+ handleEventClick = (/* arg: EventClickArg */) => {
// TODO: open popover with event description, option to open CalendarManager and change event date, delete event, etc.
};
@@ -113,8 +116,12 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
render() {
return (
<div className="calendar-box-conatiner">
- <div id="calendar-box-v1"></div>
+ <div id="calendar-box-v1" />
</div>
);
}
}
+Docs.Prototypes.TemplateMap.set(DocumentType.CALENDAR, {
+ layout: { view: CalendarBox, dataField: 'data' },
+ options: { acl: '' },
+});
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index a72ed1813..3ec49fa27 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -1,60 +1,11 @@
import { TextSelection } from 'prosemirror-state';
import * as ReactDOM from 'react-dom/client';
-import { Doc } from '../../../../fields/Doc';
-import { DocServer } from '../../../DocServer';
import * as React from 'react';
import { IReactionDisposer, computed, reaction } from 'mobx';
+import { Doc } from '../../../../fields/Doc';
+import { DocServer } from '../../../DocServer';
import { NumCast } from '../../../../fields/Types';
-// creates an inline comment in a note when '>>' is typed.
-// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
-// the comment can be toggled on/off with the '<-' text anchor.
-export class DashDocCommentView {
- dom: HTMLDivElement; // container for label and value
- root: any;
- node: any;
-
- constructor(node: any, view: any, getPos: any) {
- this.node = node;
- this.dom = document.createElement('div');
- this.dom.style.width = node.attrs.width;
- this.dom.style.height = node.attrs.height;
- this.dom.style.fontWeight = 'bold';
- this.dom.style.position = 'relative';
- this.dom.style.display = 'inline-block';
- this.dom.onkeypress = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onkeydown = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onkeyup = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onmousedown = function (e: any) {
- e.stopPropagation();
- };
-
- this.root = ReactDOM.createRoot(this.dom);
- this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />);
- (this as any).dom = this.dom;
- }
-
- setHeight = (hgt: number) => {
- !this.node.attrs.reflow && DocServer.GetRefField(this.node.attrs.docId).then(doc => doc instanceof Doc && (this.dom.style.height = hgt + ''));
- };
-
- destroy() {
- this.root.unmount();
- }
- deselectNode() {
- this.dom.classList.remove('ProseMirror-selectednode');
- }
- selectNode() {
- this.dom.classList.add('ProseMirror-selectednode');
- }
-}
-
interface IDashDocCommentViewInternal {
docId: string;
view: any;
@@ -65,9 +16,6 @@ interface IDashDocCommentViewInternal {
export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> {
_reactionDisposer: IReactionDisposer | undefined;
- @computed get _dashDoc() {
- return DocServer.GetRefField(this.props.docId);
- }
constructor(props: any) {
super(props);
this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this);
@@ -77,58 +25,62 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
}
componentDidMount(): void {
this._reactionDisposer?.();
- this._dashDoc.then(
- doc =>
- doc instanceof Doc &&
- (this._reactionDisposer = reaction(
+ this._dashDoc.then(doc => {
+ if (doc instanceof Doc) {
+ this._reactionDisposer = reaction(
() => NumCast((doc as Doc)._height),
hgt => this.props.setHeight(hgt),
- {
- fireImmediately: true,
- }
- ))
- );
+ { fireImmediately: true }
+ );
+ }
+ });
}
componentWillUnmount(): void {
this._reactionDisposer?.();
}
- onPointerLeaveCollapsed(e: any) {
+ @computed get _dashDoc() {
+ return DocServer.GetRefField(this.props.docId);
+ }
+
+ onPointerLeaveCollapsed = (e: any) => {
this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
e.preventDefault();
e.stopPropagation();
- }
+ };
- onPointerEnterCollapsed(e: any) {
+ onPointerEnterCollapsed = (e: any) => {
this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
e.preventDefault();
e.stopPropagation();
- }
+ };
- onPointerUpCollapsed(e: any) {
+ onPointerUpCollapsed = (e: any) => {
const target = this.targetNode();
if (target) {
const expand = target.hidden;
- const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
+ const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: !target.node.attrs.hidden });
this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
setTimeout(() => {
expand && this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
try {
this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1))));
- } catch (e) {}
+ } catch (err) {
+ /* empty */
+ }
}, 0);
}
e.stopPropagation();
- }
+ };
- onPointerDownCollapsed(e: any) {
+ onPointerDownCollapsed = (e: any) => {
e.stopPropagation();
- }
+ };
targetNode = () => {
// search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
- const state = this.props.view.state;
+ const { state } = this.props.view;
for (let i = this.props.getPos() + 1; i < state.doc.content.size; i++) {
const m = state.doc.nodeAt(i);
if (m && m.type === state.schema.nodes.dashDoc && m.attrs.docId === this.props.docId) {
@@ -141,7 +93,9 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
setTimeout(() => {
try {
this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2)));
- } catch (e) {}
+ } catch (err) {
+ /* empty */
+ }
}, 0);
return undefined;
};
@@ -154,7 +108,60 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
onPointerLeave={this.onPointerLeaveCollapsed}
onPointerEnter={this.onPointerEnterCollapsed}
onPointerUp={this.onPointerUpCollapsed}
- onPointerDown={this.onPointerDownCollapsed}></span>
+ onPointerDown={this.onPointerDownCollapsed}
+ />
);
}
}
+
+// creates an inline comment in a note when '>>' is typed.
+// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
+// the comment can be toggled on/off with the '<-' text anchor.
+export class DashDocCommentView {
+ dom: HTMLDivElement; // container for label and value
+ root: any;
+ node: any;
+
+ constructor(node: any, view: any, getPos: any) {
+ this.node = node;
+ this.dom = document.createElement('div');
+ this.dom.style.width = node.attrs.width;
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.fontWeight = 'bold';
+ this.dom.style.position = 'relative';
+ this.dom.style.display = 'inline-block';
+ this.dom.onkeypress = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeydown = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
+
+ this.root = ReactDOM.createRoot(this.dom);
+ this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />);
+ (this as any).dom = this.dom;
+ }
+
+ setHeight = (hgt: number) => {
+ !this.node.attrs.reflow &&
+ DocServer.GetRefField(this.node.attrs.docId).then(doc => {
+ doc instanceof Doc && (this.dom.style.height = hgt + '');
+ });
+ };
+
+ destroy() {
+ this.root.unmount();
+ }
+ deselectNode() {
+ this.dom.classList.remove('ProseMirror-selectednode');
+ }
+ selectNode() {
+ this.dom.classList.add('ProseMirror-selectednode');
+ }
+}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 7335c9286..93371685d 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -1,77 +1,23 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { NodeSelection } from 'prosemirror-state';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
+import { ClientUtils, returnFalse } from '../../../../ClientUtils';
import { Doc } from '../../../../fields/Doc';
import { Height, Width } from '../../../../fields/DocSymbols';
import { NumCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, Utils } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
-import { Docs, DocUtils } from '../../../documents/Documents';
+import { Docs } from '../../../documents/Documents';
+import { DocUtils } from '../../../documents/DocUtils';
import { Transform } from '../../../util/Transform';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { DocumentView } from '../DocumentView';
-import { FocusViewOptions } from '../FieldView';
+import { FocusViewOptions } from '../FocusViewOptions';
import { FormattedTextBox } from './FormattedTextBox';
-var horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side.
-export class DashDocView {
- dom: HTMLSpanElement; // container for label and value
- root: any;
-
- constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
- this.dom = document.createElement('span');
- this.dom.style.position = 'relative';
- this.dom.style.textIndent = '0';
- this.dom.style.width = (+node.attrs.width.toString().replace('px', '') + horizPadding).toString();
- this.dom.style.height = node.attrs.height;
- this.dom.style.display = node.attrs.hidden ? 'none' : 'inline-block';
- (this.dom.style as any).float = node.attrs.float;
- this.dom.onkeypress = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onkeydown = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onkeyup = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onmousedown = function (e: any) {
- e.stopPropagation();
- };
-
- this.root = ReactDOM.createRoot(this.dom);
- this.root.render(
- <DashDocViewInternal
- docId={node.attrs.docId}
- embedding={node.attrs.embedding}
- width={node.attrs.width}
- height={node.attrs.height}
- hidden={node.attrs.hidden}
- fieldKey={node.attrs.fieldKey}
- tbox={tbox}
- view={view}
- node={node}
- getPos={getPos}
- />
- );
- }
- destroy() {
- setTimeout(() => {
- try {
- this.root.unmount();
- } catch {}
- });
- }
- deselectNode() {
- this.dom.style.backgroundColor = '';
- }
- selectNode() {
- this.dom.style.backgroundColor = 'rgb(141, 182, 247)';
- }
-}
-
+const horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side.
interface IDashDocViewInternal {
docId: string;
embedding: string;
@@ -84,6 +30,7 @@ interface IDashDocViewInternal {
node: any;
getPos: any;
}
+
@observer
export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewInternal> {
_spanRef = React.createRef<HTMLDivElement>();
@@ -157,7 +104,7 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
getDocTransform = () => {
if (!this._spanRef.current) return Transform.Identity();
- const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current);
+ const { scale, translateX, translateY } = ClientUtils.GetScreenTransform(this._spanRef.current);
return new Transform(-translateX, -translateY, 1).scale(1 / scale);
};
outerFocus = (target: Doc, options: FocusViewOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target
@@ -226,3 +173,61 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
);
}
}
+
+export class DashDocView {
+ dom: HTMLSpanElement; // container for label and value
+ root: any;
+
+ constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+ this.dom = document.createElement('span');
+ this.dom.style.position = 'relative';
+ this.dom.style.textIndent = '0';
+ this.dom.style.width = (+node.attrs.width.toString().replace('px', '') + horizPadding).toString();
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.display = node.attrs.hidden ? 'none' : 'inline-block';
+ (this.dom.style as any).float = node.attrs.float;
+ this.dom.onkeypress = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeydown = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
+
+ this.root = ReactDOM.createRoot(this.dom);
+ this.root.render(
+ <DashDocViewInternal
+ docId={node.attrs.docId}
+ embedding={node.attrs.embedding}
+ width={node.attrs.width}
+ height={node.attrs.height}
+ hidden={node.attrs.hidden}
+ fieldKey={node.attrs.fieldKey}
+ tbox={tbox}
+ view={view}
+ node={node}
+ getPos={getPos}
+ />
+ );
+ }
+ destroy() {
+ setTimeout(() => {
+ try {
+ this.root.unmount();
+ } catch {
+ /* empty */
+ }
+ });
+ }
+ deselectNode() {
+ this.dom.style.backgroundColor = '';
+ }
+ selectNode() {
+ this.dom.style.backgroundColor = 'rgb(141, 182, 247)';
+ }
+}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 17b8b53e7..9903d0e8a 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,15 +1,20 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/control-has-associated-label */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
+import { NodeSelection } from 'prosemirror-state';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
+import { returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
import { Doc, DocListCast, Field } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
import { Cast, DocCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { Transform } from '../../../util/Transform';
@@ -18,96 +23,73 @@ import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell';
import { FilterPanel } from '../../FilterPanel';
import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { OpenWhere } from '../DocumentView';
+import { OpenWhere } from '../OpenWhere';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';
-import { DocData } from '../../../../fields/DocSymbols';
-import { NodeSelection } from 'prosemirror-state';
-export class DashFieldView {
- dom: HTMLDivElement; // container for label and value
- root: any;
- node: any;
- tbox: FormattedTextBox;
- getpos: any;
- @observable _nodeSelected = false;
- NodeSelected = () => this._nodeSelected;
+@observer
+export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
+ // eslint-disable-next-line no-use-before-define
+ static Instance: DashFieldViewMenu;
+ static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
+ static toggleFieldHide: () => void = emptyFunction;
+ static toggleValueHide: () => void = emptyFunction;
+ constructor(props: any) {
+ super(props);
+ DashFieldViewMenu.Instance = this;
+ }
- unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
- constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
- makeObservable(this);
- const self = this;
- this.node = node;
- this.tbox = tbox;
- this.getpos = getPos;
- this.dom = document.createElement('div');
- this.dom.style.width = node.attrs.width;
- this.dom.style.height = node.attrs.height;
- this.dom.style.position = 'relative';
- this.dom.style.display = 'inline-block';
- const tBox = this.tbox;
- this.dom.onkeypress = function (e: KeyboardEvent) {
- e.stopPropagation();
- };
- this.dom.onkeydown = function (e: KeyboardEvent) {
- e.stopPropagation();
- if (e.key === 'Tab') {
- e.preventDefault();
- const editor = tbox.EditorView;
- if (editor) {
- const state = editor.state;
- for (var i = self.getpos() + 1; i < state.doc.content.size; i++) {
- if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) {
- editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i))));
- return;
- }
- }
- // tBox.setFocus(state.selection.to);
- }
- }
- };
- this.dom.onkeyup = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onmousedown = function (e: any) {
- e.stopPropagation();
- };
+ showFields = (e: React.MouseEvent) => {
+ DashFieldViewMenu.createFieldView(e);
+ DashFieldViewMenu.Instance.fadeOut(true);
+ };
+ toggleFieldHide = () => {
+ DashFieldViewMenu.toggleFieldHide();
+ DashFieldViewMenu.Instance.fadeOut(true);
+ };
+ toggleValueHide = () => {
+ DashFieldViewMenu.toggleValueHide();
+ DashFieldViewMenu.Instance.fadeOut(true);
+ };
- this.root = ReactDOM.createRoot(this.dom);
- this.root.render(
- <DashFieldViewInternal
- node={node}
- unclickable={this.unclickable}
- getPos={getPos}
- fieldKey={node.attrs.fieldKey}
- docId={node.attrs.docId}
- width={node.attrs.width}
- height={node.attrs.height}
- hideKey={node.attrs.hideKey}
- hideValue={node.attrs.hideValue}
- editable={node.attrs.editable}
- nodeSelected={this.NodeSelected}
- tbox={tbox}
- />
+ @observable _fieldKey = '';
+
+ @action
+ public show = (x: number, y: number, fieldKey: string) => {
+ this._fieldKey = fieldKey;
+ this.jumpTo(x, y, true);
+ const hideMenu = () => {
+ this.fadeOut(true);
+ document.removeEventListener('pointerdown', hideMenu, true);
+ };
+ document.addEventListener('pointerdown', hideMenu, true);
+ };
+ render() {
+ return this.getElement(
+ <>
+ <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}>
+ <button type="button" className="antimodeMenu-button" onPointerDown={this.showFields}>
+ <FontAwesomeIcon icon="eye" size="sm" />
+ </button>
+ </Tooltip>
+ {this._fieldKey.startsWith('#') ? null : (
+ <Tooltip key="key" title={<div className="dash-tooltip">Toggle view of field key</div>}>
+ <button type="button" className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
+ <FontAwesomeIcon icon="bullseye" size="sm" />
+ </button>
+ </Tooltip>
+ )}
+ {this._fieldKey.startsWith('#') ? null : (
+ <Tooltip key="val" title={<div className="dash-tooltip">Toggle view of field value</div>}>
+ <button type="button" className="antimodeMenu-button" onPointerDown={this.toggleValueHide}>
+ <FontAwesomeIcon icon="hashtag" size="sm" />
+ </button>
+ </Tooltip>
+ )}
+ </>
);
}
- destroy() {
- setTimeout(() => {
- try {
- this.root.unmount();
- } catch {}
- });
- }
- deselectNode() {
- runInAction(() => (this._nodeSelected = false));
- this.dom.classList.remove('ProseMirror-selectednode');
- }
- selectNode() {
- setTimeout(() => runInAction(() => (this._nodeSelected = true)), 100);
- this.dom.classList.add('ProseMirror-selectednode');
- }
}
-
interface IDashFieldViewInternal {
fieldKey: string;
docId: string;
@@ -137,7 +119,9 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
makeObservable(this);
this._fieldKey = this._props.fieldKey;
this._textBoxDoc = this._props.tbox.Document;
- const setDoc = action((doc: Doc) => (this._dashDoc = doc));
+ const setDoc = action((doc: Doc) => {
+ this._dashDoc = doc;
+ });
if (this._props.docId) {
DocServer.GetRefField(this._props.docId).then(dashDoc => dashDoc instanceof Doc && setDoc(dashDoc));
@@ -171,7 +155,11 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
// set the display of the field's value (checkbox for booleans, span of text for strings)
@computed get fieldValueContent() {
return !this._dashDoc ? null : (
- <div onClick={action(e => (this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}>
+ <div
+ onClick={action(() => {
+ this._expanded = !this._props.editable ? !this._expanded : true;
+ })}
+ style={{ fontSize: 'smaller', width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}>
<SchemaTableCell
Document={this._dashDoc}
col={0}
@@ -188,20 +176,20 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
getFinfo={emptyFunction}
setColumnValues={returnFalse}
setSelectedColumnValues={returnFalse}
- allowCRs={true}
+ allowCRs
oneLine={!this._expanded && !this._props.nodeSelected()}
finishEdit={this.finishEdit}
transform={Transform.Identity}
menuTarget={null}
- autoFocus={true}
+ autoFocus
rootSelected={this._props.tbox._props.rootSelected}
/>
</div>
);
}
- createPivotForField = (e: React.MouseEvent) => {
- let container = this._props.tbox.DocumentView?.().containerViewPath?.().lastElement();
+ createPivotForField = () => {
+ const container = this._props.tbox.DocumentView?.().containerViewPath?.().lastElement();
if (container) {
const embedding = Doc.MakeEmbedding(container.Document);
embedding._type_collection = CollectionViewType.Time;
@@ -220,7 +208,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
toggleFieldHide = undoable(
action(() => {
const editor = this._props.tbox.EditorView!;
- editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideKey: this._props.node.attrs.hideValue ? false : !this._props.node.attrs.hideKey ? true : false }));
+ editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideKey: this._props.node.attrs.hideValue ? false : !this._props.node.attrs.hideKey }));
}),
'hideKey'
);
@@ -228,7 +216,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
toggleValueHide = undoable(
action(() => {
const editor = this._props.tbox.EditorView!;
- editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideValue: this._props.node.attrs.hideKey ? false : !this._props.node.attrs.hideValue ? true : false }));
+ editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideValue: this._props.node.attrs.hideKey ? false : !this._props.node.attrs.hideValue }));
}),
'hideValue'
);
@@ -244,11 +232,11 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
// clicking on the label creates a pivot view collection of all documents
// in the same collection. The pivot field is the fieldKey of this label
onPointerDownLabelSpan = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, returnFalse, returnFalse, e => {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, moveEv => {
DashFieldViewMenu.createFieldView = this.createPivotForField;
DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide;
DashFieldViewMenu.toggleValueHide = this.toggleValueHide;
- DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16, this._fieldKey);
+ DashFieldViewMenu.Instance.show(moveEv.clientX, moveEv.clientY + 16, this._fieldKey);
const editor = this._props.tbox.EditorView!;
setTimeout(() => editor.dispatch(editor.state.tr.setSelection(new NodeSelection(editor.state.doc.resolve(this._props.getPos())))), 100);
});
@@ -278,7 +266,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
}}>
{this._hideKey ? null : (
<span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
- {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : this._dashDoc?.title + ':') + this._fieldKey}
+ {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : (this._dashDoc?.title ?? '') + ':') + this._fieldKey}
</span>
)}
{this._props.fieldKey.startsWith('#') || this._hideValue ? null : this.fieldValueContent}
@@ -294,65 +282,92 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
);
}
}
-@observer
-export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
- static Instance: DashFieldViewMenu;
- static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
- static toggleFieldHide: () => void = emptyFunction;
- static toggleValueHide: () => void = emptyFunction;
- constructor(props: any) {
- super(props);
- DashFieldViewMenu.Instance = this;
- }
-
- showFields = (e: React.MouseEvent) => {
- DashFieldViewMenu.createFieldView(e);
- DashFieldViewMenu.Instance.fadeOut(true);
- };
- toggleFieldHide = (e: React.MouseEvent) => {
- DashFieldViewMenu.toggleFieldHide();
- DashFieldViewMenu.Instance.fadeOut(true);
- };
- toggleValueHide = (e: React.MouseEvent) => {
- DashFieldViewMenu.toggleValueHide();
- DashFieldViewMenu.Instance.fadeOut(true);
- };
-
- @observable _fieldKey = '';
+export class DashFieldView {
+ dom: HTMLDivElement; // container for label and value
+ root: any;
+ node: any;
+ tbox: FormattedTextBox;
+ getpos: any;
+ @observable _nodeSelected = false;
+ NodeSelected = () => this._nodeSelected;
- @action
- public show = (x: number, y: number, fieldKey: string) => {
- this._fieldKey = fieldKey;
- this.jumpTo(x, y, true);
- const hideMenu = () => {
- this.fadeOut(true);
- document.removeEventListener('pointerdown', hideMenu, true);
+ unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
+ constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+ makeObservable(this);
+ this.node = node;
+ this.tbox = tbox;
+ this.getpos = getPos;
+ this.dom = document.createElement('div');
+ this.dom.style.width = node.attrs.width;
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.position = 'relative';
+ this.dom.style.display = 'inline-block';
+ this.dom.onkeypress = function (e: KeyboardEvent) {
+ e.stopPropagation();
};
- document.addEventListener('pointerdown', hideMenu, true);
- };
- render() {
- return this.getElement(
- <>
- <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}>
- <button className="antimodeMenu-button" onPointerDown={this.showFields}>
- <FontAwesomeIcon icon="eye" size="sm" />
- </button>
- </Tooltip>
- {this._fieldKey.startsWith('#') ? null : (
- <Tooltip key="key" title={<div className="dash-tooltip">Toggle view of field key</div>}>
- <button className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
- <FontAwesomeIcon icon="bullseye" size="sm" />
- </button>
- </Tooltip>
- )}
- {this._fieldKey.startsWith('#') ? null : (
- <Tooltip key="val" title={<div className="dash-tooltip">Toggle view of field value</div>}>
- <button className="antimodeMenu-button" onPointerDown={this.toggleValueHide}>
- <FontAwesomeIcon icon="hashtag" size="sm" />
- </button>
- </Tooltip>
- )}
- </>
+ this.dom.onkeydown = (e: KeyboardEvent) => {
+ e.stopPropagation();
+ if (e.key === 'Tab') {
+ e.preventDefault();
+ const editor = tbox.EditorView;
+ if (editor) {
+ const { state } = editor;
+ for (let i = this.getpos() + 1; i < state.doc.content.size; i++) {
+ if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) {
+ editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i))));
+ return;
+ }
+ }
+ }
+ }
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
+
+ this.root = ReactDOM.createRoot(this.dom);
+ this.root.render(
+ <DashFieldViewInternal
+ node={node}
+ unclickable={this.unclickable}
+ getPos={getPos}
+ fieldKey={node.attrs.fieldKey}
+ docId={node.attrs.docId}
+ width={node.attrs.width}
+ height={node.attrs.height}
+ hideKey={node.attrs.hideKey}
+ hideValue={node.attrs.hideValue}
+ editable={node.attrs.editable}
+ nodeSelected={this.NodeSelected}
+ tbox={tbox}
+ />
);
}
+ destroy() {
+ setTimeout(() => {
+ try {
+ this.root.unmount();
+ } catch {
+ /* empty */
+ }
+ });
+ }
+ deselectNode() {
+ runInAction(() => {
+ this._nodeSelected = false;
+ });
+ this.dom.classList.remove('ProseMirror-selectednode');
+ }
+ selectNode() {
+ setTimeout(
+ action(() => {
+ this._nodeSelected = true;
+ }),
+ 100
+ );
+ this.dom.classList.add('ProseMirror-selectednode');
+ }
}
diff --git a/src/client/views/nodes/formattedText/EquationEditor.tsx b/src/client/views/nodes/formattedText/EquationEditor.tsx
index b4102e08e..d9b1a2cf8 100644
--- a/src/client/views/nodes/formattedText/EquationEditor.tsx
+++ b/src/client/views/nodes/formattedText/EquationEditor.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
import React, { Component, createRef } from 'react';
// Import JQuery, required for the functioning of the equation editor
@@ -5,11 +6,9 @@ import $ from 'jquery';
import './EquationEditor.scss';
-// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
window.jQuery = $;
-// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
require('mathquill/build/mathquill');
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index b90653acc..5167c8f2a 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { IReactionDisposer } from 'mobx';
import { observer } from 'mobx-react';
import { TextSelection } from 'prosemirror-state';
@@ -10,44 +11,6 @@ import EquationEditor from './EquationEditor';
import { FormattedTextBox } from './FormattedTextBox';
import { DocData } from '../../../../fields/DocSymbols';
-export class EquationView {
- dom: HTMLDivElement; // container for label and value
- root: any;
- tbox: FormattedTextBox;
- view: any;
- constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
- this.tbox = tbox;
- this.view = view;
- this.dom = document.createElement('div');
- this.dom.style.width = node.attrs.width;
- this.dom.style.height = node.attrs.height;
- this.dom.style.position = 'relative';
- this.dom.style.display = 'inline-block';
- this.dom.onmousedown = function (e: any) {
- e.stopPropagation();
- };
-
- this.root = ReactDOM.createRoot(this.dom);
- this.root.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} getPos={getPos} setEditor={this.setEditor} tbox={tbox} />);
- }
- _editor: EquationEditor | undefined;
- setEditor = (editor?: EquationEditor) => (this._editor = editor);
- destroy() {
- this.root.unmount();
- }
- setSelection() {
- this._editor?.mathField.focus();
- }
- selectNode() {
- this.tbox._applyingChange = this.tbox.fieldKey; // setting focus will make prosemirror lose focus, which will cause it to change its selection to a text selection, which causes this view to get rebuilt but it's no longer node selected, so the equationview won't have focus
- setTimeout(() => {
- this._editor?.mathField.focus();
- setTimeout(() => (this.tbox._applyingChange = ''));
- });
- }
- deselectNode() {}
-}
-
interface IEquationViewInternal {
fieldKey: string;
tbox: FormattedTextBox;
@@ -70,12 +33,12 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
this._textBoxDoc = props.tbox.Document;
}
- componentWillUnmount() {
- this._reactionDisposer?.();
- }
componentDidMount() {
this.props.setEditor(this._ref.current ?? undefined);
}
+ componentWillUnmount() {
+ this._reactionDisposer?.();
+ }
render() {
return (
@@ -100,12 +63,56 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
<EquationEditor
ref={this._ref}
value={StrCast(this._textBoxDoc[DocData][this._fieldKey])}
- onChange={(str: any) => (this._textBoxDoc[DocData][this._fieldKey] = str)}
+ onChange={(str: any) => {
+ this._textBoxDoc[DocData][this._fieldKey] = str;
+ }}
autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
autoOperatorNames="sin cos tan"
- spaceBehavesLikeTab={true}
+ spaceBehavesLikeTab
/>
</div>
);
}
}
+
+export class EquationView {
+ dom: HTMLDivElement; // container for label and value
+ root: any;
+ tbox: FormattedTextBox;
+ view: any;
+ constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+ this.tbox = tbox;
+ this.view = view;
+ this.dom = document.createElement('div');
+ this.dom.style.width = node.attrs.width;
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.position = 'relative';
+ this.dom.style.display = 'inline-block';
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
+
+ this.root = ReactDOM.createRoot(this.dom);
+ this.root.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} getPos={getPos} setEditor={this.setEditor} tbox={tbox} />);
+ }
+ _editor: EquationEditor | undefined;
+ setEditor = (editor?: EquationEditor) => {
+ this._editor = editor;
+ };
+ destroy() {
+ this.root.unmount();
+ }
+ setSelection() {
+ this._editor?.mathField.focus();
+ }
+ selectNode() {
+ this.tbox._applyingChange = this.tbox.fieldKey; // setting focus will make prosemirror lose focus, which will cause it to change its selection to a text selection, which causes this view to get rebuilt but it's no longer node selected, so the equationview won't have focus
+ setTimeout(() => {
+ this._editor?.mathField.focus();
+ setTimeout(() => {
+ this.tbox._applyingChange = '';
+ });
+ });
+ }
+ deselectNode() {}
+}
diff --git a/src/client/views/nodes/formattedText/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx
index b327e5137..4641da2e9 100644
--- a/src/client/views/nodes/formattedText/FootnoteView.tsx
+++ b/src/client/views/nodes/formattedText/FootnoteView.tsx
@@ -2,9 +2,9 @@ import { EditorView } from 'prosemirror-view';
import { EditorState } from 'prosemirror-state';
import { keymap } from 'prosemirror-keymap';
import { baseKeymap, toggleMark } from 'prosemirror-commands';
-import { schema } from './schema_rts';
import { redo, undo } from 'prosemirror-history';
import { StepMap } from 'prosemirror-transform';
+import { schema } from './schema_rts';
export class FootnoteView {
innerView: any;
@@ -100,8 +100,8 @@ export class FootnoteView {
this.innerView.updateState(state);
if (!tr.getMeta('fromOutside')) {
- const outerTr = this.outerView.state.tr,
- offsetMap = StepMap.offset(this.getPos() + 1);
+ const outerTr = this.outerView.state.tr;
+ const offsetMap = StepMap.offset(this.getPos() + 1);
for (const transaction of transactions) {
for (const step of transaction.steps) {
outerTr.step(step.map(offsetMap));
@@ -115,7 +115,7 @@ export class FootnoteView {
if (!node.sameMarkup(this.node)) return false;
this.node = node;
if (this.innerView) {
- const state = this.innerView.state;
+ const { state } = this.innerView;
const start = node.content.findDiffStart(state.doc.content);
if (start !== null) {
let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 38dd2e847..99b4a84fc 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -349,8 +349,9 @@ footnote::before {
touch-action: none;
span {
font-family: inherit;
- background-color: inherit;
- display: contents; // fixes problem where extra space is added around <ol> lists when inside a prosemirror span
+ // background-color: inherit; // intended to allow texts to inherit background from list container, but this prevents css highlights e.,g highlight text from others
+ display: inline; // needs to be inline for search highlighting to appear
+ // display: contents; // BUT needs to be 'contents' to avoid Chrome bug where extra space is added above and <ol> lists when inside a prosemirror span
}
blockquote {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 43010b2ed..321fdbb91 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
@@ -12,8 +13,9 @@ import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transacti
import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import { BsMarkdownFill } from 'react-icons/bs';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, StopEvent } from '../../../../ClientUtils';
import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc';
+import { CreateLinkToActiveAudio, Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
@@ -23,37 +25,39 @@ import { RichTextField } from '../../../../fields/RichTextField';
import { ComputedField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivWidth, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
+import { emptyFunction, numberRange, unimplementedFunction, Utils } from '../../../../Utils';
import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
import { DocServer } from '../../../DocServer';
-import { Docs, DocUtils } from '../../../documents/Documents';
-import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { Docs } from '../../../documents/Documents';
+import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
+import { DocUtils } from '../../../documents/DocUtils';
import { DictationManager } from '../../../util/DictationManager';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
import { MakeTemplate } from '../../../util/DropConverter';
import { LinkManager } from '../../../util/LinkManager';
import { RTFMarkup } from '../../../util/RTFMarkup';
-import { SelectionManager } from '../../../util/SelectionManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
-import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
import { CollectionStackingView } from '../../collections/CollectionStackingView';
import { CollectionTreeView } from '../../collections/CollectionTreeView';
import { ContextMenu } from '../../ContextMenu';
import { ContextMenuProps } from '../../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { Colors } from '../../global/globalEnums';
import { LightboxView } from '../../LightboxView';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
+import { PinDocView, PinProps } from '../../PinFuncs';
import { SidebarAnnos } from '../../SidebarAnnos';
-import { StyleProp } from '../../StyleProvider';
-import { media_state } from '../AudioBox';
-import { DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
-import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
+import { StyleProp } from '../../StyleProp';
+import { styleFromLayoutString } from '../../StyleProvider';
+import { mediaState } from '../AudioBox';
+import { DocumentView } from '../DocumentView';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { FocusViewOptions } from '../FocusViewOptions';
import { LinkInfo } from '../LinkDocPreview';
-import { PinProps, PresBox } from '../trails';
+import { OpenWhere } from '../OpenWhere';
import { DashDocCommentView } from './DashDocCommentView';
import { DashDocView } from './DashDocView';
import { DashFieldView } from './DashFieldView';
@@ -69,16 +73,17 @@ import { schema } from './schema_rts';
import { SummaryView } from './SummaryView';
// import * as applyDevTools from 'prosemirror-dev-tools';
-interface FormattedTextBoxProps extends FieldViewProps {
+export interface FormattedTextBoxProps extends FieldViewProps {
onBlur?: () => void; // callback when text loses focus
autoFocus?: boolean; // whether text should get input focus when created
}
@observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextBoxProps>() implements ViewBoxInterface {
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextBoxProps>() {
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
+ // eslint-disable-next-line no-use-before-define
public static Instance: FormattedTextBox;
public static LiveTextUndo: UndoManager.Batch | undefined;
static _globalHighlightsCache: string = '';
@@ -97,7 +102,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
private _inDrop = false;
private _finishingLink = false;
private _searchIndex = 0;
- private _lastTimedMark: Mark | undefined = undefined;
private _cachedLinks: Doc[] = [];
private _undoTyping?: UndoManager.Batch;
private _disposers: { [name: string]: IReactionDisposer } = {};
@@ -108,10 +112,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
private _keymap: any = undefined;
private _rules: RichTextRules | undefined;
private _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason. This flags the 2nd click as happening on a selection which allows bullet points to toggle
- private _forceDownNode: Node | undefined;
- private _downX = 0;
- private _downY = 0;
- private _downTime = 0;
private _break = true;
public ProseRef?: HTMLDivElement;
public get EditorView() {
@@ -152,10 +152,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins);
}
@computed get _recordingDictation() {
- return this.dataDoc?.mediaState === media_state.Recording;
+ return this.dataDoc?.mediaState === mediaState.Recording;
}
set _recordingDictation(value) {
- !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? media_state.Recording : undefined);
+ !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? mediaState.Recording : undefined);
}
@computed get config() {
this._keymap = buildKeymap(schema, this._props);
@@ -170,8 +170,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
keymap(baseKeymap),
new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }),
new Plugin({
- view(editorView) {
- return new FormattedTextBoxComment(editorView);
+ view(/* editorView */) {
+ return new FormattedTextBoxComment();
},
}),
],
@@ -183,26 +183,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
private gptRes: string = '';
public static PasteOnLoad: ClipboardEvent | undefined;
- 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) {
- return html.indexOf('data-pm-slice') !== -1;
- }
- public static GetHref(html: string): string {
- const parser = new DOMParser();
- const parsedHtml = parser.parseFromString(html, 'text/html');
- if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && (parsedHtml.body.childNodes[0].childNodes[0] as any).href) {
- return (parsedHtml.body.childNodes[0].childNodes[0] as any).href;
- }
- return '';
- }
- public static GetDocFromUrl(url: string) {
- return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId
- }
constructor(props: FormattedTextBoxProps) {
super(props);
@@ -217,13 +199,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
public RemoveLinkFromDoc(linkDoc?: Doc) {
this.unhighlightSearchTerms();
const state = this._editorView?.state;
- const a1 = linkDoc?.link_anchor_1 as Doc;
- const a2 = linkDoc?.link_anchor_2 as Doc;
+ const a1 = DocCast(linkDoc?.link_anchor_1);
+ const a2 = DocCast(linkDoc?.link_anchor_2);
if (state && a1 && a2 && this._editorView) {
this.removeDocument(a1);
this.removeDocument(a2);
- var allFoundLinkAnchors: any[] = [];
- state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any, pos: number, parent: any) => {
+ let allFoundLinkAnchors: any[] = [];
+ state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any /* , pos: number, parent: any */) => {
const foundLinkAnchors = findLinkMark(node.marks)?.attrs.allAnchors.filter((a: any) => a.anchorId === a1[Id] || a.anchorId === a2[Id]) || [];
allFoundLinkAnchors = foundLinkAnchors.length ? foundLinkAnchors : allFoundLinkAnchors;
return true;
@@ -246,14 +228,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
- const rootDoc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : DocCast(this.Document.rootDocument, this.Document);
+ const rootDoc: Doc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : DocCast(this.Document.rootDocument, this.Document);
if (!pinProps && this._editorView?.state.selection.empty) return rootDoc;
const anchor = Docs.Create.ConfigDocument({ title: StrCast(rootDoc.title), annotationOn: rootDoc });
this.addDocument(anchor);
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.Document);
+ PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true } }, this.Document);
return anchor;
};
@@ -261,11 +243,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
setupAnchorMenu = () => {
AnchorMenu.Instance.Status = 'marquee';
- AnchorMenu.Instance.OnClick = (e: PointerEvent) => {
+ AnchorMenu.Instance.OnClick = () => {
!this.layoutDoc.layout_showSidebar && this.toggleSidebar();
setTimeout(() => this._sidebarRef.current?.anchorMenuClick(this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true))); // give time for sidebarRef to be created
};
- AnchorMenu.Instance.OnAudio = (e: PointerEvent) => {
+ AnchorMenu.Instance.OnAudio = () => {
!this.layoutDoc.layout_showSidebar && this.toggleSidebar();
const anchor = this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true, true);
@@ -275,8 +257,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
anchor.followLinkAudio = true;
let stopFunc: any;
const targetData = target[DocData];
- targetData.mediaState = media_state.Recording;
- DocumentViewInternal.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => (stopFunc = stop));
+ targetData.mediaState = mediaState.Recording;
+ DictationManager.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => { stopFunc = stop }); // prettier-ignore
+
const reactionDisposer = reaction(
() => target.mediaState,
dictation => {
@@ -286,12 +269,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
}
);
- target.title = ComputedField.MakeFunction(`self["text_audioAnnotations_text"].lastElement()`);
+ target.title = ComputedField.MakeFunction(`this.text_audioAnnotations_text.lastElement()`);
}
});
};
AnchorMenu.Instance.Highlight = undoable((color: string) => {
- this._editorView?.state && RichTextMenu.Instance?.setHighlight(color);
+ this._editorView?.state && RichTextMenu.Instance?.setFontField(color, 'fontHighlight');
return undefined;
}, 'highlght text');
AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
@@ -305,7 +288,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
e.stopPropagation();
const targetCreator = (annotationOn?: Doc) => {
const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn);
- FormattedTextBox.SetSelectOnLoad(target);
+ Doc.SetSelectOnLoad(target);
return target;
};
@@ -313,7 +296,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
});
const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
this._props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
- let ele: Opt<HTMLDivElement> = undefined;
+ let ele: Opt<HTMLDivElement>;
try {
const contents = window.getSelection()?.getRangeAt(0).cloneContents();
if (contents) {
@@ -321,7 +304,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
ele.append(contents);
}
this._selectionHTML = ele?.innerHTML;
- } catch (e) {}
+ } catch (e) {
+ /* empty */
+ }
};
leafText = (node: Node) => {
@@ -330,13 +315,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const fieldKey = StrCast(node.attrs.fieldKey);
return (
(node.attrs.hideKey ? '' : fieldKey + ':') + //
- (node.attrs.hideValue ? '' : Field.toJavascriptString(refDoc[fieldKey] as Field))
+ (node.attrs.hideValue ? '' : Field.toJavascriptString(refDoc[fieldKey] as FieldType))
);
}
return '';
};
dispatchTransaction = (tx: Transaction) => {
- if (this._editorView && (this._editorView as any).docView) {
+ if (this._editorView) {
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
this.tryUpdateDoc(false);
@@ -344,13 +329,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
tryUpdateDoc = (force: boolean) => {
- if (this._editorView && (this._editorView as any).docView) {
- const state = this._editorView.state;
- const dataDoc = this.dataDoc;
+ if (this._editorView) {
+ const { state } = this._editorView;
+ const { dataDoc } = this;
const newText = state.doc.textBetween(0, state.doc.content.size, ' \n', this.leafText);
const newJson = JSON.stringify(state.toJSON());
const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box
- const templateData = this.Document !== this.layoutDoc ? prevData : undefined; // the default text stored in a layout template
const protoData = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
const layoutData = this.layoutDoc.isTemplateDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text inherited from a prototype
const effectiveAcl = GetEffectiveAcl(dataDoc);
@@ -359,7 +343,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if ([AclEdit, AclAdmin, AclSelfEdit, AclAugment].includes(effectiveAcl)) {
const accumTags = [] as string[];
- state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => {
+ state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any /* , pos: number, parent: any */) => {
if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith('#')) {
accumTags.push(node.attrs.fieldKey);
}
@@ -386,7 +370,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
} else {
// if we've deleted all the text in a note driven by a template, then restore the template data
dataDoc[this.fieldKey] = undefined;
- this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(((layoutData !== prevData ? layoutData : undefined) ?? protoData).Data)));
+ this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(((layoutData !== prevData ? layoutData : undefined) ?? protoData)?.Data)));
ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, text: newText });
unchanged = false;
}
@@ -414,37 +398,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
insertTime = () => {
let linkTime;
let linkAnchor;
- let link;
- LinkManager.Links(this.dataDoc).forEach((l, i) => {
- const anchor = (l.link_anchor_1 as Doc).annotationOn ? (l.link_anchor_1 as Doc) : (l.link_anchor_2 as Doc).annotationOn ? (l.link_anchor_2 as Doc) : undefined;
- if (anchor && (anchor.annotationOn as Doc).mediaState === media_state.Recording) {
+ Doc.Links(this.dataDoc).forEach(l => {
+ const anchor = DocCast(l.link_anchor_1)?.annotationOn ? DocCast(l.link_anchor_1) : DocCast(l.link_anchor_2)?.annotationOn ? DocCast(l.link_anchor_2) : undefined;
+ if (anchor && (anchor.annotationOn as Doc).mediaState === mediaState.Recording) {
linkTime = NumCast(anchor._timecodeToShow /* audioStart */);
linkAnchor = anchor;
- link = l;
}
});
if (this._editorView && linkTime) {
- const state = this._editorView.state;
- const now = Date.now();
- let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) });
- if (!this._break && state.selection.to !== state.selection.from) {
- for (let i = state.selection.from; i <= state.selection.to; i++) {
- const pos = state.doc.resolve(i);
- const um = Array.from(pos.marks()).find(m => m.type === schema.marks.user_mark);
- if (um) {
- mark = um;
- break;
- }
- }
- }
-
- const path = (this._editorView.state.selection.$from as any).path;
- if (linkAnchor && path[path.length - 3].type !== this._editorView.state.schema.nodes.code_block) {
+ const { state } = this._editorView;
+ const { path } = state.selection.$from as any;
+ if (linkAnchor && path[path.length - 3].type !== state.schema.nodes.code_block) {
const time = linkTime + Date.now() / 1000 - this._recordingStart / 1000;
this._break = false;
- const from = state.selection.from;
- const value = this._editorView.state.schema.nodes.audiotag.create({ timeCode: time, audioId: linkAnchor[Id] });
- const replaced = this._editorView.state.tr.insert(from - 1, value);
+ const { from } = state.selection;
+ const value = state.schema.nodes.audiotag.create({ timeCode: time, audioId: linkAnchor[Id] });
+ const replaced = state.tr.insert(from - 1, value);
this._editorView.dispatch(replaced.setSelection(new TextSelection(replaced.doc.resolve(from + 1))));
}
}
@@ -452,7 +421,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
autoLink = () => {
const newAutoLinks = new Set<Doc>();
- const oldAutoLinks = LinkManager.Links(this.Document).filter(
+ const oldAutoLinks = Doc.Links(this.Document).filter(
link =>
((!Doc.isTemplateForField(this.Document) &&
(!Doc.isTemplateForField(DocCast(link.link_anchor_1)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_1), this.Document)) &&
@@ -461,17 +430,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
link.link_relationship === LinkManager.AutoKeywords
); // prettier-ignore
if (this._editorView?.state.doc.textContent) {
- const f = this._editorView.state.selection.from;
-
- const t = this._editorView.state.selection.to;
- var tr = this._editorView.state.tr as any;
- const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor;
- tr = tr.removeMark(0, tr.doc.content.size, autoAnch);
- Doc.MyPublishedDocs.filter(term => term.title).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
- tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
+ let { tr } = this._editorView.state;
+ const { from, to } = this._editorView.state.selection;
+ const { autoLinkAnchor } = this._editorView.state.schema.marks;
+ tr = tr.removeMark(0, tr.doc.content.size, autoLinkAnchor);
+ Doc.MyPublishedDocs.filter(term => term.title).forEach(term => {
+ tr = this.hyperlinkTerm(tr, term, newAutoLinks);
+ });
+ tr = tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)));
this._editorView?.dispatch(tr);
}
- oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(LinkManager.Instance.deleteLink);
+ oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(doc => Doc.DeleteLink?.(doc));
};
updateTitle = () => {
@@ -504,11 +473,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
* function of a freeform view that is driven by the text box's text. The include directive will copy the code of the published
* document into the code being evaluated.
*/
- hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
+ hyperlinkTerm = (trIn: any, target: Doc, newAutoLinks: Set<Doc>) => {
+ let tr = trIn;
const editorView = this._editorView;
- if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.Document)) {
- const autoLinkTerm = Field.toString(target.title as Field).replace(/^@/, '');
- var alink: Doc | undefined;
+ if (editorView && !Doc.AreProtosEqual(target, this.Document)) {
+ const autoLinkTerm = Field.toString(target.title as FieldType).replace(/^@/, '');
+ let alink: Doc | undefined;
this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => {
if (
!sel.$anchor.pos ||
@@ -520,11 +490,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
) {
const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
tr = tr.addMark(sel.from, sel.to, splitter);
- tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
+ tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number /* , parent: any */) => {
if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
alink =
alink ??
- (LinkManager.Links(this.Document).find(
+ (Doc.Links(this.Document).find(
link =>
Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), this.Document) && //
Doc.AreProtosEqual(Cast(link.link_anchor_2, Doc, null), target)
@@ -551,12 +521,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return true;
};
highlightSearchTerms = (terms: string[], backward: boolean) => {
- if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
- const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
- const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
- const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
- const length = res[0].length;
- let tr = this._editorView.state.tr;
+ const { _editorView } = this;
+ if (_editorView && terms.some(t => t)) {
+ const { state } = _editorView;
+ let { tr } = state;
+ const mark = state.schema.mark(state.schema.marks.search_highlight);
+ const activeMark = state.schema.mark(state.schema.marks.search_highlight, { selected: true });
+ const res = terms.filter(t => t).map(term => this.findInNode(_editorView, state.doc, term));
+ const { length } = res[0];
const flattened: TextSelection[] = [];
res.map(r => r.map(h => flattened.push(h)));
this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
@@ -571,23 +543,27 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
const lastSel = Math.min(flattened.length - 1, this._searchIndex);
- flattened.forEach((h: TextSelection, ind: number) => (tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)));
- flattened[lastSel] && this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
+ flattened.forEach((h: TextSelection, ind: number) => {
+ tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark);
+ });
+ flattened[lastSel] && _editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
}
};
unhighlightSearchTerms = () => {
- if (window.screen.width < 600) null;
- else if (this._editorView && (this._editorView as any).docView) {
- const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
- const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
- const end = this._editorView.state.doc.nodeSize - 2;
- this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
+ if (this._editorView) {
+ const { state } = this._editorView;
+ if (state) {
+ const mark = state.schema.mark(state.schema.marks.search_highlight);
+ const activeMark = state.schema.mark(state.schema.marks.search_highlight, { selected: true });
+ const end = state.doc.nodeSize - 2;
+ this._editorView.dispatch(state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
+ }
}
};
adoptAnnotation = (start: number, end: number, mark: Mark) => {
const view = this._editorView!;
- const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail });
+ const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: ClientUtils.CurrentUserEmail() });
view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
};
protected createDropTarget = (ele: HTMLDivElement) => {
@@ -631,7 +607,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
float: 'unset',
});
if (!de.embedKey && ![dropActionType.embed, dropActionType.copy].includes(dropAction ?? dropActionType.move)) {
- added = dragData.removeDocument?.(draggedDoc) ? true : false;
+ added = !!dragData.removeDocument?.(draggedDoc);
} else {
added = true;
}
@@ -643,9 +619,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._inDrop = true;
const pos = view.posAtCoords({ left: de.x, top: de.y })?.pos;
pos && view.dispatch(view.state.tr.insert(pos, node));
- added = pos ? true : false; // pos will be null if you don't drop onto an actual text location
- } catch (e) {
- console.log('Drop failed', e);
+ added = !!pos; // pos will be null if you don't drop onto an actual text location
+ } catch (err) {
+ console.log('Drop failed', err);
added = false;
} finally {
this._inDrop = false;
@@ -677,29 +653,28 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
offset += (context.content as any).content[i].nodeSize;
}
- return null;
- } else {
- return null;
}
+ return null;
}
- //Recursively finds matches within a given node
+ // Recursively finds matches within a given node
findInNode(pm: EditorView, node: Node, find: string) {
let ret: TextSelection[] = [];
if (node.isTextblock) {
- let index = 0,
- foundAt;
+ let index = 0;
+ let foundAt;
const ep = this.getNodeEndpoints(pm.state.doc, node);
const regexp = new RegExp(find, 'i');
if (regexp) {
- var blockOffset = 0;
- for (var i = 0; i < node.childCount; i++) {
- var textContent = '';
+ let blockOffset = 0;
+ for (let i = 0; i < node.childCount; i++) {
+ let textContent = '';
while (i < node.childCount && node.child(i).type === pm.state.schema.nodes.text) {
textContent += node.child(i).textContent;
i++;
}
+ // eslint-disable-next-line no-cond-assign
while (ep && (foundAt = textContent.slice(index).search(regexp)) > -1) {
const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + 1), pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + find.length + 1));
ret.push(sel);
@@ -710,14 +685,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
}
} else {
- node.content.forEach((child, i) => (ret = ret.concat(this.findInNode(pm, child, find))));
+ node.content.forEach(child => {
+ ret = ret.concat(this.findInNode(pm, child, find));
+ });
}
return ret;
}
updateHighlights = (highlights: string[]) => {
if (Array.from(highlights).join('') === FormattedTextBox._globalHighlightsCache) return;
- setTimeout(() => (FormattedTextBox._globalHighlightsCache = Array.from(highlights).join('')));
+ setTimeout(() => {
+ FormattedTextBox._globalHighlightsCache = Array.from(highlights).join('');
+ });
clearStyleSheetRules(FormattedTextBox._userStyleSheet);
if (!highlights.includes('Audio Tags')) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'audiotag', { display: 'none' }, '');
@@ -726,7 +705,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' });
}
if (highlights.includes('My Text')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace(/\./g, '').replace(/@/g, ''), { background: 'moccasin' });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace(/\./g, '').replace(/@/g, ''), { background: 'moccasin' });
}
if (highlights.includes('Todo Items')) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-todo', { outline: 'black solid 1px' });
@@ -745,21 +724,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-ignore', { 'font-size': '1' });
}
if (highlights.includes('By Recent Minute')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace('.', '').replace('@', ''), { opacity: '0.1' });
const min = Math.round(Date.now() / 1000 / 60);
numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-min-' + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
}
if (highlights.includes('By Recent Hour')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace('.', '').replace('@', ''), { opacity: '0.1' });
const hr = Math.round(Date.now() / 1000 / 60 / 60);
numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
}
+ // eslint-disable-next-line operator-assignment
this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css changes happen outside of react/mobx. so we need to set a flag that will notify anyone interested in layout changes triggered by css changes (eg., CollectionLinkView)
};
@observable _showSidebar = false;
@computed get SidebarShown() {
- return this._showSidebar || this.layoutDoc._layout_showSidebar ? true : false;
+ return !!(this._showSidebar || this.layoutDoc._layout_showSidebar);
}
@action
@@ -781,7 +761,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this,
e,
this.sidebarMove,
- (e, movement, isClick) => !isClick && batch.end(),
+ (moveEv, movement, isClick) => !isClick && batch.end(),
() => {
this.toggleSidebar();
batch.end();
@@ -805,7 +785,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
deleteAnnotation = (anchor: Doc) => {
const batch = UndoManager.StartBatch('delete link');
- LinkManager.Instance.deleteLink(LinkManager.Links(anchor)[0]);
+ Doc.DeleteLink?.(Doc.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));
// AnchorMenu.Instance.fadeOut(true);
@@ -817,7 +797,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
pinToPres = (anchor: Doc) => this._props.pinToPres(anchor, {});
@undoBatch
- makeTargetToggle = (anchor: Doc) => (anchor.followLinkToggle = !anchor.followLinkToggle);
+ makeTargetToggle = (anchor: Doc) => {
+ anchor.followLinkToggle = !anchor.followLinkToggle;
+ };
@undoBatch
showTargetTrail = (anchor: Doc) => {
@@ -833,11 +815,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
- const editor = this._editorView!;
- const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
let target = e.target as any; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span>
while (target && !target.dataset?.targethrefs) target = target.parentElement;
- if (target && !(e.nativeEvent as any).dash) {
+ const editor = this._editorView;
+ if (editor && target && !(e.nativeEvent as any).dash) {
const hrefs = (target.dataset?.targethrefs as string)
?.trim()
.split(' ')
@@ -847,14 +828,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
.replace(Doc.localServerPath(), '')
.split('?')[0];
const deleteMarkups = undoBatch(() => {
- const sel = editor.state.selection;
- editor.dispatch(editor.state.tr.removeMark(sel.from, sel.to, editor.state.schema.marks.linkAnchor));
+ const { selection } = editor.state;
+ editor.dispatch(editor.state.tr.removeMark(selection.from, selection.to, editor.state.schema.marks.linkAnchor));
});
e.persist();
anchorDoc &&
DocServer.GetRefField(anchorDoc).then(
action(anchor => {
- anchor && SelectionManager.SelectSchemaViewDoc(anchor as Doc);
+ anchor && DocumentView.SelectSchemaDoc(anchor as Doc);
AnchorMenu.Instance.Status = 'annotation';
AnchorMenu.Instance.Delete = !anchor && editor.state.selection.empty ? returnFalse : !anchor ? deleteMarkups : () => this.deleteAnnotation(anchor as Doc);
AnchorMenu.Instance.Pinned = false;
@@ -884,7 +865,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
event: undoBatch(() => {
this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
this.Document.layout_fieldKey = 'layout_meta';
- setTimeout(() => (this.layoutDoc._header_height = this.layoutDoc._layout_autoHeightMargins = 50), 50);
+ setTimeout(() => {
+ this.layoutDoc._header_height = this.layoutDoc._layout_autoHeightMargins = 50;
+ }, 50);
}),
icon: 'eye',
});
@@ -923,19 +906,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
appearanceItems.push({
description: !this.Document._layout_noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle',
- event: () => (this.layoutDoc._layout_noSidebar = !this.layoutDoc._layout_noSidebar),
+ event: () => {
+ this.layoutDoc._layout_noSidebar = !this.layoutDoc._layout_noSidebar;
+ },
icon: !this.Document._layout_noSidebar ? 'eye-slash' : 'eye',
});
appearanceItems.push({
description: (this.Document._layout_enableAltContentUI ? 'Hide' : 'Show') + ' Alt Content UI',
- event: () => (this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI),
+ event: () => {
+ this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI;
+ },
icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye',
});
!Doc.noviceMode && appearanceItems.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
!Doc.noviceMode &&
appearanceItems.push({
description: 'Broadcast Message',
- event: () => DocServer.GetRefField('rtfProto').then(proto => proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text)),
+ event: () =>
+ DocServer.GetRefField('rtfProto').then(proto => {
+ proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text);
+ }),
icon: 'expand-arrows-alt',
});
@@ -957,27 +947,36 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const options = cm.findByDescription('Options...');
const optionItems = options && 'subitems' in options ? options.subitems : [];
- optionItems.push({ description: `Toggle auto update from template`, event: () => (this.dataDoc[this.fieldKey + '_autoUpdate'] = !this.dataDoc[this.fieldKey + '_autoUpdate']), icon: 'star' });
+ optionItems.push({
+ description: `Toggle auto update from template`,
+ event: () => {
+ this.dataDoc[this.fieldKey + '_autoUpdate'] = !this.dataDoc[this.fieldKey + '_autoUpdate'];
+ },
+ icon: 'star',
+ });
optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' });
this._props.renderDepth &&
optionItems.push({
description: !this.Document._createDocOnCR ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns',
- event: () => (this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR),
+ event: () => {
+ this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR;
+ },
icon: !this.Document._createDocOnCR ? 'grip-lines' : 'bars',
});
!Doc.noviceMode &&
optionItems.push({
description: `${this.Document._layout_autoHeight ? 'Lock' : 'Auto'} Height`,
- event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight),
+ event: () => {
+ this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight;
+ },
icon: this.Document._layout_autoHeight ? 'lock' : 'unlock',
});
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
const help = cm.findByDescription('Help...');
const helpItems = help && 'subitems' in help ? help.subitems : [];
- helpItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: <BsMarkdownFill /> });
+ helpItems.push({ description: `show markdown options`, event: () => RTFMarkup.Instance.setOpen(true), icon: <BsMarkdownFill /> });
!help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' });
- this._downX = this._downY = Number.NaN;
};
animateRes = (resIndex: number, newText: string) => {
@@ -990,7 +989,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
askGPT = action(async () => {
try {
- let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
+ const res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
if (!res) {
this.animateRes(0, 'Something went wrong.');
} else if (this._editorView) {
@@ -1016,10 +1015,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
breakupDictation = () => {
if (this._editorView && this._recordingDictation) {
- this.stopDictation(true);
+ this.stopDictation(/* true */);
this._break = true;
- const state = this._editorView.state;
- const to = state.selection.to;
+ const { state } = this._editorView;
+ const { to } = state.selection;
const updated = TextSelection.create(state.doc, to, to);
this._editorView.dispatch(state.tr.setSelection(updated).insert(to, state.schema.nodes.paragraph.create({})));
if (this._recordingDictation) {
@@ -1037,7 +1036,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
});
};
- stopDictation = (abort: boolean) => DictationManager.Controls.stop(!abort);
+ stopDictation = (/* abort: boolean */) => DictationManager.Controls.stop(/* !abort */);
setDictationContent = (value: string) => {
if (this._editorView && this._recordingStart) {
@@ -1046,7 +1045,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const tanch = Docs.Create.ConfigDocument({ title: 'dictation anchor' });
return this.addDocument(tanch) ? tanch : undefined;
};
- const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement();
+ const link = CreateLinkToActiveAudio(textanchorFunc, false).lastElement();
if (link) {
link[DocData].isDictation = true;
const audioanchor = Cast(link.link_anchor_2, Doc, null);
@@ -1065,7 +1064,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
}
}
- const from = this._editorView.state.selection.from;
+ const { from } = this._editorView.state.selection;
this._break = false;
const tr = this._editorView.state.tr.insertText(value);
this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, from, tr.doc.content.size)).scrollIntoView());
@@ -1074,23 +1073,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// TODO: nda -- Look at how link anchors are added
makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string, noPreview?: boolean, addAsAnnotation?: boolean) {
- const state = this._editorView?.state;
- if (state) {
+ const { _editorView } = this;
+ if (_editorView) {
+ const { state } = _editorView;
let selectedText = '';
- const sel = state.selection;
+ const { selection } = state;
const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
- let tr = state.tr.addMark(sel.from, sel.to, splitter);
- if (sel.from !== sel.to) {
+ let tr = state.tr.addMark(selection.from, selection.to, splitter);
+ if (selection.from !== selection.to) {
const anchor =
anchorDoc ??
Docs.Create.ConfigDocument({
//
- title: 'text(' + this._editorView?.state.doc.textBetween(sel.from, sel.to) + ')',
+ title: 'text(' + state.doc.textBetween(selection.from, selection.to) + ')',
annotationOn: this.dataDoc,
});
const href = targetHref ?? Doc.localServerPath(anchor);
if (anchor !== anchorDoc && addAsAnnotation) this.addDocument(anchor);
- tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
+ tr.doc.nodesBetween(selection.from, selection.to, (node: any, pos: number /* , parent: any */) => {
if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
const allAnchors = [{ href, title, anchorId: anchor[Id] }];
allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allAnchors ?? []));
@@ -1100,7 +1100,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
});
this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents
- this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
+ this._editorView!.dispatch(tr.removeMark(selection.from, selection.to, splitter));
this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
anchor.text = selectedText;
anchor.text_html = this._selectionHTML ?? selectedText;
@@ -1121,15 +1121,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
setTimeout(() => this._sidebarRef?.current?.makeDocUnfiltered(doc));
}
- return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ return new Promise<Opt<DocumentView>>(res => {
+ DocumentView.addViewRenderedCb(doc, dv => res(dv));
+ });
};
focus = (textAnchor: Doc, options: FocusViewOptions) => {
const focusSpeed = options.zoomTime ?? 500;
const textAnchorId = textAnchor[Id];
+ let start = 0;
const findAnchorFrag = (frag: Fragment, editor: EditorView) => {
const nodes: Node[] = [];
let hadStart = start !== 0;
frag.forEach((node, index) => {
+ // eslint-disable-next-line no-use-before-define
const examinedNode = findAnchorNode(node, editor);
if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.dashDoc || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) {
nodes.push(examinedNode.node);
@@ -1161,7 +1165,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => textAnchorId === item.href.replace(/.*\/doc\//, '')) ? { node, start: 0 } : undefined;
};
- let start = 0;
this._didScroll = false; // assume we don't need to scroll. if we do, this will get set to true in handleScrollToSelextion when we dispatch the setSelection below
if (this._editorView && textAnchorId) {
const editor = this._editorView;
@@ -1177,13 +1180,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
const escAnchorId = textAnchorId[0] >= '0' && textAnchorId[0] <= '9' ? `\\3${textAnchorId[0]} ${textAnchorId.substr(1)}` : textAnchorId;
addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: 'yellow', transform: 'scale(3)', 'transform-origin': 'left bottom' });
- setTimeout(() => (this._focusSpeed = undefined), this._focusSpeed);
+ setTimeout(() => {
+ this._focusSpeed = undefined;
+ }, this._focusSpeed);
setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000));
return focusSpeed;
- } else {
- return this._props.focus(this.Document, options);
}
+ return this._props.focus(this.Document, options);
}
+ return undefined;
};
// if the scroll height has changed and we're in layout_autoHeight mode, then we need to update the textHeight component of the doc.
@@ -1199,11 +1204,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
componentDidMount() {
!this._props.dontSelectOnLoad && this._props.setContentViewBox?.(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._cachedLinks = Doc.Links(this.Document);
this._disposers.breakupDictation = reaction(() => Doc.RecordingEvent, this.breakupDictation);
this._disposers.layout_autoHeight = reaction(
() => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize, css: this.Document[DocCss] }),
- (autoHeight, fontSize) => setTimeout(() => autoHeight && this.tryUpdateScrollHeight())
+ autoHeight => setTimeout(() => autoHeight && this.tryUpdateScrollHeight())
);
this._disposers.highlights = reaction(
() => Array.from(FormattedTextBox._globalHighlights).slice(),
@@ -1212,21 +1217,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
);
this._disposers.width = reaction(
() => this._props.PanelWidth(),
- width => this.tryUpdateScrollHeight()
+ () => this.tryUpdateScrollHeight()
);
this._disposers.scrollHeight = reaction(
- () => ({ scrollHeight: this.scrollHeight, layout_autoHeight: this.layout_autoHeight, width: NumCast(this.layoutDoc._width) }),
- ({ width, scrollHeight, layout_autoHeight }) => width && layout_autoHeight && this.resetNativeHeight(scrollHeight),
+ () => ({ scrollHeight: this.scrollHeight, layoutAutoHeight: this.layout_autoHeight, width: NumCast(this.layoutDoc._width) }),
+ ({ width, scrollHeight, layoutAutoHeight }) => width && layoutAutoHeight && this.resetNativeHeight(scrollHeight),
{ fireImmediately: true }
);
this._disposers.componentHeights = reaction(
// set the document height when one of the component heights changes and layout_autoHeight is on
- () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layout_autoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }),
- ({ sidebarHeight, textHeight, layout_autoHeight, marginsHeight }) => {
+ () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layoutAutoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }),
+ ({ sidebarHeight, textHeight, layoutAutoHeight, marginsHeight }) => {
const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight));
if (
(!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this._props.isSelected()) && //
- layout_autoHeight &&
+ layoutAutoHeight &&
newHeight &&
newHeight !== this.layoutDoc.height &&
!this._props.dontRegisterView
@@ -1237,7 +1242,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
{ fireImmediately: !Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') }
);
this._disposers.links = reaction(
- () => LinkManager.Links(this.dataDoc), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
+ () => Doc.Links(this.dataDoc), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
newLinks => {
this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l));
this._cachedLinks = newLinks;
@@ -1274,14 +1279,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._disposers.search = reaction(
() => Doc.IsSearchMatch(this.Document),
search => (search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms()),
- { fireImmediately: Doc.IsSearchMatchUnmemoized(this.Document) ? true : false }
+ { fireImmediately: !!Doc.IsSearchMatchUnmemoized(this.Document) }
);
this._disposers.selected = reaction(
() => this._props.rootSelected?.(),
action(selected => {
- //selected && setTimeout(() => this.prepareForTyping());
+ this.prepareForTyping();
if (FormattedTextBox._globalHighlights.has('Bold Text')) {
+ // eslint-disable-next-line operator-assignment
this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed
}
if (RichTextMenu.Instance?.view === this._editorView && !selected) {
@@ -1299,7 +1305,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._disposers.record = reaction(
() => this._recordingDictation,
() => {
- this.stopDictation(true);
+ this.stopDictation(/* true */);
this._recordingDictation && this.recordDictation();
},
{ fireImmediately: true }
@@ -1322,10 +1328,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
clipboardTextSerializer = (slice: Slice): string => {
- let text = '',
- separated = true;
- const from = 0,
- to = slice.content.size;
+ let text = '';
+ let separated = true;
+ const from = 0;
+ const to = slice.content.size;
slice.content.nodesBetween(
from,
to,
@@ -1345,9 +1351,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return text;
};
- handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
+ handlePaste = (view: EditorView, event: Event /* , slice: Slice */): boolean => {
const pdfAnchorId = (event as ClipboardEvent).clipboardData?.getData('dash/pdfAnchor');
- return pdfAnchorId && this.addPdfReference(pdfAnchorId) ? true : false;
+ return !!(pdfAnchorId && this.addPdfReference(pdfAnchorId));
};
addPdfReference = (pdfAnchorId: string) => {
@@ -1378,7 +1384,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return false;
};
- isActiveTab(el: Element | null | undefined) {
+ isActiveTab(elIn: Element | null | undefined) {
+ let el = elIn;
while (el && el !== document.body) {
if (getComputedStyle(el).display === 'none') return false;
el = el.parentNode as any;
@@ -1390,7 +1397,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const self = this;
return new Plugin({
view(newView) {
- runInAction(() => self._props.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
+ runInAction(() => {
+ self._props.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView);
+ });
return new RichTextMenuPlugin({ editorProps: this._props });
},
});
@@ -1415,7 +1424,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
const scrollPos = scrollRef.scrollTop + shift * self.ScreenToLocalBoxXf().Scale;
if (this._focusSpeed !== undefined) {
- setTimeout(() => scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper)));
+ setTimeout(() => {
+ scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper));
+ });
} else {
scrollRef.scrollTo({ top: scrollPos });
}
@@ -1425,25 +1436,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
},
dispatchTransaction: this.dispatchTransaction,
nodeViews: {
- dashComment(node: any, view: any, getPos: any) {
- return new DashDocCommentView(node, view, getPos);
- },
- dashDoc(node: any, view: any, getPos: any) {
- return new DashDocView(node, view, getPos, self);
- },
- dashField(node: any, view: any, getPos: any) {
- return new DashFieldView(node, view, getPos, self);
- },
- equation(node: any, view: any, getPos: any) {
- return new EquationView(node, view, getPos, self);
- },
- summary(node: any, view: any, getPos: any) {
- return new SummaryView(node, view, getPos);
- },
- //ordered_list(node: any, view: any, getPos: any) { return new OrderedListView(); },
- footnote(node: any, view: any, getPos: any) {
- return new FootnoteView(node, view, getPos);
- },
+ dashComment(node: any, view: any, getPos: any) { return new DashDocCommentView(node, view, getPos); }, // prettier-ignore
+ dashDoc(node: any, view: any, getPos: any) { return new DashDocView(node, view, getPos, self); }, // prettier-ignore
+ dashField(node: any, view: any, getPos: any) { return new DashFieldView(node, view, getPos, self); }, // prettier-ignore
+ equation(node: any, view: any, getPos: any) { return new EquationView(node, view, getPos, self); }, // prettier-ignore
+ summary(node: any, view: any, getPos: any) { return new SummaryView(node, view, getPos); }, // prettier-ignore
+ footnote(node: any, view: any, getPos: any) { return new FootnoteView(node, view, getPos); }, // prettier-ignore
},
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
@@ -1451,7 +1449,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const { state, dispatch } = this._editorView;
if (!rtfField) {
const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
- const startupText = Field.toString(dataDoc[fieldKey] as Field);
+ const startupText = Field.toString(dataDoc[fieldKey] as FieldType);
if (startupText) {
dispatch(state.tr.insertText(startupText));
}
@@ -1465,17 +1463,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
(this._editorView as any).TextView = this;
}
- const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()));
+ const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, Doc.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()));
const selLoadChar = FormattedTextBox.SelectOnLoadChar;
if (selectOnLoad) {
- FormattedTextBox.SelectOnLoad = undefined;
+ Doc.SetSelectOnLoad(undefined);
FormattedTextBox.SelectOnLoadChar = '';
}
if (this._editorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
this._props.select(false);
if (selLoadChar) {
const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
- const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
+ const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) });
const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
const tr1 = this._editorView.state.tr.setStoredMarks(storedMarks);
@@ -1483,8 +1481,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const tr = tr2.setStoredMarks(storedMarks);
this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
- } else if (curText && !FormattedTextBox.DontSelectInitialText) {
- selectAll(this._editorView.state, this._editorView?.dispatch);
+ this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
+ } else if (!FormattedTextBox.DontSelectInitialText) {
+ const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) });
+ selectAll(this._editorView.state, (tx: Transaction) => {
+ this._editorView?.dispatch(tx.deleteSelection().addStoredMark(mark));
+ });
+ this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
+ } else {
+ const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
+ const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) });
+ const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
+ const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
+ const { tr } = this._editorView.state;
+ this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))).setStoredMarks(storedMarks));
this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
}
}
@@ -1503,17 +1513,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
prepareForTyping = () => {
- if (!this._editorView) return;
- const docDefaultMarks = [
- ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
- ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []),
- ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []),
- ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily) })] : []),
- ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize) })] : []),
- ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []),
- ...[schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })],
- ];
- this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks));
+ if (this._editorView) {
+ const { text, paragraph } = schema.nodes;
+ const selNode = this._editorView.state.selection.$anchor.node();
+ if (this._editorView.state.selection.from === 1 && this._editorView.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) {
+ const docDefaultMarks = [schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })];
+ this._editorView.state.selection.empty && this._editorView.state.selection.from === 1 && this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks).removeStoredMark(schema.marks.pFontColor));
+ }
+ }
};
componentWillUnmount() {
@@ -1532,8 +1539,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
onPointerDown = (e: React.PointerEvent): void => {
if ((e.nativeEvent as any).handledByInnerReactInstance) {
- return; //e.stopPropagation();
- } else (e.nativeEvent as any).handledByInnerReactInstance = true;
+ return; // e.stopPropagation();
+ }
+ (e.nativeEvent as any).handledByInnerReactInstance = true;
if (this.Document.forceActive) e.stopPropagation();
this.tryUpdateScrollHeight(); // if a doc a fitWidth doc is being viewed in different embedContainer (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view.
@@ -1546,7 +1554,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// const timecode = NumCast(anchor.timecodeToShow, 0);
const audiodoc = anchor.annotationOn as Doc;
const func = () => {
- const docView = DocumentManager.Instance.getDocumentView(audiodoc);
+ const docView = DocumentView.getDocumentView(audiodoc);
if (!docView) {
this._props.addDocTab(audiodoc, OpenWhere.addBottom);
setTimeout(func);
@@ -1559,9 +1567,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (this._recordingDictation && !e.ctrlKey && e.button === 0) {
this.breakupDictation();
}
- this._downX = e.clientX;
- this._downY = e.clientY;
- this._downTime = Date.now();
FormattedTextBoxComment.textBox = this;
if (e.button === 0 && this._props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
@@ -1575,17 +1580,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
e.preventDefault();
}
};
- onSelectEnd = (e: PointerEvent) => {
- document.removeEventListener('pointerup', this.onSelectEnd);
- };
+ onSelectEnd = (): void => document.removeEventListener('pointerup', this.onSelectEnd);
onPointerUp = (e: React.PointerEvent): void => {
const state = this.EditorView?.state;
if (state && this.ProseRef?.children[0].className.includes('-focused') && this._props.isContentActive() && !e.button) {
if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
- let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
- for (let target = e.target as any; target && !target.dataset?.targethrefs; target = target.parentElement);
- while (target && !target.dataset?.targethrefs) target = target.parentElement;
- FormattedTextBoxComment.update(this, this.EditorView!, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
+ let clickTarget = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
+ for (let { target } = e as any; target && !target.dataset?.targethrefs; target = target.parentElement);
+ while (clickTarget && !clickTarget.dataset?.targethrefs) clickTarget = clickTarget.parentElement;
+ FormattedTextBoxComment.update(this, this.EditorView!, undefined, clickTarget?.dataset?.targethrefs, clickTarget?.dataset.linkdoc, clickTarget?.dataset.nopreview === 'true');
}
};
@action
@@ -1613,7 +1616,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
@action
onFocused = (e: React.FocusEvent): void => {
- //applyDevTools.applyDevTools(this._editorView);
+ // applyDevTools.applyDevTools(this._editorView);
this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this._props, this.layoutDoc);
e.stopPropagation();
};
@@ -1624,10 +1627,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
e.stopPropagation();
return;
}
- if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) {
- this._forceDownNode = undefined;
- return;
- }
if (!this._forceUncollapse || (this._editorView!.root as any).getSelection().isCollapsed) {
// this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
@@ -1651,13 +1650,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (this._props.rootSelected?.()) {
// if text box is selected, then it consumes all click events
(e.nativeEvent as any).handledByInnerReactInstance = true;
- this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey);
+ this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, e.shiftKey);
}
this._forceUncollapse = !(this._editorView!.root as any).getSelection().isCollapsed;
- this._forceDownNode = (this._editorView!.state.selection as NodeSelection)?.node;
};
// this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them.
- hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean, downNode: Node | undefined = undefined, selectOrderedList: boolean = false) {
+ hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean, selectOrderedList: boolean = false) {
this._forceUncollapse = false;
clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
const clickPos = this._editorView!.posAtCoords({ left: x, top: y });
@@ -1710,9 +1708,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (!(this.EditorView?.state.selection instanceof NodeSelection)) {
this.autoLink();
if (this._editorView?.state.tr) {
- const tr = stordMarks?.reduce((tr, m) => {
- tr.addStoredMark(m);
- return tr;
+ const tr = stordMarks?.reduce((tr2, m) => {
+ tr2.addStoredMark(m);
+ return tr2;
}, this._editorView.state.tr);
tr && this._editorView.dispatch(tr);
}
@@ -1727,6 +1725,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const match = RTFCast(this.Document[this.fieldKey])?.Text.match(/^(@[a-zA-Z][a-zA-Z_0-9 -]*[a-zA-Z_0-9-]+)/);
if (match) {
this.dataDoc.title_custom = true;
+ // eslint-disable-next-line prefer-destructuring
this.dataDoc.title = match[1]; // this triggers the collectionDockingView to publish this Doc
this.EditorView?.dispatch(this.EditorView?.state.tr.deleteRange(0, match[1].length + 1));
}
@@ -1736,33 +1735,31 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
FormattedTextBox.LiveTextUndo?.end();
FormattedTextBox.LiveTextUndo = undefined;
- const state = this._editorView!.state;
// if the text box blurs and none of its contents are focused(), then pass the blur along
setTimeout(() => !this.ProseRef?.contains(document.activeElement) && this._props.onBlur?.());
};
onKeyDown = (e: React.KeyboardEvent) => {
+ const { _editorView } = this;
+ if (!_editorView) return;
if ((e.altKey || e.ctrlKey) && e.key === 't') {
- e.preventDefault();
- e.stopPropagation();
this._props.setTitleFocus?.();
+ StopEvent(e);
return;
}
- const state = this._editorView!.state;
+ const { state } = _editorView;
if (!state.selection.empty && e.key === '%') {
this._rules!.EnteringStyle = true;
- e.preventDefault();
- e.stopPropagation();
+ StopEvent(e);
return;
}
if (state.selection.empty || !this._rules!.EnteringStyle) {
this._rules!.EnteringStyle = false;
}
- let stopPropagation = true;
- for (var i = state.selection.from; i <= state.selection.to; i++) {
+ for (let i = state.selection.from; i <= state.selection.to; i++) {
const node = state.doc.resolve(i);
- if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.Document))) {
+ if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== ClientUtils.CurrentUserEmail()) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.Document))) {
e.preventDefault();
}
}
@@ -1770,27 +1767,27 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
case 'Escape':
this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
(document.activeElement as any).blur?.();
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
return;
case 'Enter':
this.insertTime();
+ // eslint-disable-next-line no-fallthrough
case 'Tab':
e.preventDefault();
break;
- case 'c':
- this._editorView?.state.selection.empty && (stopPropagation = false);
+ case 'Space':
+ case 'Backspace':
break;
default:
- if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
- case ' ':
- if (e.code !== 'Space' && e.code !== 'Backspace') {
- [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.Document)) &&
- this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
+ if ([AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.Document))) {
+ const modified = Math.floor(Date.now() / 1000);
+ const mark = state.selection.$to.marks().find(m => m.type === schema.marks.user_mark && m.attrs.modified === modified);
+ _editorView.dispatch(state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(mark ?? schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified })));
}
break;
}
- if (stopPropagation) e.stopPropagation();
+ e.stopPropagation();
this.startUndoTypingBatch();
};
ondrop = (e: React.DragEvent) => {
@@ -1810,6 +1807,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
if (children && !SnappingManager.IsDragging) {
+ // eslint-disable-next-line no-use-before-define
const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0;
const toNum = (val: string) => Number(val.replace('px', ''));
const toHgt = (node: Element): number => {
@@ -1821,7 +1819,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const scrollHeight = this.ProseRef && proseHeight;
if (this._props.setHeight && !this._props.suppressSetHeight && scrollHeight && !this._props.dontRegisterView) {
// if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
- const setScrollHeight = () => (this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight);
+ const setScrollHeight = () => {
+ this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight;
+ };
if (this.Document === this.layoutDoc || this.layoutDoc.resolvedDataDoc) {
setScrollHeight();
@@ -1839,7 +1839,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey);
- setSidebarHeight = (height: number) => (this.dataDoc[this.SidebarKey + '_height'] = height);
+ setSidebarHeight = (height: number) => {
+ this.dataDoc[this.SidebarKey + '_height'] = height;
+ };
sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
sidebarScreenToLocal = () =>
this._props
@@ -1857,10 +1859,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
e,
returnFalse,
emptyFunction,
- action(e => (this._recordingDictation = !this._recordingDictation))
+ action(() => {
+ this._recordingDictation = !this._recordingDictation;
+ })
)
}>
- <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: 'red' }} icon={'microphone'} size="sm" />
+ <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: 'red' }} icon="microphone" size="sm" />
</div>
);
}
@@ -1885,15 +1889,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
@computed get sidebarCollection() {
const renderComponent = (tag: string) => {
- const ComponentTag: any = tag === CollectionViewType.Freeform ? CollectionFreeFormView : tag === CollectionViewType.Tree ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView;
+ const ComponentTag: any = tag === CollectionViewType.Tree ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView;
return ComponentTag === CollectionStackingView ? (
<SidebarAnnos
ref={this._sidebarRef}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
Document={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
- usePanelWidth={true}
+ usePanelWidth
nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
showSidebar={this.SidebarShown}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
@@ -1906,8 +1911,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
setHeight={this.setSidebarHeight}
/>
) : (
- <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}>
+ <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => DocumentView.SelectView(this.DocumentView?.()!, false), true)}>
<ComponentTag
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
ref={this._sidebarTagRef as any}
setContentView={emptyFunction}
@@ -1930,8 +1936,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
renderDepth={this._props.renderDepth + 1}
setHeight={this.setSidebarHeight}
fitContentsToBox={this.fitContentsToBox}
- noSidebar={true}
- treeViewHideTitle={true}
+ noSidebar
+ treeViewHideTitle
fieldKey={this.layoutDoc[this.SidebarKey + '_type_collection'] === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`}
/>
</div>
@@ -1969,7 +1975,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}>
<div
className="formattedTextBox-alternateButton"
- onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => this.cycleAlternateText())}
+ onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => this.cycleAlternateText())}
style={{
display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none',
background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
@@ -1990,23 +1996,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
@observable _isHovering = false;
onPassiveWheel = (e: WheelEvent) => {
if (e.clientX > this.ProseRef!.getBoundingClientRect().right) {
- 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();
- }
return;
}
// if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
if (this._props.isContentActive()) {
const scale = this._props.NativeDimScaling?.() || 1;
- const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
- const height = Number(styleFromLayoutString.height?.replace('px', ''));
+ const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
+ const height = Number(styleFromLayout.height?.replace('px', ''));
// prevent default if selected || child is active but this doc isn't scrollable
if (
- !Number.isNaN(height) &&
- (this._scrollRef?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && //
+ !isNaN(height) &&
+ (this._scrollRef?.scrollHeight ?? 0) <= Math.ceil((height || this._props.PanelHeight()) / scale) && //
(this._props.rootSelected?.() || this.isAnyChildContentActive())
) {
e.preventDefault();
@@ -2016,7 +2017,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
_oldWheel: any;
@computed get fontColor() {
- return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontColor);
}
@computed get fontSize() {
return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
@@ -2029,20 +2030,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
render() {
TraceMobx();
- const scale = this._props.NativeDimScaling?.() || 1; // * NumCast(this.layoutDoc._freeform_scale, 1);
+ const scale = this._props.NativeDimScaling?.() || 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.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
- return styleFromLayoutString?.height === '0px' ? null : (
+ const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
+ return styleFromLayout?.height === '0px' ? null : (
<div
className="formattedTextBox"
onPointerEnter={action(() => {
this._isHovering = true;
this.layoutDoc[`_${this._props.fieldKey}_usePath`] && (this.Document.isHovering = true);
})}
- onPointerLeave={action(() => (this.Document.isHovering = this._isHovering = false))}
+ onPointerLeave={action(() => { this.Document.isHovering = this._isHovering = false; })} // prettier-ignore
ref={r => {
this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
this._oldWheel = r;
@@ -2062,7 +2063,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
fontSize: this.fontSize,
fontFamily: this.fontFamily,
fontWeight: this.fontWeight,
- ...styleFromLayoutString,
+ ...styleFromLayout,
}}>
<div
className="formattedTextBox-cont"
@@ -2071,7 +2072,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
cursor: this._props.isContentActive() ? 'text' : undefined,
height: this._props.height ? 'max-content' : undefined,
overflow: this.layout_autoHeight ? 'hidden' : undefined,
- pointerEvents: Doc.ActiveTool === InkTool.None && !this._props.onBrowseClickScript?.() ? undefined : 'none',
+ pointerEvents: Doc.ActiveTool === InkTool.None && !SnappingManager.ExploreMode ? undefined : 'none',
}}
onContextMenu={this.specificContextMenu}
onKeyDown={this.onKeyDown}
@@ -2084,7 +2085,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
onDoubleClick={this.onDoubleClick}>
<div
className="formattedTextBox-outer"
- ref={r => (this._scrollRef = r)}
+ ref={r => {
+ this._scrollRef = r;
+ }}
style={{
width: this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
@@ -2112,3 +2115,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
);
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.RTF, {
+ layout: { view: FormattedTextBox, dataField: 'text' },
+ options: {
+ acl: '',
+ _height: 35,
+ _xMargin: 10,
+ _yMargin: 10,
+ _layout_nativeDimEditable: true,
+ _layout_reflowVertical: true,
+ _layout_reflowHorizontal: true,
+ defaultDoubleClick: 'ignore',
+ systemIcon: 'BsFileEarmarkTextFill',
+ },
+});
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index ce17af6ca..01c46edeb 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -1,15 +1,16 @@
import { Mark, ResolvedPos } from 'prosemirror-model';
-import { EditorState, NodeSelection } from 'prosemirror-state';
+import { EditorState } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
+import { ClientUtils } from '../../../../ClientUtils';
import { Doc } from '../../../../fields/Doc';
import { DocServer } from '../../../DocServer';
-import { LinkDocPreview, LinkInfo } from '../LinkDocPreview';
+import { LinkInfo } from '../LinkDocPreview';
import { FormattedTextBox } from './FormattedTextBox';
import './FormattedTextBoxComment.scss';
import { schema } from './schema_rts';
export function findOtherUserMark(marks: readonly Mark[]): Mark | undefined {
- return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
+ return marks.find(m => m.attrs.userid && m.attrs.userid !== ClientUtils.CurrentUserEmail());
}
export function findUserMark(marks: readonly Mark[]): Mark | undefined {
return marks.find(m => m.attrs.userid);
@@ -18,20 +19,22 @@ export function findLinkMark(marks: readonly Mark[]): Mark | undefined {
return marks.find(m => m.type === schema.marks.autoLinkAnchor || m.type === schema.marks.linkAnchor);
}
export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) {
- let before = 0,
- nbef = rpos.nodeBefore;
+ let before = 0;
+ let nbef = rpos.nodeBefore;
while (nbef && finder(nbef.marks)) {
before += nbef.nodeSize;
+ // eslint-disable-next-line no-param-reassign
rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
rpos && (nbef = rpos.nodeBefore);
}
return before;
}
export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) {
- let after = 0,
- naft = rpos.nodeAfter;
+ let after = 0;
+ let naft = rpos.nodeAfter;
while (naft && finder(naft.marks)) {
after += naft.nodeSize;
+ // eslint-disable-next-line no-param-reassign
rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
rpos && (naft = rpos.nodeAfter);
}
@@ -49,7 +52,7 @@ export class FormattedTextBoxComment {
static userMark: Mark;
static textBox: FormattedTextBox | undefined;
- constructor(view: any) {
+ constructor() {
if (!FormattedTextBoxComment.tooltip) {
const tooltip = (FormattedTextBoxComment.tooltip = document.createElement('div'));
const tooltipText = (FormattedTextBoxComment.tooltipText = document.createElement('div'));
@@ -79,10 +82,10 @@ export class FormattedTextBoxComment {
}
static showCommentbox(view: EditorView, nbef: number) {
- const state = view.state;
+ const { state } = view;
// These are in screen coordinates
- const start = view.coordsAtPos(state.selection.from - nbef),
- end = view.coordsAtPos(state.selection.from - nbef);
+ const start = view.coordsAtPos(state.selection.from - nbef);
+ const end = view.coordsAtPos(state.selection.from - nbef);
// The box in which the tooltip is positioned, to use as base
const box = (document.getElementsByClassName('mainView-container') as any)[0].getBoundingClientRect();
// Find a center-ish x position from the selection endpoints (when crossing lines, end may be more to the left)
@@ -109,14 +112,16 @@ export class FormattedTextBoxComment {
}
static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[], linkDoc?: string, noPreview?: boolean) {
- const state = view.state;
+ const { state } = view;
// this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date
if (state.selection.$from) {
const nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
const noselection = state.selection.$from === state.selection.$to;
let child: any = null;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any /* , pos: number, parent: any */) => {
+ !child && node.marks.length && (child = node);
+ });
const mark = child && findOtherUserMark(child.marks);
if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
FormattedTextBoxComment.saveMarkRegion(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
@@ -131,7 +136,7 @@ export class FormattedTextBoxComment {
if (state.selection.$from && hrefs?.length) {
const nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef;
- //nbef &&
+ // nbef &&
naft &&
LinkInfo.SetLinkInfo({
DocumentView: textBox.DocumentView,
diff --git a/src/client/views/nodes/formattedText/OrderedListView.tsx b/src/client/views/nodes/formattedText/OrderedListView.tsx
index c3595e59b..dbc60f7bf 100644
--- a/src/client/views/nodes/formattedText/OrderedListView.tsx
+++ b/src/client/views/nodes/formattedText/OrderedListView.tsx
@@ -1,8 +1,7 @@
export class OrderedListView {
-
- update(node: any) {
- // if attr's of an ordered_list (e.g., bulletStyle) change,
+ update() {
+ // if attr's of an ordered_list (e.g., bulletStyle) change,
// return false forces the dom node to be recreated which is necessary for the bullet labels to update
- return false;
+ return false;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
index 30da91710..8799964b3 100644
--- a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
+++ b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
@@ -1,18 +1,18 @@
+import { Node, DOMOutputSpec } from 'prosemirror-model';
import clamp from '../../../util/clamp';
import convertToCSSPTValue from '../../../util/convertToCSSPTValue';
import toCSSLineSpacing from '../../../util/toCSSLineSpacing';
-import { Node, DOMOutputSpec } from 'prosemirror-model';
-//import type { NodeSpec } from './Types';
+// import type { NodeSpec } from './Types';
type NodeSpec = {
- attrs?: { [key: string]: any },
- content?: string,
- draggable?: boolean,
- group?: string,
- inline?: boolean,
- name?: string,
- parseDOM?: Array<any>,
- toDOM?: (node: any) => DOMOutputSpec,
+ attrs?: { [key: string]: any };
+ content?: string;
+ draggable?: boolean;
+ group?: string;
+ inline?: boolean;
+ name?: string;
+ parseDOM?: Array<any>;
+ toDOM?: (node: any) => DOMOutputSpec;
};
// This assumes that every 36pt maps to one indent level.
@@ -25,41 +25,18 @@ export const EMPTY_CSS_VALUE = new Set(['', '0%', '0pt', '0px']);
const ALIGN_PATTERN = /(left|right|center|justify)/;
-// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
-// :: NodeSpec A plain paragraph textblock. Represented in the DOM
-// as a `<p>` element.
-export const ParagraphNodeSpec: NodeSpec = {
- attrs: {
- align: { default: null },
- color: { default: null },
- id: { default: null },
- indent: { default: null },
- inset: { default: null },
- lineSpacing: { default: null },
- // TODO: Add UI to let user edit / clear padding.
- paddingBottom: { default: null },
- // TODO: Add UI to let user edit / clear padding.
- paddingTop: { default: null },
- },
- content: 'inline*',
- group: 'block',
- parseDOM: [{ tag: 'p', getAttrs }],
- toDOM,
-};
+function convertMarginLeftToIndentValue(marginLeft: string): number {
+ const ptValue = convertToCSSPTValue(marginLeft);
+ return clamp(MIN_INDENT_LEVEL, Math.floor(ptValue / INDENT_MARGIN_PT_SIZE), MAX_INDENT_LEVEL);
+}
function getAttrs(dom: HTMLElement): Object {
- const {
- lineHeight,
- textAlign,
- marginLeft,
- paddingTop,
- paddingBottom,
- } = dom.style;
+ const { lineHeight, textAlign, marginLeft, paddingTop, paddingBottom } = dom.style;
let align = dom.getAttribute('align') || textAlign || '';
- align = ALIGN_PATTERN.test(align) ? align : "";
+ align = ALIGN_PATTERN.test(align) ? align : '';
- let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || "", 10);
+ let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || '', 10);
if (!indent && marginLeft) {
indent = convertMarginLeftToIndentValue(marginLeft);
@@ -74,15 +51,7 @@ function getAttrs(dom: HTMLElement): Object {
}
function toDOM(node: Node): DOMOutputSpec {
- const {
- align,
- indent,
- inset,
- lineSpacing,
- paddingTop,
- paddingBottom,
- id,
- } = node.attrs;
+ const { align, indent, inset, lineSpacing, paddingTop, paddingBottom, id } = node.attrs;
const attrs: { [key: string]: any } | null = {};
let style = '';
@@ -128,16 +97,29 @@ function toDOM(node: Node): DOMOutputSpec {
return ['p', attrs, 0];
}
+// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
+// :: NodeSpec A plain paragraph textblock. Represented in the DOM
+// as a `<p>` element.
+export const ParagraphNodeSpec: NodeSpec = {
+ attrs: {
+ align: { default: null },
+ color: { default: null },
+ id: { default: null },
+ indent: { default: null },
+ inset: { default: null },
+ lineSpacing: { default: null },
+ // TODO: Add UI to let user edit / clear padding.
+ paddingBottom: { default: null },
+ // TODO: Add UI to let user edit / clear padding.
+ paddingTop: { default: null },
+ },
+ content: 'inline*',
+ group: 'block',
+ parseDOM: [{ tag: 'p', getAttrs }],
+ toDOM,
+};
+
export const toParagraphDOM = toDOM;
export const getParagraphNodeAttrs = getAttrs;
-export function convertMarginLeftToIndentValue(marginLeft: string): number {
- const ptValue = convertToCSSPTValue(marginLeft);
- return clamp(
- MIN_INDENT_LEVEL,
- Math.floor(ptValue / INDENT_MARGIN_PT_SIZE),
- MAX_INDENT_LEVEL
- );
-}
-
-export default ParagraphNodeSpec; \ No newline at end of file
+export default ParagraphNodeSpec;
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 03c902580..7a8b72be0 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -1,29 +1,29 @@
import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from 'prosemirror-commands';
import { redo, undo } from 'prosemirror-history';
import { Schema } from 'prosemirror-model';
-import { splitListItem, wrapInList, sinkListItem, liftListItem } from 'prosemirror-schema-list';
+import { liftListItem, sinkListItem, splitListItem, wrapInList } from 'prosemirror-schema-list';
import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
import { liftTarget } from 'prosemirror-transform';
-import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
-import { GetEffectiveAcl } from '../../../../fields/util';
+import { EditorView } from 'prosemirror-view';
+import { ClientUtils } from '../../../../ClientUtils';
import { Utils } from '../../../../Utils';
+import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols';
+import { GetEffectiveAcl } from '../../../../fields/util';
import { Docs } from '../../../documents/Documents';
import { RTFMarkup } from '../../../util/RTFMarkup';
-import { SelectionManager } from '../../../util/SelectionManager';
-import { OpenWhere } from '../DocumentView';
-import { Doc } from '../../../../fields/Doc';
-import { EditorView } from 'prosemirror-view';
+import { DocumentView } from '../DocumentView';
+import { OpenWhere } from '../OpenWhere';
const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
export type KeyMap = { [key: string]: any };
-export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: string, from?: number, to?: number) => {
+export const updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: string, from?: number, to?: number) => {
let mapStyle = assignedMapStyle;
- tx2.doc.descendants((node: any, offset: any, index: any) => {
+ tx2.doc.descendants((node: any, offset: any /* , index: any */) => {
if ((from === undefined || to === undefined || (from <= offset + node.nodeSize && to >= offset)) && (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item)) {
- const path = (tx2.doc.resolve(offset) as any).path;
- let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty('type') && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
+ const { path } = tx2.doc.resolve(offset) as any;
+ let depth = Array.from(path).reduce((p: number, c: any) => p + (c.type === schema.nodes.ordered_list ? 1 : 0), 0);
if (node.type === schema.nodes.ordered_list) {
if (depth === 0 && !assignedMapStyle) mapStyle = node.attrs.mapStyle;
depth++;
@@ -34,38 +34,44 @@ export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?:
return tx2;
};
-export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKeys?: KeyMap): KeyMap {
+export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMap {
const keys: { [key: string]: any } = {};
function bind(key: string, cmd: any) {
- if (mapKeys) {
- const mapped = mapKeys[key];
- if (mapped === false) return;
- if (mapped) key = mapped;
- }
keys[key] = cmd;
}
+ function onKey(): boolean | undefined {
+ // bcz: this is pretty hacky -- prosemirror doesn't send us the keyboard event, but the 'event' variable is in scope.. so we access it anyway
+ // eslint-disable-next-line no-restricted-globals
+ return props.onKey?.(event, props);
+ }
+
const canEdit = (state: any) => {
- switch (GetEffectiveAcl(props.TemplateDataDocument)) {
+ const permissions = GetEffectiveAcl(props.TemplateDataDocument ?? props.Document[DocData]);
+ switch (permissions) {
case AclAugment:
- const prevNode = state.selection.$cursor.nodeBefore;
- const prevUser = !prevNode ? Doc.CurrentUserEmail : prevNode.marks[prevNode.marks.length - 1].attrs.userid;
- if (prevUser != Doc.CurrentUserEmail) {
- return false;
+ {
+ const prevNode = state.selection.$cursor.nodeBefore;
+ const prevUser = !prevNode ? ClientUtils.CurrentUserEmail() : prevNode.marks.lastElement()?.attrs.userid;
+ if (prevUser !== ClientUtils.CurrentUserEmail()) {
+ return false;
+ }
}
+ break;
+ default:
}
return true;
};
const toggleEditableMark = (mark: any) => (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && toggleMark(mark)(state, dispatch);
- //History commands
+ // History commands
bind('Mod-z', undo);
bind('Shift-Mod-z', redo);
!mac && bind('Mod-y', redo);
- //Commands to modify Mark
+ // Commands to modify Mark
bind('Mod-b', toggleEditableMark(schema.marks.strong));
bind('Mod-B', toggleEditableMark(schema.marks.strong));
@@ -77,15 +83,15 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
bind('Mod-u', toggleEditableMark(schema.marks.underline));
bind('Mod-U', toggleEditableMark(schema.marks.underline));
- //Commands for lists
+ // Commands for lists
bind('Ctrl-i', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any));
- bind('Ctrl-Tab', () => (props.onKey?.(event, props) ? true : true));
- bind('Alt-Tab', () => (props.onKey?.(event, props) ? true : true));
- bind('Meta-Tab', () => (props.onKey?.(event, props) ? true : true));
- bind('Meta-Enter', () => (props.onKey?.(event, props) ? true : true));
+ bind('Ctrl-Tab', () => onKey() || true);
+ bind('Alt-Tab', () => onKey() || true);
+ bind('Meta-Tab', () => onKey() || true);
+ bind('Meta-Enter', () => onKey() || true);
bind('Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- if (props.onKey?.(event, props)) return true;
+ if (onKey()) return true;
if (!canEdit(state)) return true;
const ref = state.selection;
const range = ref.$from.blockRange(ref.$to);
@@ -103,8 +109,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
if (
!wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => {
const tx25 = updateBullets(tx2, schema);
- const ol_node = tx25.doc.nodeAt(range!.start)!;
- const tx3 = tx25.setNodeMarkup(range!.start, ol_node.type, ol_node.attrs, marks);
+ const olNode = tx25.doc.nodeAt(range!.start)!;
+ const tx3 = tx25.setNodeMarkup(range!.start, olNode.type, olNode.attrs, marks);
// when promoting to a list, assume list will format things so don't copy the stored marks.
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
@@ -115,10 +121,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
console.log('bullet promote fail');
}
}
+ return undefined;
});
bind('Shift-Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- if (props.onKey?.(event, props)) return true;
+ if (onKey()) return true;
if (!canEdit(state)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
@@ -132,15 +139,16 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
) {
console.log('bullet demote fail');
}
+ return undefined;
});
- //Command to create a new Tab with a PDF of all the command shortcuts
- bind('Mod-/', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- const newDoc = Docs.Create.PdfDocument(Utils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 });
+ // Command to create a new Tab with a PDF of all the command shortcuts
+ bind('Mod-/', () => {
+ const newDoc = Docs.Create.PdfDocument(ClientUtils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 });
props.addDocTab(newDoc, OpenWhere.addRight);
});
- //Commands to modify BlockType
+ // Commands to modify BlockType
bind('Ctrl->', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any)));
bind('Alt-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any));
bind('Shift-Ctrl-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any));
@@ -156,25 +164,25 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
bind('Shift-Ctrl-' + i, (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any));
}
- //Command to create a horizontal break line
+ // Command to create a horizontal break line
const hr = schema.nodes.horizontal_rule;
bind('Mod-_', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()));
- //Command to unselect all
+ // Command to unselect all
bind('Escape', (state: EditorState, dispatch: (tx: Transaction) => void) => {
dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
(document.activeElement as any).blur?.();
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
});
- bind('Alt-Enter', () => (props.onKey?.(event, props) ? true : true));
- bind('Ctrl-Enter', () => (props.onKey?.(event, props) ? true : true));
+ bind('Alt-Enter', () => onKey() || true);
+ bind('Ctrl-Enter', () => onKey() || true);
bind('Cmd-a', (state: EditorState, dispatch: (tx: Transaction) => void) => {
dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1))));
return true;
});
- bind('Cmd-?', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- RTFMarkup.Instance.open();
+ bind('Cmd-?', () => {
+ RTFMarkup.Instance.setOpen(true);
return true;
});
bind('Cmd-e', (state: EditorState, dispatch: (tx: Transaction) => void) => {
@@ -189,14 +197,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
bind('Cmd-]', (state: EditorState, dispatch: (tx: Transaction) => void) => {
const resolved = state.doc.resolve(state.selection.from) as any;
- const tr = state.tr;
+ const { tr } = state;
if (resolved?.parent.type.name === 'paragraph') {
tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks);
} else {
const node = resolved.nodeAfter;
const sm = state.storedMarks || undefined;
if (node) {
- tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]);
+ tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm || [])]);
}
}
dispatch(tr);
@@ -204,14 +212,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
bind('Cmd-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => {
const resolved = state.doc.resolve(state.selection.from) as any;
- const tr = state.tr;
+ const { tr } = state;
if (resolved?.parent.type.name === 'paragraph') {
tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks);
} else {
const node = resolved.nodeAfter;
const sm = state.storedMarks || undefined;
if (node) {
- tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]);
+ tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm || [])]);
}
}
dispatch(tr);
@@ -219,14 +227,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
bind('Cmd-[', (state: EditorState, dispatch: (tx: Transaction) => void) => {
const resolved = state.doc.resolve(state.selection.from) as any;
- const tr = state.tr;
+ const { tr } = state;
if (resolved?.parent.type.name === 'paragraph') {
tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks);
} else {
const node = resolved.nodeAfter;
const sm = state.storedMarks || undefined;
if (node) {
- tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]);
+ tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm || [])]);
}
}
dispatch(tr);
@@ -236,7 +244,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
bind('Cmd-f', (state: EditorState, dispatch: (tx: Transaction) => void) => {
const content = state.tr.selection.empty ? undefined : state.tr.selection.content().content.textBetween(0, state.tr.selection.content().size + 1);
const newNode = schema.nodes.footnote.create({}, content ? state.schema.text(content) : undefined);
- const tr = state.tr;
+ const { tr } = state;
tr.replaceSelectionWith(newNode); // replace insertion with a footnote.
dispatch(
tr.setSelection(
@@ -258,7 +266,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
// backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => {
- if (props.onKey?.(event, props)) return true;
+ if (onKey()) return true;
if (!canEdit(state)) return true;
if (
@@ -271,7 +279,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
dispatch(updateBullets(tx, schema));
if (view.state.selection.$anchor.node(-1)?.type === schema.nodes.list_item) {
// gets rid of an extra paragraph when joining two list items together.
- joinBackward(view.state, (tx: Transaction) => view.dispatch(tx));
+ joinBackward(view.state, (tx2: Transaction) => view.dispatch(tx2));
}
})
) {
@@ -288,11 +296,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
};
bind('Backspace', backspace);
- //newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
- //command to break line
+ // newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
+ // command to break line
const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => {
- if (props.onKey?.(event, props)) return true;
+ if (onKey()) return true;
if (!canEdit(state)) return true;
const trange = state.selection.$from.blockRange(state.selection.$to);
@@ -337,7 +345,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
!splitBlockKeepMarks(state, (tx3: Transaction) => {
const tonode = tx3.selection.$to.node();
if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) {
- const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks);
+ const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks).setStoredMarks(marks || []);
dispatch(tx4);
}
@@ -356,9 +364,10 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
};
bind('Enter', enter);
- //Command to create a blank space
- bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- if (props.TemplateDataDocument && GetEffectiveAcl(props.TemplateDataDocument) != AclEdit && GetEffectiveAcl(props.TemplateDataDocument) != AclAugment && GetEffectiveAcl(props.TemplateDataDocument) != AclAdmin) return true;
+ // Command to create a blank space
+ bind('Space', () => {
+ const editDoc = props.TemplateDataDocument ?? props.Document[DocData];
+ if (editDoc && ![AclAdmin, AclAugment, AclEdit].includes(GetEffectiveAcl(editDoc))) return true;
return false;
});
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index cecf106a3..a612f3c65 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -3,30 +3,30 @@ import { Tooltip } from '@mui/material';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { lift, wrapIn } from 'prosemirror-commands';
-import { Mark, MarkType, Node as ProsNode, ResolvedPos } from 'prosemirror-model';
+import { Mark, MarkType } from 'prosemirror-model';
import { wrapInList } from 'prosemirror-schema-list';
import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import { Doc } from '../../../../fields/Doc';
import { BoolCast, Cast, StrCast } from '../../../../fields/Types';
-import { numberRange } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
-import { LinkManager } from '../../../util/LinkManager';
-import { SelectionManager } from '../../../util/SelectionManager';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { DocumentView } from '../DocumentView';
import { EquationBox } from '../EquationBox';
import { FieldViewProps } from '../FieldView';
import { FormattedTextBox } from './FormattedTextBox';
import { updateBullets } from './ProsemirrorExampleTransfer';
import './RichTextMenu.scss';
import { schema } from './schema_rts';
+
const { toggleMark } = require('prosemirror-commands');
@observer
export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
+ // eslint-disable-next-line no-use-before-define
static _instance: { menu: RichTextMenu | undefined } = observable({ menu: undefined });
static get Instance() {
return RichTextMenu._instance?.menu;
@@ -115,8 +115,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
_disposer: IReactionDisposer | undefined;
componentDidMount() {
this._disposer = reaction(
- () => SelectionManager.Views.slice(),
- views => this.updateMenu(undefined, undefined, undefined, undefined)
+ () => DocumentView.Selected().slice(),
+ () => this.updateMenu(undefined, undefined, undefined, undefined)
);
}
componentWillUnmount() {
@@ -139,18 +139,19 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setActiveMarkButtons(this.getActiveMarksOnSelection());
const active = this.getActiveFontStylesOnSelection();
- const activeFamilies = active.activeFamilies;
- const activeSizes = active.activeSizes;
- const activeColors = active.activeColors;
- const activeHighlights = active.activeHighlights;
- const refDoc = SelectionManager.Views.lastElement()?.layoutDoc ?? Doc.UserDoc();
- const refField = (pfx => (pfx ? pfx + '_' : ''))(SelectionManager.Views.lastElement()?.LayoutFieldKey);
+ const { activeFamilies } = active;
+ const { activeSizes } = active;
+ const { activeColors } = active;
+ const { activeHighlights } = active;
+ const refDoc = DocumentView.Selected().lastElement()?.layoutDoc ?? Doc.UserDoc();
+ const refField = (pfx => (pfx ? pfx + '_' : ''))(DocumentView.Selected().lastElement()?.LayoutFieldKey);
+ const refVal = (field: string, dflt: string) => StrCast(refDoc[refField + field], StrCast(Doc.UserDoc()[field], dflt));
this._activeListType = this.getActiveListStyle();
this._activeAlignment = this.getActiveAlignment();
- this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(refDoc[refField + 'fontFamily'], 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
- this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(refDoc[refField + 'fontSize'], '10px')) : activeSizes[0];
- this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(refDoc[refField + 'fontColor'], 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
+ this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, refVal('fontFamily', 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
+ this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, refVal('fontSize', '10px')) : activeSizes[0];
+ this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, refVal('fontColor', 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
// update link in current selection
@@ -159,12 +160,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => {
if (mark) {
- const liFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.list_item);
- const liTo = numberRange(state.selection.$to.depth + 1).find(i => state.selection.$to.node(i)?.type === state.schema.nodes.list_item);
- const olFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.ordered_list);
- const nodeOl = (liFirst && liTo && state.selection.$from.node(liFirst) !== state.selection.$to.node(liTo) && olFirst) || (!liFirst && !liTo && olFirst);
- const fromRange = numberRange(state.selection.from).reverse();
- const newPos = nodeOl ? fromRange.find(i => state.doc.nodeAt(i)?.type === state.schema.nodes.ordered_list) ?? state.selection.from : state.selection.from;
+ const newPos = state.selection.$anchor.node()?.type === schema.nodes.ordered_list ? state.selection.from : state.selection.from;
const node = (state.selection as NodeSelection).node ?? (newPos >= 0 ? state.doc.nodeAt(newPos) : undefined);
if (node?.type === schema.nodes.ordered_list || node?.type === schema.nodes.list_item) {
const hasMark = node.marks.some(m => m.type === mark.type);
@@ -172,25 +168,23 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const addAnyway = node.marks.filter(m => m.type === mark.type && Object.keys(m.attrs).some(akey => m.attrs[akey] !== mark.attrs[akey]));
const markup = state.tr.setNodeMarkup(newPos, node.type, node.attrs, hasMark && !addAnyway ? otherMarks : [...otherMarks, mark]);
dispatch(updateBullets(markup, state.schema));
- } else {
- const state = this.view?.state;
- const tr = this.view?.state.tr;
- if (tr && state) {
- if (dontToggle) {
- tr.addMark(state.selection.from, state.selection.to, mark);
- dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise
- } else {
- toggleMark(mark.type, mark.attrs)(state, dispatch);
- }
+ } else if (state) {
+ const { tr } = state;
+ if (dontToggle) {
+ tr.addMark(state.selection.from, state.selection.to, mark);
+ dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise
+ } else {
+ toggleMark(mark.type, mark.attrs)(state, dispatch);
}
}
+ this.updateMenu(this.view, undefined, undefined, this.layoutDoc);
}
};
// finds font sizes and families in selection
getActiveAlignment() {
if (this.view && this.TextView?._props.rootSelected?.()) {
- const path = (this.view.state.selection.$from as any).path;
+ const { path } = this.view.state.selection.$from as any;
for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) {
return path[i].attrs.align || 'left';
@@ -222,27 +216,28 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const activeColors = new Set<string>();
const activeHighlights = new Set<string>();
if (this.view && this.TextView?._props.rootSelected?.()) {
- const state = this.view.state;
+ const { state } = this.view;
const pos = this.view.state.selection.$from;
- var marks: Mark[] = [...(state.storedMarks ?? [])];
+ let marks: Mark[] = [...(state.storedMarks ?? [])];
if (state.storedMarks !== null) {
+ /* empty */
} else if (state.selection.empty) {
for (let i = 0; i <= pos.depth; i++) {
marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
}
} else {
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node /* , pos, parent, index */) => {
node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
});
}
marks.forEach(m => {
- m.type === state.schema.marks.pFontFamily && activeFamilies.add(m.attrs.family);
- m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.color);
+ m.type === state.schema.marks.pFontFamily && activeFamilies.add(m.attrs.fontFamily);
+ m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.fontColor);
m.type === state.schema.marks.pFontSize && activeSizes.add(m.attrs.fontSize);
- m.type === state.schema.marks.marker && activeHighlights.add(String(m.attrs.highlight));
+ m.type === state.schema.marks.pFontHighlight && activeHighlights.add(String(m.attrs.fontHighlight));
});
- } else if (SelectionManager.Views.some(dv => dv.ComponentView instanceof EquationBox)) {
- SelectionManager.Views.forEach(dv => StrCast(dv.Document._text_fontSize) && activeSizes.add(StrCast(dv.Document._text_fontSize)));
+ } else if (DocumentView.Selected().some(dv => dv.ComponentView instanceof EquationBox)) {
+ DocumentView.Selected().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) };
}
@@ -254,26 +249,27 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return found;
}
- //finds all active marks on selection in given group
+ // finds all active marks on selection in given group
getActiveMarksOnSelection() {
if (!this.view || !this.TextView?._props.rootSelected?.()) return [] as MarkType[];
- const state = this.view.state;
- var marks: Mark[] = [...(state.storedMarks ?? [])];
+ const { state } = this.view;
+ let marks: Mark[] = [...(state.storedMarks ?? [])];
const pos = this.view.state.selection.$from;
if (state.storedMarks !== null) {
+ /* empty */
} else if (state.selection.empty) {
for (let i = 0; i <= pos.depth; i++) {
marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
}
} else {
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node /* , pos, parent, index */) => {
node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
});
}
const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
- return markGroup.filter(mark_type => {
- const mark = state.schema.mark(mark_type);
+ return markGroup.filter(markType => {
+ const mark = state.schema.mark(markType);
return mark.isInSet(marks);
});
}
@@ -291,7 +287,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this._superscriptActive = false;
activeMarks.forEach(mark => {
- // prettier-ignore
switch (mark.name) {
case 'noAutoLinkAnchor': this._noLinkActive = true; break;
case 'strong': this._boldActive = true; break;
@@ -300,7 +295,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
case 'strikethrough': this._strikethroughActive = true; break;
case 'subscript': this._subscriptActive = true; break;
case 'superscript': this._superscriptActive = true; break;
- }
+ default:
+ } // prettier-ignore
});
}
@@ -353,53 +349,25 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
};
- setFontSize = (fontSize: string) => {
+ setFontField = (value: string, fontField: 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => {
if (this.view) {
- if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) {
- this.TextView.dataDoc[this.TextView.fieldKey + '_fontSize'] = fontSize;
- this.view.focus();
- } else {
- const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize });
- this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
+ const { text, paragraph } = this.view.state.schema.nodes;
+ const selNode = this.view.state.selection.$anchor.node();
+ if (this.view.state.selection.from === 1 && this.view.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) {
+ this.TextView.dataDoc[this.TextView.fieldKey + `_${fontField}`] = value;
this.view.focus();
}
- } else if (SelectionManager.Views.length) {
- SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontSize'] = fontSize));
- } else Doc.UserDoc().fontSize = fontSize;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
- };
-
- setFontFamily = (family: string) => {
- if (this.view) {
- const fmark = this.view.state.schema.marks.pFontFamily.create({ family });
+ const attrs: { [key: string]: string } = {};
+ attrs[fontField] = value;
+ const fmark = this.view?.state.schema.marks['pF' + fontField.substring(1)].create(attrs);
this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
this.view.focus();
- } else if (SelectionManager.Views.length) {
- SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontFamily'] = family));
- } else Doc.UserDoc().fontFamily = family;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+ } else {
+ Doc.UserDoc()[fontField] = value;
+ this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+ }
};
- setHighlight(color: string) {
- if (this.view) {
- const highlightMark = this.view.state.schema.mark(this.view.state.schema.marks.marker, { highlight: color });
- this.setMark(highlightMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(highlightMark)), true);
- this.view.focus();
- } else Doc.UserDoc()._fontHighlight = color;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
- }
-
- setColor(color: string) {
- if (this.view) {
- const colorMark = this.view.state.schema.mark(this.view.state.schema.marks.pFontColor, { color });
- this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true);
- this.view.focus();
- } else if (SelectionManager.Views.length) {
- SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontColor'] = color));
- } else Doc.UserDoc().fontColor = color;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
- }
-
// TODO: remove doesn't work
// remove all node type and apply the passed-in one to the selected text
changeListType = (mapStyle: string) => {
@@ -407,7 +375,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const newMapStyle = active === mapStyle ? '' : mapStyle;
if (!this.view || newMapStyle === '') return;
- let inList = this.view.state.selection.$anchor.node(1).type === schema.nodes.ordered_list;
+ const inList = this.view.state.selection.$anchor.node(1).type === schema.nodes.ordered_list;
const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
if (inList) {
const tx2 = updateBullets(this.view.state.tr, schema, newMapStyle, this.view.state.doc.resolve(this.view.state.selection.$anchor.before(1) + 1).pos, this.view.state.doc.resolve(this.view.state.selection.$anchor.after(1)).pos);
@@ -428,7 +396,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
insertSummarizer(state: EditorState, dispatch: any) {
if (state.selection.empty) return false;
const mark = state.schema.marks.summarize.create();
- const tr = state.tr;
+ const { tr } = state;
tr.addMark(state.selection.from, state.selection.to, mark);
const content = tr.selection.content();
const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
@@ -436,13 +404,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- vcenterToggle = (view: EditorView, dispatch: any) => {
+ vcenterToggle = () => {
this.layoutDoc && (this.layoutDoc._layout_centered = !this.layoutDoc._layout_centered);
};
align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => {
if (this.TextView?._props.rootSelected?.()) {
- var tr = view.state.tr;
- view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => {
+ let { tr } = view.state;
+ view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos) => {
if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) {
tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, align: alignment }, node.marks);
return false;
@@ -455,56 +423,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
};
- insetParagraph(state: EditorState, dispatch: any) {
- var tr = state.tr;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
- const inset = (node.attrs.inset ? Number(node.attrs.inset) : 0) + 10;
- tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
- return false;
- }
- return true;
- });
- dispatch?.(tr);
- return true;
- }
- outsetParagraph(state: EditorState, dispatch: any) {
- var tr = state.tr;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
- const inset = Math.max(0, (node.attrs.inset ? Number(node.attrs.inset) : 0) - 10);
- tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
- return false;
- }
- return true;
- });
- dispatch?.(tr);
- return true;
- }
-
- indentParagraph(state: EditorState, dispatch: any) {
- var tr = state.tr;
- const heading = false;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
- const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
- const indent = !nodeval ? 25 : nodeval < 0 ? 0 : nodeval + 25;
- tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
- return false;
- }
- return true;
- });
- !heading && dispatch?.(tr);
- return true;
- }
-
- hangingIndentParagraph(state: EditorState, dispatch: any) {
- var tr = state.tr;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ paragraphSetup(state: EditorState, dispatch: any, field: 'inset' | 'indent', value?: 0 | 10 | -10) {
+ let { tr } = state;
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos) => {
if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
- const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
- const indent = !nodeval ? -25 : nodeval > 0 ? 0 : nodeval - 10;
- tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
+ const newValue = !value ?
+ (node.attrs[field] ? 0 : node.attrs[field] + 10) :
+ Math.max(0, value); // prettier-ignore
+ tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, ...(field === 'inset' ? { inset: newValue } : { indent: newValue }) }, node.marks);
return false;
}
return true;
@@ -514,7 +440,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
insertBlockquote(state: EditorState, dispatch: any) {
- const path = (state.selection.$from as any).path;
+ const { path } = state.selection.$from as any;
if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) {
lift(state, dispatch);
} else {
@@ -547,13 +473,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
@action
- fillBrush(state: EditorState, dispatch: any) {
+ fillBrush() {
if (!this.view) return;
if (!Array.from(this.brushMarks.keys()).length) {
- const selected_marks = this.getMarksInSelection(this.view.state);
- if (selected_marks.size >= 0) {
- this.brushMarks = selected_marks;
+ const selectedMarks = this.getMarksInSelection(this.view.state);
+ if (selectedMarks.size >= 0) {
+ this.brushMarks = selectedMarks;
}
} else {
const { from, to, $from } = this.view.state.selection;
@@ -597,9 +523,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const button = (
<Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom">
- <button className="antimodeMenu-button color-preview-button">
- <FontAwesomeIcon icon="link" size="lg" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button type="button" className="antimodeMenu-button color-preview-button">
+ <FontAwesomeIcon icon="link" size="lg" />
+ </button>
+ }
</Tooltip>
);
@@ -607,21 +536,22 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
<div className="dropdown link-menu">
<p>Linked to:</p>
<input value={link} ref={this._linkToRef} placeholder="Enter URL" onChange={onLinkChange} />
- <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, 'add:right')}>
+ <button type="button" className="make-button" onPointerDown={() => this.makeLinkToURL(link)}>
Apply hyperlink
</button>
<div className="divider" />
- <button className="remove-button" onPointerDown={e => this.deleteLink()}>
+ <button type="button" className="remove-button" onPointerDown={() => this.deleteLink()}>
Remove link
</button>
</div>
);
- return <ButtonDropdown view={this.view} key={'link button'} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} link={true} />;
+ // eslint-disable-next-line no-use-before-define
+ return <ButtonDropdown view={this.view} key="link button" button={button} dropdownContent={dropdownContent} openDropdownOnButton link />;
}
async getTextLinkTargetTitle() {
- if (!this.view) return;
+ if (!this.view) return undefined;
const node = this.view.state.selection.$from.nodeAfter;
const link = node && node.marks.find(m => m.type.name === 'link');
@@ -633,15 +563,15 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (linkclicked) {
const linkDoc = await DocServer.GetRefField(linkclicked);
if (linkDoc instanceof Doc) {
- const link_anchor_1 = await Cast(linkDoc.link_anchor_1, Doc);
- const link_anchor_2 = await Cast(linkDoc.link_anchor_2, Doc);
- const 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);
+ const linkAnchor1 = await Cast(linkDoc.link_anchor_1, Doc);
+ const linkAnchor2 = await Cast(linkDoc.link_anchor_2, Doc);
+ const currentDoc = DocumentView.Selected().lastElement().Document;
+ if (currentDoc && linkAnchor1 && linkAnchor2) {
+ if (Doc.AreProtosEqual(currentDoc, linkAnchor1)) {
+ return StrCast(linkAnchor2.title);
}
- if (Doc.AreProtosEqual(currentDoc, link_anchor_2)) {
- return StrCast(link_anchor_1.title);
+ if (Doc.AreProtosEqual(currentDoc, linkAnchor2)) {
+ return StrCast(linkAnchor1.title);
}
}
}
@@ -653,11 +583,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return link.attrs.title;
}
}
+ return undefined;
}
// TODO: should check for valid URL
@undoBatch
- makeLinkToURL = (target: string, lcoation: string) => {
+ makeLinkToURL = (target: string) => {
((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target);
};
@@ -673,7 +604,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
.filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0)
.forEach((aref: any) => {
const anchorId = aref.href.replace(Doc.localServerPath(), '').split('?')[0];
- anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
+ anchorId && DocServer.GetRefField(anchorId).then(linkDoc => Doc.DeleteLink?.(linkDoc as Doc));
});
}
}
@@ -736,7 +667,11 @@ export class ButtonDropdown extends ObservableReactComponent<ButtonDropdownProps
render() {
return (
- <div className="button-dropdown-wrapper" ref={node => (this.ref = node)}>
+ <div
+ className="button-dropdown-wrapper"
+ ref={node => {
+ this.ref = node;
+ }}>
{!this._props.pdf ? (
<div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this._props.openDropdownOnButton ? this.onDropdownClick : undefined}>
{this._props.button}
@@ -747,9 +682,12 @@ export class ButtonDropdown extends ObservableReactComponent<ButtonDropdownProps
) : (
<>
{this._props.button}
- <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
- <FontAwesomeIcon icon="caret-down" size="sm" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button type="button" className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
+ <FontAwesomeIcon icon="caret-down" size="sm" />
+ </button>
+ }
</>
)}
{this.showDropdown ? this._props.dropdownContent : null}
@@ -762,10 +700,11 @@ interface RichTextMenuPluginProps {
editorProps: any;
}
export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> {
- render() {
- return null;
- }
+ // eslint-disable-next-line react/no-unused-class-component-methods
update(view: EditorView, lastState: EditorState | undefined) {
RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, (view as any).TextView?.layoutDoc);
}
+ render() {
+ return null;
+ }
}
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 42665830f..bf11dfe62 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -1,17 +1,17 @@
import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules';
import { NodeSelection, TextSelection } from 'prosemirror-state';
+import { ClientUtils } from '../../../../ClientUtils';
import { Doc, DocListCast, FieldResult, StrListCast } from '../../../../fields/Doc';
import { DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
import { NumCast, StrCast } from '../../../../fields/Types';
import { Utils } from '../../../../Utils';
-import { DocServer } from '../../../DocServer';
-import { Docs, DocUtils } from '../../../documents/Documents';
+import { Docs } from '../../../documents/Documents';
import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { DocUtils } from '../../../documents/DocUtils';
import { CollectionView } from '../../collections/CollectionView';
import { ContextMenu } from '../../ContextMenu';
-import { KeyValueBox } from '../KeyValueBox';
import { FormattedTextBox } from './FormattedTextBox';
import { wrappingInputRule } from './prosemirrorPatches';
import { RichTextMenu } from './RichTextMenu';
@@ -48,13 +48,9 @@ export class RichTextRules {
/^A\.\s$/,
schema.nodes.ordered_list,
// match => {
- () => {
- return { mapStyle: 'multi', bulletStyle: 1 };
- // return ({ order: +match[1] })
- },
- (match: any, node: any) => {
- return node.childCount + node.attrs.order === +match[1];
- },
+ () => ({ mapStyle: 'multi', bulletStyle: 1 }),
+ // return ({ order: +match[1] })
+ (match: any, node: any) => node.childCount + node.attrs.order === +match[1],
((type: any) => ({ type: type, attrs: { mapStyle: 'multi', bulletStyle: 1 } })) as any
),
@@ -70,7 +66,7 @@ export class RichTextRules {
// ``` create code block
new InputRule(/^```$/, (state, match, start, end) => {
- let $start = state.doc.resolve(start);
+ const $start = state.doc.resolve(start);
if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), schema.nodes.code_block)) return null;
// this enables text with code blocks to be used as a 'paint' function via a styleprovider button that is added to Docs that have an onPaint script
@@ -86,13 +82,13 @@ export class RichTextRules {
}),
// %<font-size> set the font size
- new InputRule(new RegExp(/%([0-9]+)\s$/), (state, match, start, end) => {
+ new InputRule(/%([0-9]+)\s$/, (state, match, start, end) => {
const size = Number(match[1]);
return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
}),
- //Create annotation to a field on the text document
- new InputRule(new RegExp(/>::$/), (state, match, start, end) => {
+ // Create annotation to a field on the text document
+ new InputRule(/>::$/, (state, match, start, end) => {
const creator = (doc: Doc) => {
const textDoc = this.Document[DocData];
const numInlines = NumCast(textDoc.inlineTextCount);
@@ -107,7 +103,7 @@ export class RichTextRules {
.insert(start, newNode)
.replaceRangeWith(start + 1, end + 2, dashDoc)
.insertText(' ', start + 2)
- .setStoredMarks([...node.marks, ...(sm ? sm : [])])
+ .setStoredMarks([...node.marks, ...(sm || [])])
: this.TextBox.EditorView.state.tr
);
};
@@ -117,8 +113,8 @@ export class RichTextRules {
return null;
}),
- //Create annotation to a field on the text document
- new InputRule(new RegExp(/>>$/), (state, match, start, end) => {
+ // Create annotation to a field on the text document
+ new InputRule(/>>$/, (state, match, start, end) => {
const textDoc = this.Document[DocData];
const numInlines = NumCast(textDoc.inlineTextCount);
textDoc.inlineTextCount = numInlines + 1;
@@ -150,13 +146,13 @@ export class RichTextRules {
.insert(start, newNode)
.replaceRangeWith(start + 1, end + 1, dashDoc)
.insertText(' ', start + 2)
- .setStoredMarks([...node.marks, ...(sm ? sm : [])])
+ .setStoredMarks([...node.marks, ...(sm || [])])
: state.tr;
return replaced;
}),
// set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(new RegExp(/(%d|d)$/), (state, match, start, end) => {
+ new InputRule(/(%d|d)$/, (state, match, start, end) => {
if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
const pos = state.doc.resolve(start) as any;
for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
@@ -171,7 +167,7 @@ export class RichTextRules {
}),
// set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(new RegExp(/(%h|h)$/), (state, match, start, end) => {
+ new InputRule(/(%h|h)$/, (state, match, start, end) => {
if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
const pos = state.doc.resolve(start) as any;
for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
@@ -186,11 +182,11 @@ export class RichTextRules {
}),
// set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(new RegExp(/(%q|q)$/), (state, match, start, end) => {
+ new InputRule(/(%q|q)$/, (state, match, start, end) => {
if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
const pos = state.doc.resolve(start) as any;
if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) {
- const node = state.selection.node;
+ const { node } = state.selection;
return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 });
}
for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
@@ -205,46 +201,43 @@ export class RichTextRules {
}),
// center justify text
- new InputRule(new RegExp(/%\^/), (state, match, start, end) => {
+ new InputRule(/%\^/, (state, match, start, end) => {
const resolved = state.doc.resolve(start) as any;
if (resolved?.parent.type.name === 'paragraph') {
return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks);
- } else {
- const node = resolved.nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}),
// left justify text
- new InputRule(new RegExp(/%\[/), (state, match, start, end) => {
+ new InputRule(/%\[/, (state, match, start, end) => {
const resolved = state.doc.resolve(start) as any;
if (resolved?.parent.type.name === 'paragraph') {
return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks);
- } else {
- const node = resolved.nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}),
// right justify text
- new InputRule(new RegExp(/%\]/), (state, match, start, end) => {
+ new InputRule(/%\]/, (state, match, start, end) => {
const resolved = state.doc.resolve(start) as any;
if (resolved?.parent.type.name === 'paragraph') {
return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks);
- } else {
- const node = resolved.nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}),
// activate a style by name using prefix '%<color name>'
- new InputRule(new RegExp(/%[a-zA-Z_]+$/), (state, match, start, end) => {
+ new InputRule(/%[a-zA-Z_]+$/, (state, match, start, end) => {
const color = match[0].substring(1, match[0].length);
const marks = RichTextMenu.Instance?._brushMap.get(color);
@@ -259,7 +252,7 @@ export class RichTextRules {
}
if (marks) {
const tr = state.tr.deleteRange(start, end);
- return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
+ return marks ? Array.from(marks).reduce((tr2, m) => tr2.addStoredMark(m), tr) : tr;
}
const isValidColor = (strColor: string) => {
@@ -269,33 +262,33 @@ export class RichTextRules {
};
if (isValidColor(color)) {
- return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
+ return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ fontColor: color }));
}
return null;
}),
// toggle alternate text UI %/
- new InputRule(new RegExp(/%\//), (state, match, start, end) => {
+ new InputRule(/%\//, (state, match, start, end) => {
setTimeout(() => this.TextBox.cycleAlternateText(true));
return state.tr.deleteRange(start, end);
}),
// stop using active style
- new InputRule(new RegExp(/%%$/), (state, match, start, end) => {
+ new InputRule(/%%$/, (state, match, start, end) => {
const tr = state.tr.deleteRange(start, end);
const marks = state.tr.selection.$anchor.nodeBefore?.marks;
return marks
? Array.from(marks)
.filter(m => m.type !== state.schema.marks.user_mark)
- .reduce((tr, m) => tr.removeStoredMark(m), tr)
+ .reduce((tr2, m) => tr2.removeStoredMark(m), tr)
: tr;
}),
// create a hyperlink to a titled document
// @(<doctitle>)
- new InputRule(new RegExp(/@\(([a-zA-Z_@\.\? \-0-9]+)\)/), (state, match, start, end) => {
+ new InputRule(/@\(([a-zA-Z_@.? \-0-9]+)\)/, (state, match, start, end) => {
const docTitle = match[1];
const prefixLength = '@('.length;
if (docTitle) {
@@ -315,11 +308,11 @@ export class RichTextRules {
teditor.dispatch(tstate.tr.setSelection(new TextSelection(tstate.doc.resolve(selection))));
}
};
- const getTitledDoc = (docTitle: string) => {
- if (!DocServer.FindDocByTitle(docTitle)) {
- Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true });
+ const getTitledDoc = (title: string) => {
+ if (!Doc.FindDocByTitle(title)) {
+ Docs.Create.TextDocument('', { title: title, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true });
}
- const titledDoc = DocServer.FindDocByTitle(docTitle);
+ const titledDoc = Doc.FindDocByTitle(title);
return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc;
};
const target = getTitledDoc(docTitle);
@@ -335,22 +328,31 @@ export class RichTextRules {
// [@{this,doctitle,}.fieldKey{:,=,:=,=:=}value]
// [@{this,doctitle,}.fieldKey]
new InputRule(
- new RegExp(/\[(@|@this\.|@[a-zA-Z_\? \-0-9]+\.)([a-zA-Z_\?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_\(\)\.@\?\+\-\*\/\ 0-9\(\)]*))?\]/),
+ /\[(@|@this\.|@[a-zA-Z_? \-0-9]+\.)([a-zA-Z_?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_().@?+\-*/ 0-9()]*))?\]/,
(state, match, start, end) => {
const docTitle = match[1].substring(1).replace(/\.$/, '');
const fieldKey = match[2];
const assign = match[4] === ':' ? (match[4] = '') : match[4];
const value = match[5];
const dataDoc = value === undefined ? !fieldKey.startsWith('_') : !assign?.startsWith('=');
- const getTitledDoc = (docTitle: string) => DocServer.FindDocByTitle(docTitle);
+ const getTitledDoc = (title: string) => Doc.FindDocByTitle(title);
// if the value has commas assume its an array (unless it's part of a chat gpt call indicated by '((' )
if (value?.includes(',') && !value.startsWith('((')) {
const values = value.split(',');
const strs = values.some(v => !v.match(/^[-]?[0-9.]$/));
this.Document[DocData][fieldKey] = strs ? new List<string>(values) : new List<number>(values.map(v => Number(v)));
} else if (value) {
- KeyValueBox.SetField(this.Document, fieldKey, assign + value, Doc.IsDataProto(this.Document) ? true : undefined, assign.includes(":=") ? undefined:
- (gptval: FieldResult) => (dataDoc ? this.Document[DocData]:this.Document)[fieldKey] = gptval as string ); // prettier-ignore
+ Doc.SetField(
+ this.Document,
+ fieldKey,
+ assign + value,
+ Doc.IsDataProto(this.Document) ? true : undefined,
+ assign.includes(':=')
+ ? undefined
+ : (gptval: FieldResult) => {
+ (dataDoc ? this.Document[DocData] : this.Document)[fieldKey] = gptval as string;
+ }
+ );
if (fieldKey === this.TextBox.fieldKey) return this.TextBox.EditorView!.state.tr;
}
const target = docTitle ? getTitledDoc(docTitle) : undefined;
@@ -361,9 +363,9 @@ export class RichTextRules {
),
// pass the contents between '((' and '))' to chatGPT and append the result
- new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))$/), (state, match, start, end) => {
- var count = 0; // ignore first return value which will be the notation that chat is pending a result
- KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => {
+ new InputRule(/(^|[^=])(\(\(.*\)\))$/, (state, match, start, end) => {
+ let count = 0; // ignore first return value which will be the notation that chat is pending a result
+ Doc.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => {
if (count) {
const tr = this.TextBox.EditorView?.state.tr.insertText(' ' + (gptval as string));
tr && this.TextBox.EditorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(end + 2), tr.doc.resolve(end + 2 + (gptval as string).length))));
@@ -376,7 +378,7 @@ export class RichTextRules {
// create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
// @(wiki:title)
- new InputRule(new RegExp(/@\(wiki:([a-zA-Z_@:\.\?\-0-9 ]+)\)$/), (state, match, start, end) => {
+ new InputRule(/@\(wiki:([a-zA-Z_@:.?\-0-9 ]+)\)$/, (state, match, start, end) => {
const title = match[1].trim().replace(/ /g, '_');
this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))));
@@ -392,7 +394,7 @@ export class RichTextRules {
// create an inline equation node
// %eq
- new InputRule(new RegExp(/%eq/), (state, match, start, end) => {
+ new InputRule(/%eq/, (state, match, start, end) => {
const fieldKey = 'math' + Utils.GenerateGuid();
this.TextBox.dataDoc[fieldKey] = 'y=';
const tr = state.tr.setSelection(new TextSelection(state.tr.doc.resolve(end - 3), state.tr.doc.resolve(end))).replaceSelectionWith(schema.nodes.equation.create({ fieldKey }));
@@ -400,10 +402,10 @@ export class RichTextRules {
}),
// create an inline view of a tag stored under the '#' field
- new InputRule(new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), (state, match, start, end) => {
+ new InputRule(/#([a-zA-Z_-]+[a-zA-Z_\-0-9]*)\s$/, (state, match, start, end) => {
const tag = match[1];
if (!tag) return state.tr;
- //this.Document[DocData]['#' + tag] = '#' + tag;
+ // this.Document[DocData]['#' + tag] = '#' + tag;
const tags = StrListCast(this.Document[DocData].tags);
if (!tags.includes(tag)) {
tags.push(tag);
@@ -417,29 +419,25 @@ export class RichTextRules {
}),
// # heading
- textblockTypeInputRule(new RegExp(/^(#{1,6})\s$/), schema.nodes.heading, match => {
- return { level: match[1].length };
- }),
+ textblockTypeInputRule(/^(#{1,6})\s$/, schema.nodes.heading, match => ({ level: match[1].length })),
// set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(new RegExp(/[ti!x]$/), (state, match, start, end) => {
+ new InputRule(/[ti!x]$/, (state, match, start, end) => {
if (state.selection.to === state.selection.from || !this.EnteringStyle) return null;
const tag = match[0] === 't' ? 'todo' : match[0] === 'i' ? 'ignore' : match[0] === 'x' ? 'disagree' : match[0] === '!' ? 'important' : '??';
const node = (state.doc.resolve(start) as any).nodeAfter;
if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
- if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_mark) !== -1) {
- }
return node
? state.tr
.removeMark(start, end, schema.marks.user_mark)
- .addMark(start, end, schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))
- .addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) }))
+ .addMark(start, end, schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) }))
+ .addMark(start, end, schema.marks.user_tag.create({ userid: ClientUtils.CurrentUserEmail(), tag: tag, modified: Math.round(Date.now() / 1000 / 60) }))
: state.tr;
}),
- new InputRule(new RegExp(/%\(/), (state, match, start, end) => {
+ new InputRule(/%\(/, (state, match, start, end) => {
const node = (state.doc.resolve(start) as any).nodeAfter;
const sm = state.storedMarks?.slice() || [];
const mark = state.schema.marks.summarizeInclusive.create();
@@ -452,9 +450,7 @@ export class RichTextRules {
return replaced.setSelection(new TextSelection(replaced.doc.resolve(end))).setStoredMarks([...node.marks, ...sm]);
}),
- new InputRule(new RegExp(/%\)/), (state, match, start, end) => {
- return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
- }),
+ new InputRule(/%\)/, (state, match, start, end) => state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create())),
],
};
}
diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
index 7ec296ed2..238267f6e 100644
--- a/src/client/views/nodes/formattedText/SummaryView.tsx
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -3,6 +3,15 @@ import { Fragment, Node, Slice } from 'prosemirror-model';
import * as ReactDOM from 'react-dom/client';
import * as React from 'react';
+interface ISummaryView {}
+// currently nothing needs to be rendered for the internal view of a summary.
+// eslint-disable-next-line react/prefer-stateless-function
+export class SummaryViewInternal extends React.Component<ISummaryView> {
+ render() {
+ return null;
+ }
+}
+
// an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked.
// this node actively edits prosemirror (as opposed to just changing how things are rendered) and thus doesn't
// really need a react view. However, it would be cleaner to figure out how to do this just as a react rendering
@@ -12,11 +21,10 @@ export class SummaryView {
root: any;
constructor(node: any, view: any, getPos: any) {
- const self = this;
this.dom = document.createElement('span');
this.dom.className = this.className(node.attrs.visibility);
- this.dom.onpointerdown = function (e: any) {
- self.onPointerDown(e, node, view, getPos);
+ this.dom.onpointerdown = (e: any) => {
+ this.onPointerDown(e, node, view, getPos);
};
this.dom.onkeypress = function (e: any) {
e.stopPropagation();
@@ -32,8 +40,8 @@ export class SummaryView {
};
const js = node.toJSON;
- node.toJSON = function () {
- return js.apply(this, arguments);
+ node.toJSON = function (...args: any[]) {
+ return js.apply(this, args);
};
this.root = ReactDOM.createRoot(this.dom);
@@ -54,7 +62,8 @@ export class SummaryView {
const visited = new Set();
for (let i: number = start + 1; i < view.state.doc.nodeSize - 1; i++) {
let skip = false;
- view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
+ // eslint-disable-next-line no-loop-func
+ view.state.doc.nodesBetween(start, i, (node: Node /* , pos: number, parent: Node, index: number */) => {
if (node.isLeaf && !visited.has(node) && !skip) {
if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
visited.add(node);
@@ -87,11 +96,3 @@ export class SummaryView {
this.dom.className = this.className(visible);
};
}
-
-interface ISummaryView {}
-// currently nothing needs to be rendered for the internal view of a summary.
-export class SummaryViewInternal extends React.Component<ISummaryView> {
- render() {
- return <> </>;
- }
-}
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index ccf7de4a1..6e1f325cf 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -1,6 +1,5 @@
-import * as React from 'react';
-import { DOMOutputSpec, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from 'prosemirror-model';
-import { Doc } from '../../../../fields/Doc';
+import { DOMOutputSpec, MarkSpec } from 'prosemirror-model';
+import { ClientUtils } from '../../../../ClientUtils';
import { Utils } from '../../../../Utils';
const emDOM: DOMOutputSpec = ['em', 0];
@@ -13,7 +12,7 @@ export const marks: { [index: string]: MarkSpec } = {
attrs: {
id: { default: '' },
},
- toDOM(node: any) {
+ toDOM() {
return ['div', { className: 'dummy' }, 0];
},
},
@@ -45,7 +44,7 @@ export const marks: { [index: string]: MarkSpec } = {
toDOM(node: any) {
const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), '');
const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
- return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
+ return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /* 'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
},
},
noAutoLinkAnchor: {
@@ -61,7 +60,7 @@ export const marks: { [index: string]: MarkSpec } = {
},
},
],
- toDOM(node: any) {
+ toDOM() {
return ['span', { 'data-noAutoLink': 'true' }, 0];
},
},
@@ -128,29 +127,29 @@ export const marks: { [index: string]: MarkSpec } = {
/* FONTS */
pFontFamily: {
- attrs: { family: { default: '' } },
+ attrs: { fontFamily: { default: '' } },
parseDOM: [
{
tag: 'span',
getAttrs(dom: any) {
const cstyle = getComputedStyle(dom);
if (cstyle.font) {
- if (cstyle.font.indexOf('Times New Roman') !== -1) return { family: 'Times New Roman' };
- if (cstyle.font.indexOf('Arial') !== -1) return { family: 'Arial' };
- if (cstyle.font.indexOf('Georgia') !== -1) return { family: 'Georgia' };
- if (cstyle.font.indexOf('Comic Sans') !== -1) return { family: 'Comic Sans MS' };
- if (cstyle.font.indexOf('Tahoma') !== -1) return { family: 'Tahoma' };
- if (cstyle.font.indexOf('Crimson') !== -1) return { family: 'Crimson Text' };
+ if (cstyle.font.indexOf('Times New Roman') !== -1) return { fontFamily: 'Times New Roman' };
+ if (cstyle.font.indexOf('Arial') !== -1) return { fontFamily: 'Arial' };
+ if (cstyle.font.indexOf('Georgia') !== -1) return { fontFamily: 'Georgia' };
+ if (cstyle.font.indexOf('Comic Sans') !== -1) return { fontFamily: 'Comic Sans MS' };
+ if (cstyle.font.indexOf('Tahoma') !== -1) return { fontFamily: 'Tahoma' };
+ if (cstyle.font.indexOf('Crimson') !== -1) return { fontFamily: 'Crimson Text' };
}
- return { family: '' };
+ return { fontFamily: '' };
},
},
],
- toDOM: node => (node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0]),
+ toDOM: node => (node.attrs.fontFamily ? ['span', { style: `font-family: "${node.attrs.fontFamily}";` }] : ['span', 0]),
},
// :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text.
pFontColor: {
- attrs: { color: { default: '' } },
+ attrs: { fontColor: { default: '' } },
inclusive: true,
parseDOM: [
{
@@ -160,24 +159,24 @@ export const marks: { [index: string]: MarkSpec } = {
},
},
],
- toDOM: node => (node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]),
+ toDOM: node => (node.attrs.fontColor ? ['span', { style: 'color:' + node.attrs.fontColor }] : ['span', 0]),
},
- marker: {
+ pFontHighlight: {
attrs: {
- highlight: { default: 'transparent' },
+ fontHighlight: { default: 'transparent' },
},
inclusive: true,
parseDOM: [
{
tag: 'span',
getAttrs(dom: any) {
- return { highlight: dom.getAttribute('backgroundColor') };
+ return { fontHighlight: dom.getAttribute('background-color') };
},
},
],
toDOM(node: any) {
- return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }];
+ return node.attrs.fontHighlight ? ['span', { style: 'background-color:' + node.attrs.fontHighlight }] : ['span', { style: 'background-color: transparent' }];
},
},
@@ -336,7 +335,7 @@ export const marks: { [index: string]: MarkSpec } = {
const min = Math.round(node.attrs.modified / 60);
const hr = Math.round(min / 60);
const day = Math.round(hr / 60 / 24);
- const remote = node.attrs.userid !== Doc.CurrentUserEmail ? ' UM-remote' : '';
+ const remote = node.attrs.userid !== ClientUtils.CurrentUserEmail() ? ' UM-remote' : '';
return ['span', { class: 'UM-' + uid + remote + ' UM-min-' + min + ' UM-hr-' + hr + ' UM-day-' + day }, 0];
},
},
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 62b8b03d6..5bf942218 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -2,16 +2,17 @@ import { DOMOutputSpec, Node, NodeSpec } from 'prosemirror-model';
import { listItem, orderedList } from 'prosemirror-schema-list';
import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec';
import { DocServer } from '../../../DocServer';
-import { Doc, Field } from '../../../../fields/Doc';
+import { Doc, Field, FieldType } from '../../../../fields/Doc';
+import { schema } from './schema_rts';
-const blockquoteDOM: DOMOutputSpec = ['blockquote', 0],
- hrDOM: DOMOutputSpec = ['hr'],
- preDOM: DOMOutputSpec = ['pre', ['code', 0]],
- brDOM: DOMOutputSpec = ['br'],
- ulDOM: DOMOutputSpec = ['ul', 0];
+const blockquoteDOM: DOMOutputSpec = ['blockquote', 0];
+const hrDOM: DOMOutputSpec = ['hr'];
+const preDOM: DOMOutputSpec = ['pre', ['code', 0]];
+const brDOM: DOMOutputSpec = ['br'];
+// const ulDOM: DOMOutputSpec = ['ul', 0];
-function formatAudioTime(time: number) {
- time = Math.round(time);
+function formatAudioTime(timeIn: number) {
+ const time = Math.round(timeIn);
const hours = Math.floor(time / 60 / 60);
const minutes = Math.floor(time / 60) - hours * 60;
const seconds = time % 60;
@@ -266,7 +267,7 @@ export const nodes: { [index: string]: NodeSpec } = {
hideValue: { default: false },
editable: { default: true },
},
- leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as Field),
+ leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as FieldType),
group: 'inline',
draggable: false,
toDOM(node) {
@@ -353,7 +354,7 @@ export const nodes: { [index: string]: NodeSpec } = {
},
{
style: 'list-style-type=disc',
- getAttrs(dom: any) {
+ getAttrs() {
return { mapStyle: 'bullet' };
},
},
@@ -373,10 +374,10 @@ export const nodes: { [index: string]: NodeSpec } = {
],
toDOM(node: Node) {
const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
- const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
- const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
- const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
- const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
+ const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontHighlight)?.attrs.fontHighlight);
+ const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontSize)?.attrs.fontSize);
+ const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontFamily)?.attrs.fontFamily);
+ const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontColor)?.attrs.fontColor);
const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : '';
if (node.attrs.mapStyle === 'bullet') {
return [
@@ -421,10 +422,10 @@ export const nodes: { [index: string]: NodeSpec } = {
},
],
toDOM(node: Node) {
- const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
- const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
- const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
- const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
+ const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontHighlight)?.attrs.fontHighlight);
+ const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontSize)?.attrs.fontSize);
+ const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontFamily)?.attrs.fontFamily);
+ const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontColor)?.attrs.fontColor);
const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
return [
'li',
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index a485ea4c3..91b0ebd5c 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -1,33 +1,39 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+/* eslint-disable jsx-a11y/img-redundant-alt */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/function-component-definition */
import { Checkbox, FormControlLabel, Slider, TextField } from '@mui/material';
import { IconButton } from 'browndash-components';
+import * as React from 'react';
import { useEffect, useRef, useState } from 'react';
import { CgClose } from 'react-icons/cg';
import { IoMdRedo, IoMdUndo } from 'react-icons/io';
+import { ClientUtils } from '../../../../ClientUtils';
import { Doc, DocListCast } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { NumCast } from '../../../../fields/Types';
-import { Utils } from '../../../../Utils';
-import { Docs, DocUtils } from '../../../documents/Documents';
import { Networking } from '../../../Network';
-import { DocumentManager } from '../../../util/DocumentManager';
+import { DocUtils } from '../../../documents/DocUtils';
+import { Docs } from '../../../documents/Documents';
import { CollectionDockingView } from '../../collections/CollectionDockingView';
import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
-import { OpenWhereMod } from '../DocumentView';
-import { ImageBox, ImageEditorData } from '../ImageBox';
+import { ImageEditorData } from '../ImageBox';
+import { OpenWhereMod } from '../OpenWhere';
import './GenerativeFill.scss';
import Buttons from './GenerativeFillButtons';
import { BrushHandler } from './generativeFillUtils/BrushHandler';
-import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants';
-import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces';
import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler';
import { PointerHandler } from './generativeFillUtils/PointerHandler';
-import * as React from 'react';
+import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants';
+import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces';
+import { DocumentView } from '../DocumentView';
-enum BrushStyle {
- ADD,
- SUBTRACT,
- MARQUEE,
-}
+// enum BrushStyle {
+// ADD,
+// SUBTRACT,
+// MARQUEE,
+// }
interface GenerativeFillProps {
imageEditorOpen: boolean;
@@ -52,7 +58,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
// format: array of [image source, corresponding image Doc]
const [edits, setEdits] = useState<(string | Doc)[][]>([]);
const [edited, setEdited] = useState(false);
- const [brushStyle, setBrushStyle] = useState<BrushStyle>(BrushStyle.ADD);
+ // const [brushStyle] = useState<BrushStyle>(BrushStyle.ADD);
const [input, setInput] = useState('');
const [loading, setLoading] = useState(false);
const [canvasDims, setCanvasDims] = useState<ImageDimensions>({
@@ -98,8 +104,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
if (!ctx || !currImg.current || !canvasRef.current) return;
const target = redoStack.current[redoStack.current.length - 1];
- if (!target) {
- } else {
+ if (target) {
undoStack.current = [...undoStack.current, canvasRef.current?.toDataURL()];
const img = new Image();
img.src = target;
@@ -131,11 +136,11 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
setIsBrushing(true);
const { x, y } = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale);
- BrushHandler.brushCircleOverlay(x, y, cursorData.width / 2 / canvasScale, ctx, eraserColor, brushStyle === BrushStyle.SUBTRACT);
+ BrushHandler.brushCircleOverlay(x, y, cursorData.width / 2 / canvasScale, ctx, eraserColor /* , brushStyle === BrushStyle.SUBTRACT */);
};
// stop brushing, push to undo stack
- const handlePointerUp = (e: React.PointerEvent) => {
+ const handlePointerUp = () => {
const ctx = ImageUtility.getCanvasContext(canvasBackgroundRef);
if (!ctx) return;
if (!isBrushing) return;
@@ -144,11 +149,11 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
// handles brushing on pointer movement
useEffect(() => {
- if (!isBrushing) return;
+ if (!isBrushing) return undefined;
const canvas = canvasRef.current;
- if (!canvas) return;
+ if (!canvas) return undefined;
const ctx = ImageUtility.getCanvasContext(canvasRef);
- if (!ctx) return;
+ if (!ctx) return undefined;
const handlePointerMove = (e: PointerEvent) => {
const currPoint = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale);
@@ -156,7 +161,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
x: currPoint.x - e.movementX / canvasScale,
y: currPoint.y - e.movementY / canvasScale,
};
- BrushHandler.createBrushPathOverlay(lastPoint, currPoint, cursorData.width / 2 / canvasScale, ctx, eraserColor, brushStyle === BrushStyle.SUBTRACT);
+ BrushHandler.createBrushPathOverlay(lastPoint, currPoint, cursorData.width / 2 / canvasScale, ctx, eraserColor /* , brushStyle === BrushStyle.SUBTRACT */);
};
drawingAreaRef.current?.addEventListener('pointermove', handlePointerMove);
@@ -290,12 +295,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
_height: newCollectionSize,
title: 'Image edit collection',
});
- DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false });
+ DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History' });
// opening new tab
CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
// add the doc to the main freeform
+ // eslint-disable-next-line no-use-before-define
await createNewImgDoc(originalImg.current, true);
}
} else {
@@ -309,12 +315,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImgToCanvasUrl(url, canvasDims.width, canvasDims.height)));
const imgRes = await Promise.all(
imgUrls.map(async url => {
+ // eslint-disable-next-line no-use-before-define
const saveRes = await onSave(url);
return [url, saveRes as Doc];
})
);
setEdits(imgRes);
const image = new Image();
+ // eslint-disable-next-line prefer-destructuring
image.src = imgUrls[0];
ImageUtility.drawImgToCanvas(image, canvasRef, canvasDims.width, canvasDims.height);
currImg.current = image;
@@ -332,7 +340,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
const startY = NumCast(parentDoc.current.y);
const children = DocListCast(parentDoc.current.gen_fill_children);
const len = children.length;
- let initialYPositions: number[] = [];
+ const initialYPositions: number[] = [];
for (let i = 0; i < len; i++) {
initialYPositions.push(startY + i * offsetDistanceY);
}
@@ -347,10 +355,10 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
// creates a new image document and returns its reference
const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean): Promise<Doc | undefined> => {
- if (!imageRootDoc) return;
- const src = img.src;
+ if (!imageRootDoc) return undefined;
+ const { src } = img;
const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] });
- const source = Utils.prepend(result.accessPaths.agnostic.client);
+ const source = ClientUtils.prepend(result.accessPaths.agnostic.client);
if (firstDoc) {
const x = 0;
@@ -370,51 +378,51 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
}
parentDoc.current = newImg;
return newImg;
- } else {
- if (!parentDoc.current) return;
- const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX;
- const initialY = 0;
-
- const newImg = Docs.Create.ImageDocument(source, {
- x: x,
- y: initialY,
- _height: freeformRenderSize,
- _width: freeformRenderSize,
- data_nativeWidth: result.nativeWidth,
- data_nativeHeight: result.nativeHeight,
- });
+ }
+ if (!parentDoc.current) return undefined;
+ const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX;
+ const initialY = 0;
+
+ const newImg = Docs.Create.ImageDocument(source, {
+ x: x,
+ y: initialY,
+ _height: freeformRenderSize,
+ _width: freeformRenderSize,
+ data_nativeWidth: result.nativeWidth,
+ data_nativeHeight: result.nativeHeight,
+ });
- const parentList = DocListCast(parentDoc.current.gen_fill_children);
- if (parentList.length > 0) {
- parentList.push(newImg);
- parentDoc.current.gen_fill_children = new List<Doc>(parentList);
- } else {
- parentDoc.current.gen_fill_children = new List<Doc>([newImg]);
- }
+ const parentList = DocListCast(parentDoc.current.gen_fill_children);
+ if (parentList.length > 0) {
+ parentList.push(newImg);
+ parentDoc.current.gen_fill_children = new List<Doc>(parentList);
+ } else {
+ parentDoc.current.gen_fill_children = new List<Doc>([newImg]);
+ }
- DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}`, link_displayLine: true });
- adjustImgPositions();
+ DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}` });
+ adjustImgPositions();
- if (isNewCollection && newCollectionRef.current) {
- Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
- } else {
- addDoc?.(newImg);
- }
- return newImg;
+ if (isNewCollection && newCollectionRef.current) {
+ Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
+ } else {
+ addDoc?.(newImg);
}
+ return newImg;
};
// Saves an image to the collection
const onSave = async (src: string) => {
const img = new Image();
img.src = src;
- if (!currImg.current || !originalImg.current || !imageRootDoc) return;
+ if (!currImg.current || !originalImg.current || !imageRootDoc) return undefined;
try {
const res = await createNewImgDoc(img, false);
return res;
} catch (err) {
console.log(err);
}
+ return undefined;
};
// Closes the editor view
@@ -422,7 +430,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
ImageEditorData.Open = false;
ImageEditorData.Source = '';
if (newCollectionRef.current) {
- DocumentManager.Instance.AddViewRenderedCb(newCollectionRef.current, dv => (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce());
+ DocumentView.addViewRenderedCb(newCollectionRef.current, dv => (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce());
}
setEdits([]);
};
@@ -443,12 +451,12 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
}}
/>
}
- label={'Create New Collection'}
+ label="Create New Collection"
labelPlacement="end"
sx={{ whiteSpace: 'nowrap' }}
/>
<Buttons getEdit={getEdit} loading={loading} onReset={handleReset} />
- <IconButton color={activeColor} tooltip="close" icon={<CgClose size={'16px'} />} onClick={handleViewClose} />
+ <IconButton color={activeColor} tooltip="close" icon={<CgClose size="16px" />} onClick={handleViewClose} />
</div>
</div>
{/* Main canvas for editing */}
@@ -469,7 +477,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
width: cursorData.width,
height: cursorData.width,
}}>
- <div className="innerPointer"></div>
+ <div className="innerPointer" />
</div>
{/* Icons */}
<div className="iconContainer">
@@ -519,11 +527,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
/>
</div>
</div>
- {/* Edits thumbnails*/}
+ {/* Edits thumbnails */}
<div className="editsBox">
{edits.map((edit, i) => (
<img
+ // eslint-disable-next-line react/no-array-index-key
key={i}
+ alt="image edits"
width={75}
src={edit[0] as string}
style={{ cursor: 'pointer' }}
@@ -552,6 +562,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
Original
</label>
<img
+ alt="image stuff"
width={75}
src={originalImg.current?.src}
style={{ cursor: 'pointer' }}
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
index 185ba2280..d1f68ee0e 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
@@ -1,9 +1,9 @@
import './GenerativeFillButtons.scss';
import * as React from 'react';
import ReactLoading from 'react-loading';
-import { activeColor } from './generativeFillUtils/generativeFillConstants';
import { Button, IconButton, Type } from 'browndash-components';
import { AiOutlineInfo } from 'react-icons/ai';
+import { activeColor } from './generativeFillUtils/generativeFillConstants';
interface ButtonContainerProps {
getEdit: () => Promise<void>;
@@ -11,7 +11,7 @@ interface ButtonContainerProps {
onReset: () => void;
}
-const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => {
+function Buttons({ loading, getEdit, onReset }: ButtonContainerProps) {
return (
<div className="generativeFillBtnContainer">
<Button text="RESET" type={Type.PRIM} color={activeColor} onClick={onReset} />
@@ -20,7 +20,7 @@ const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => {
text="GET EDITS"
type={Type.TERT}
color={activeColor}
- icon={<ReactLoading type="spin" color={'#ffffff'} width={20} height={20} />}
+ icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />}
iconPlacement="right"
onClick={() => {
if (!loading) getEdit();
@@ -36,9 +36,9 @@ const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => {
}}
/>
)}
- <IconButton type={Type.SEC} color={activeColor} tooltip="Open Documentation" icon={<AiOutlineInfo size={'16px'} />} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')} />
+ <IconButton type={Type.SEC} color={activeColor} tooltip="Open Documentation" icon={<AiOutlineInfo size="16px" />} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')} />
</div>
);
-};
+}
export default Buttons;
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
index f4ec70fbc..16d529d93 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
@@ -3,7 +3,7 @@ import { eraserColor } from './generativeFillConstants';
import { Point } from './generativeFillInterfaces';
export class BrushHandler {
- static brushCircleOverlay = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, erase: boolean) => {
+ static brushCircleOverlay = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string /* , erase: boolean */) => {
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = fillColor;
ctx.shadowColor = eraserColor;
@@ -14,12 +14,12 @@ export class BrushHandler {
ctx.closePath();
};
- static createBrushPathOverlay = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, erase: boolean) => {
+ static createBrushPathOverlay = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string /* , erase: boolean */) => {
const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint);
for (let i = 0; i < dist; i += 5) {
const s = i / dist;
- BrushHandler.brushCircleOverlay(startPoint.x * (1 - s) + endPoint.x * s, startPoint.y * (1 - s) + endPoint.y * s, brushRadius, ctx, fillColor, erase);
+ BrushHandler.brushCircleOverlay(startPoint.x * (1 - s) + endPoint.x * s, startPoint.y * (1 - s) + endPoint.y * s, brushRadius, ctx, fillColor /* , erase */);
}
};
}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
index 97e03ff20..6da8c3da0 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
@@ -1,10 +1,6 @@
import { Point } from './generativeFillInterfaces';
export class GenerativeFillMathHelpers {
- static distanceBetween = (p1: Point, p2: Point) => {
- return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
- };
- static angleBetween = (p1: Point, p2: Point) => {
- return Math.atan2(p2.x - p1.x, p2.y - p1.y);
- };
+ static distanceBetween = (p1: Point, p2: Point) => Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
+ static angleBetween = (p1: Point, p2: Point) => Math.atan2(p2.x - p1.x, p2.y - p1.y);
}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
index 47a14135f..24dba1778 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
@@ -17,15 +17,14 @@ export class ImageUtility {
* @param canvas Canvas to convert
* @returns Blob of canvas
*/
- static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> => {
- return new Promise(resolve => {
+ static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> =>
+ new Promise(resolve => {
canvas.toBlob(blob => {
if (blob) {
resolve(blob);
}
}, 'image/png');
});
- };
// given a square api image, get the cropped img
static getCroppedImg = (img: HTMLImageElement, width: number, height: number): HTMLCanvasElement | undefined => {
@@ -48,11 +47,12 @@ export class ImageUtility {
}
return canvas;
}
+ return undefined;
};
// converts an image to a canvas data url
- static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise<string> => {
- return new Promise<string>((resolve, reject) => {
+ static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise<string> =>
+ new Promise<string>((resolve, reject) => {
const img = new Image();
img.onload = () => {
const canvas = this.getCroppedImg(img, width, height);
@@ -66,7 +66,6 @@ export class ImageUtility {
};
img.src = imageSrc;
});
- };
// calls the openai api to get image edits
static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise<APISuccess | APIError> => {
@@ -91,7 +90,7 @@ export class ImageUtility {
console.log(data.data);
return {
status: 'success',
- urls: (data.data as { b64_json: string }[]).map(data => `data:image/png;base64,${data.b64_json}`),
+ urls: (data.data as { b64_json: string }[]).map(urlData => `data:image/png;base64,${urlData.b64_json}`),
};
} catch (err) {
console.log(err);
@@ -100,12 +99,10 @@ export class ImageUtility {
};
// mock api call
- static mockGetEdit = async (mockSrc: string): Promise<APISuccess | APIError> => {
- return {
- status: 'success',
- urls: [mockSrc, mockSrc, mockSrc],
- };
- };
+ static mockGetEdit = async (mockSrc: string): Promise<APISuccess | APIError> => ({
+ status: 'success',
+ urls: [mockSrc, mockSrc, mockSrc],
+ });
// Gets the canvas rendering context of a canvas
static getCanvasContext = (canvasRef: RefObject<HTMLCanvasElement>): CanvasRenderingContext2D | null => {
@@ -150,12 +147,12 @@ export class ImageUtility {
// Draws the image to the current canvas
static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject<HTMLCanvasElement>, width: number, height: number) => {
- const drawImg = (img: HTMLImageElement) => {
+ const drawImg = (htmlImg: HTMLImageElement) => {
const ctx = this.getCanvasContext(canvasRef);
if (!ctx) return;
ctx.globalCompositeOperation = 'source-over';
ctx.clearRect(0, 0, width, height);
- ctx.drawImage(img, 0, 0, width, height);
+ ctx.drawImage(htmlImg, 0, 0, width, height);
};
if (img.complete) {
@@ -173,7 +170,7 @@ export class ImageUtility {
canvas.width = canvasSize;
canvas.height = canvasSize;
const ctx = canvas.getContext('2d');
- if (!ctx) return;
+ if (!ctx) return undefined;
ctx?.clearRect(0, 0, canvasSize, canvasSize);
ctx.drawImage(paddedCanvas, 0, 0);
@@ -195,7 +192,7 @@ export class ImageUtility {
// Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas)
static drawHorizontalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, xOffset: number) => {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
- const data = imageData.data;
+ const { data } = imageData;
for (let i = 0; i < canvas.height; i++) {
for (let j = 0; j < xOffset; j++) {
const targetIdx = 4 * (i * canvas.width + j);
@@ -224,7 +221,7 @@ export class ImageUtility {
// Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas)
static drawVerticalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, yOffset: number) => {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
- const data = imageData.data;
+ const { data } = imageData;
for (let j = 0; j < canvas.width; j++) {
for (let i = 0; i < yOffset; i++) {
const targetIdx = 4 * (i * canvas.width + j);
@@ -256,7 +253,7 @@ export class ImageUtility {
canvas.width = canvasSize;
canvas.height = canvasSize;
const ctx = canvas.getContext('2d');
- if (!ctx) return;
+ if (!ctx) return undefined;
// fix scaling
const scale = Math.min(canvasSize / img.width, canvasSize / img.height);
const width = Math.floor(img.width * scale);
@@ -310,5 +307,6 @@ export class ImageUtility {
} catch (err) {
console.error(err);
}
+ return undefined;
};
}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
index 9e620ad11..260923a64 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
@@ -1,15 +1,11 @@
-import { Point } from "./generativeFillInterfaces";
+import { Point } from './generativeFillInterfaces';
export class PointerHandler {
- static getPointRelativeToElement = (
- element: HTMLElement,
- e: React.PointerEvent | PointerEvent,
- scale: number
- ): Point => {
- const boundingBox = element.getBoundingClientRect();
- return {
- x: (e.clientX - boundingBox.x) / scale,
- y: (e.clientY - boundingBox.y) / scale,
+ static getPointRelativeToElement = (element: HTMLElement, e: React.PointerEvent | PointerEvent, scale: number): Point => {
+ const boundingBox = element.getBoundingClientRect();
+ return {
+ x: (e.clientX - boundingBox.x) / scale,
+ y: (e.clientY - boundingBox.y) / scale,
+ };
};
- };
}
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
index 6e7c3e612..317719032 100644
--- a/src/client/views/nodes/importBox/ImportElementBox.tsx
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -1,7 +1,7 @@
import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { returnFalse } from '../../../../Utils';
+import { returnFalse } from '../../../../ClientUtils';
import { Doc } from '../../../../fields/Doc';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { DocumentView } from '../DocumentView';
@@ -22,13 +22,14 @@ export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
return (
<div style={{ backgroundColor: 'pink' }}>
<DocumentView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props} //
LayoutTemplateString={undefined}
Document={this.Document}
isContentActive={returnFalse}
addDocument={returnFalse}
ScreenToLocalTransform={this.screenToLocalXf}
- hideResizeHandles={true}
+ hideResizeHandles
/>
</div>
);
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 91fdb90fc..6b4f5e073 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1,68 +1,45 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast, Field, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
+import { lightOrDark, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../ClientUtils';
+import { Doc, DocListCast, Field, FieldResult, FieldType, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
import { Animation, DocData, TransitionTimer } from '../../../../fields/DocSymbols';
-import { Copy, Id } from '../../../../fields/FieldSymbols';
+import { Copy } from '../../../../fields/FieldSymbols';
import { InkField } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
import { ObjectField } from '../../../../fields/ObjectField';
import { listSpec } from '../../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { AudioField } from '../../../../fields/URLField';
-import { emptyFunction, emptyPath, lightOrDark, returnFalse, returnOne, setupMoveUpEvents, StopEvent, stringHash } from '../../../../Utils';
+import { BoolCast, Cast, DocCast, NumCast, StrCast, toList } from '../../../../fields/Types';
+import { emptyFunction, emptyPath, stringHash } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { Docs } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { dropActionType } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
-import { SelectionManager } from '../../../util/SelectionManager';
import { SerializationHelper } from '../../../util/SerializationHelper';
-import { SettingsManager } from '../../../util/SettingsManager';
+import { SnappingManager } from '../../../util/SnappingManager';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { CollectionDockingView } from '../../collections/CollectionDockingView';
-import { CollectionFreeFormView, MarqueeViewBounds } from '../../collections/collectionFreeForm';
-import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline';
+import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
+import { CollectionFreeFormPannableContents } from '../../collections/collectionFreeForm/CollectionFreeFormPannableContents';
import { CollectionView } from '../../collections/CollectionView';
import { TreeView } from '../../collections/TreeView';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { Colors } from '../../global/globalEnums';
import { LightboxView } from '../../LightboxView';
-import { DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView';
-import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
+import { pinDataTypes as dataTypes } from '../../PinFuncs';
+import { DocumentView } from '../DocumentView';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { FocusViewOptions } from '../FocusViewOptions';
+import { OpenWhere, OpenWhereMod } from '../OpenWhere';
import { ScriptingBox } from '../ScriptingBox';
import './PresBox.scss';
import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
-export interface pinDataTypes {
- scrollable?: boolean;
- dataviz?: number[];
- pannable?: boolean;
- type_collection?: boolean;
- inkable?: boolean;
- filters?: boolean;
- pivot?: boolean;
- temporal?: boolean;
- clippable?: boolean;
- datarange?: boolean;
- dataview?: boolean;
- poslayoutview?: boolean;
- dataannos?: boolean;
- map?: boolean;
-}
-export interface PinProps {
- audioRange?: boolean;
- activeFrame?: number;
- currentFrame?: number;
- hidePresBox?: boolean;
- pinViewport?: MarqueeViewBounds; // pin a specific viewport on a freeform view (use MarqueeView.CurViewBounds to compute if no region has been selected)
- pinDocLayout?: boolean; // pin layout info (width/height/x/y)
- pinAudioPlay?: boolean; // pin audio annotation
- pinData?: pinDataTypes;
-}
@observer
export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -75,8 +52,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
super(props);
makeObservable(this);
if (!PresBox.navigateToDocScript) {
- PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(this.presentation_targetDoc, self)')!;
+ PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(this.presentation_targetDoc, this)')!;
}
+ CollectionFreeFormPannableContents.SetOverlayPlugin((fform: Doc) => PresBox.Instance.pathLines(fform));
}
private _disposers: { [name: string]: IReactionDisposer } = {};
@@ -86,6 +64,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
_unmounting = false; // flag that view is unmounting used to block RemFromMap from deleting things
_presTimer: NodeJS.Timeout | undefined;
+ // eslint-disable-next-line no-use-before-define
@observable public static Instance: PresBox;
@observable _isChildActive = false;
@@ -126,7 +105,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return DocCast(this.childDocs[NumCast(this.Document._itemIndex)]);
}
@computed get targetDoc() {
- return Cast(this.activeItem?.presentation_targetDoc, Doc, null);
+ return DocCast(this.activeItem?.presentation_targetDoc);
}
public static targetRenderedDoc = (doc: Doc) => {
const targetDoc = Cast(doc?.presentation_targetDoc, Doc, null);
@@ -141,8 +120,9 @@ 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.Document);
+ if (DocumentView.Selected().length) return DocumentView.Selected()[0];
+ if (this.selectedArray.size) return DocumentView.getDocumentView(this.Document);
+ return undefined;
}
@computed get isPres() {
return this.selectedDoc === this.Document;
@@ -165,6 +145,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
componentDidMount() {
+ this._disposers.pause = reaction(
+ () => SnappingManager.UserPanned,
+ () => this.pauseAutoPres()
+ );
this._disposers.keyboard = reaction(
() => this.selectedDoc,
selected => {
@@ -189,7 +173,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._unmounting = false;
this.turnOffEdit(true);
this._disposers.selection = reaction(
- () => SelectionManager.Views.slice(),
+ () => DocumentView.Selected().slice(),
views => (!PresBox.Instance || views.some(view => view.Document === this.Document)) && this.updateCurrentPresentation(),
{ fireImmediately: true }
);
@@ -197,7 +181,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
() => this.layoutDoc.presentation_status === PresStatus.Edit,
editing => editing &&
this.childDocs.filter(doc => doc.presentation_indexed !== undefined).forEach(doc => {
- this.progressivizedItems(doc)?.forEach(indexedDoc => (indexedDoc.opacity = undefined));
+ this.progressivizedItems(doc)?.forEach(indexedDoc => { indexedDoc.opacity = undefined; });
doc.presentation_indexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, 1);
}) // prettier-ignore
);
@@ -214,7 +198,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
startTempMedia = (targetDoc: Doc, activeItem: Doc) => {
const duration: number = NumCast(activeItem.config_clipEnd) - NumCast(activeItem.config_clipStart);
if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) {
- const targMedia = DocumentManager.Instance.getDocumentView(targetDoc);
+ const targMedia = DocumentView.getDocumentView(targetDoc);
targMedia?.ComponentView?.playFrom?.(NumCast(activeItem.config_clipStart), NumCast(activeItem.config_clipStart) + duration);
}
};
@@ -222,18 +206,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
stopTempMedia = (targetDocField: FieldResult) => {
const targetDoc = DocCast(DocCast(targetDocField).annotationOn) ?? DocCast(targetDocField);
if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) {
- const targMedia = DocumentManager.Instance.getDocumentView(targetDoc);
+ const targMedia = DocumentView.getDocumentView(targetDoc);
targMedia?.ComponentView?.Pause?.();
}
};
- //TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time
+ // TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time
// TODO: to handle child slides (entering into subtrail and exiting), also the next() and back() functions
// No more frames in current doc and next slide is defined, therefore move to next slide
nextSlide = (slideNum?: number) => {
const nextSlideInd = slideNum ?? this.itemIndex + 1;
let curSlideInd = nextSlideInd;
- //CollectionStackedTimeline.CurrentlyPlaying?.map(clipView => clipView?.ComponentView?.Pause?.());
+ // CollectionStackedTimeline.CurrentlyPlaying?.map(clipView => clipView?.ComponentView?.Pause?.());
this.clearSelectedArray();
const doGroupWithUp =
(nextSelected: number, force = false) =>
@@ -245,7 +229,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (serial) {
this.gotoDocument(nextSelected, this.activeItem, true, async () => {
const waitTime = NumCast(this.activeItem.presentation_duration);
- await new Promise<void>(res => setTimeout(() => res(), Math.max(0, waitTime)));
+ await new Promise<void>(res => {
+ setTimeout(res, Math.max(0, waitTime));
+ });
doGroupWithUp(nextSelected + 1)();
});
} else {
@@ -264,14 +250,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const targetList = PresBox.targetRenderedDoc(doc);
if (doc.presentation_indexed !== undefined && targetList) {
const listItems = (Cast(targetList[Doc.LayoutFieldKey(targetList)], listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[]) ?? DocListCast(targetList[Doc.LayoutFieldKey(targetList) + '_annotations']);
- return listItems.filter(doc => !doc.layout_unrendered);
+ return listItems.filter(ldoc => !ldoc.layout_unrendered);
}
+ return undefined;
};
// go to documents chain
runSubroutines = (childrenToRun: Opt<Doc[]>, normallyNextSlide: Doc) => {
if (childrenToRun && childrenToRun[0] !== normallyNextSlide) {
- childrenToRun.forEach(child => DocumentManager.Instance.showDocument(child, {}));
+ childrenToRun.forEach(child => DocumentView.showDocument(child, {}));
}
};
@@ -284,12 +271,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const targetRenderedDoc = PresBox.targetRenderedDoc(this.activeItem);
targetRenderedDoc._dataTransition = 'all 1s';
targetRenderedDoc.opacity = 1;
- setTimeout(() => (targetRenderedDoc._dataTransition = 'inherit'), 1000);
+ setTimeout(() => {
+ targetRenderedDoc._dataTransition = 'inherit';
+ }, 1000);
const listItems = this.progressivizedItems(this.activeItem);
if (listItems && presIndexed < listItems.length) {
if (!first) {
const listItemDoc = listItems[presIndexed];
- const targetView = listItems && DocumentManager.Instance.getFirstDocumentView(listItemDoc);
+ const targetView = listItems && DocumentView.getFirstDocumentView(listItemDoc);
Doc.linkFollowUnhighlight();
Doc.HighlightDoc(listItemDoc);
listItemDoc.presentation_effect = this.activeItem.presBulletEffect;
@@ -305,6 +294,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return true;
}
}
+ return undefined;
};
if (progressiveReveal(false)) return true;
if (this.childDocs[this.itemIndex + 1] !== undefined) {
@@ -314,7 +304,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// before moving onto next slide, run the subroutines :)
const currentDoc = this.childDocs[this.itemIndex];
- //could i do this.childDocs[this.itemIndex] for first arg?
+ // could i do this.childDocs[this.itemIndex] for first arg?
this.runSubroutines(TreeView.GetRunningChildren.get(currentDoc)?.(), this.childDocs[this.itemIndex + 1]);
this.nextSlide(curLast + 1 === this.childDocs.length ? (this.layoutDoc.presLoop ? 0 : curLast) : curLast + 1);
@@ -334,7 +324,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// Called when the user activates 'back' - to move to the previous part of the pres. trail
@action
back = () => {
- const activeItem: Doc = this.activeItem;
+ const { activeItem } = this;
let prevSelected = this.itemIndex;
// Functionality for group with up
let didZoom = activeItem.presentation_movement;
@@ -353,8 +343,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return this.itemIndex;
};
- //The function that is called when a document is clicked or reached through next or back.
- //it'll also execute the necessary actions if presentation is playing.
+ // The function that is called when a document is clicked or reached through next or back.
+ // it'll also execute the necessary actions if presentation is playing.
@undoBatch
public gotoDocument = action((index: number, from?: Doc, group?: boolean, finished?: () => void) => {
Doc.UnBrushAllDocs();
@@ -371,13 +361,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.startTempMedia(this.targetDoc, this.activeItem);
}
if (!group) this.clearSelectedArray();
- this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); //Update selected array
+ this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); // Update selected array
this.turnOffEdit();
- this.navigateToActiveItem(finished); //Handles movement to element only when presentationTrail is list
- this.doHideBeforeAfter(); //Handles hide after/before
+ this.navigateToActiveItem(finished); // Handles movement to element only when presentationTrail is list
+ this.doHideBeforeAfter(); // Handles hide after/before
}
});
- static pinDataTypes(target?: Doc): pinDataTypes {
+ static pinDataTypes(target?: Doc): dataTypes {
const targetType = target?.type as any;
const inkable = [DocumentType.INK].includes(targetType);
const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetType) || target?._type_collection === CollectionViewType.Stacking;
@@ -388,19 +378,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const datarange = [DocumentType.FUNCPLOT].includes(targetType);
const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG, DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined;
const poslayoutview = [DocumentType.COL].includes(targetType) && target?.activeFrame === undefined;
- const type_collection = targetType === DocumentType.COL;
+ const typeCollection = targetType === DocumentType.COL;
const filters = true;
const pivot = true;
const dataannos = false;
- return { scrollable, pannable, inkable, type_collection, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos };
+ return { scrollable, pannable, inkable, type_collection: typeCollection, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos };
}
@action
- playAnnotation = (anno: AudioField) => {};
+ playAnnotation = (/* anno: AudioField */) => {
+ /* empty */
+ };
@action
- static restoreTargetDocView(bestTargetView: Opt<DocumentView>, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.config_pinLayout), pinDataTypes?: pinDataTypes, targetDoc?: Doc) {
+ // eslint-disable-next-line default-param-last
+ static restoreTargetDocView(bestTargetView: Opt<DocumentView>, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.config_pinLayout), pinDataTypes?: dataTypes, targetDoc?: Doc) {
const bestTarget = bestTargetView?.Document ?? (targetDoc?.layout_unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc);
- if (!bestTarget) return;
+ if (!bestTarget) return undefined;
let changed = false;
if (pinDocLayout) {
if (
@@ -417,20 +410,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
bestTarget.width = NumCast(activeItem.config_width, NumCast(bestTarget.width));
bestTarget.height = NumCast(activeItem.config_height, NumCast(bestTarget.height));
bestTarget[TransitionTimer] && clearTimeout(bestTarget[TransitionTimer]);
- bestTarget[TransitionTimer] = setTimeout(() => (bestTarget[TransitionTimer] = bestTarget._dataTransition = undefined), transTime + 10);
+ bestTarget[TransitionTimer] = setTimeout(() => {
+ bestTarget[TransitionTimer] = bestTarget._dataTransition = undefined;
+ }, transTime + 10);
changed = true;
}
}
const activeFrame = activeItem.config_activeFrame ?? activeItem.config_currentFrame;
if (activeFrame !== undefined) {
- const transTime = NumCast(activeItem.presentation_transition, 500);
+ const frameTime = NumCast(activeItem.presentation_transition, 500);
const acontext = activeItem.config_activeFrame !== undefined ? DocCast(DocCast(activeItem.presentation_targetDoc).embedContainer) : DocCast(activeItem.presentation_targetDoc);
const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext).annotationOn) : acontext;
if (context) {
- const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.CollectionFreeFormView;
+ const ffview = CollectionFreeFormView.from(DocumentView.getFirstDocumentView(context));
if (ffview?.childDocs) {
- PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, transTime);
+ PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, frameTime);
ffview.layoutDoc._currentFrame = NumCast(activeFrame);
}
}
@@ -443,12 +438,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
else {
const bestTargetData = bestTarget[DocData];
const current = bestTargetData[fkey];
- const hash = bestTargetData[fkey] ? stringHash(Field.toString(bestTargetData[fkey] as Field)) : undefined;
+ const hash = bestTargetData[fkey] ? stringHash(Field.toString(bestTargetData[fkey] as FieldType)) : undefined;
if (hash) bestTargetData[fkey + '_' + hash] = current instanceof ObjectField ? current[Copy]() : current;
bestTargetData[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
}
bestTarget[fkey + '_usePath'] = activeItem.config_usePath;
- setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10);
+ setTimeout(() => {
+ bestTarget._dataTransition = undefined;
+ }, transTime + 10);
}
if (pinDataTypes?.datarange || (!pinDataTypes && activeItem.config_xRange !== undefined)) {
if (bestTarget.xRange !== activeItem.config_xRange) {
@@ -590,7 +587,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
Doc.AddDocToList(bestTarget[DocData], layoutField, doc);
}
});
- setTimeout(() => Array.from(transitioned).forEach(action(doc => (doc._dataTransition = undefined))), transTime + 10);
+ setTimeout(
+ () =>
+ Array.from(transitioned).forEach(
+ action(doc => {
+ doc._dataTransition = undefined;
+ })
+ ),
+ transTime + 10
+ );
}
if ((pinDataTypes?.pannable || (!pinDataTypes && (activeItem.config_viewBounds !== undefined || activeItem.config_panX !== undefined || activeItem.config_viewScale !== undefined))) && !bestTarget.isGroup) {
const contentBounds = Cast(activeItem.config_viewBounds, listSpec('number'));
@@ -598,132 +603,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] };
bestTarget._freeform_panX = viewport.panX;
bestTarget._freeform_panY = viewport.panY;
- const dv = DocumentManager.Instance.getDocumentView(bestTarget);
+ const dv = DocumentView.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);
activeItem.presentation_movement === PresMovement.Zoom && (bestTarget._freeform_scale = computedScale);
dv.ComponentView?.brushView?.(viewport, transTime, 2500);
}
- } else {
- if (bestTarget._freeform_panX !== activeItem.config_panX || bestTarget._freeform_panY !== activeItem.config_panY || bestTarget._freeform_scale !== activeItem.config_viewScale) {
- bestTarget._freeform_panX = activeItem.config_panX ?? bestTarget._freeform_panX;
- bestTarget._freeform_panY = activeItem.config_panY ?? bestTarget._freeform_panY;
- bestTarget._freeform_scale = activeItem.config_viewScale ?? bestTarget._freeform_scale;
- changed = true;
- }
+ } else if (bestTarget._freeform_panX !== activeItem.config_panX || bestTarget._freeform_panY !== activeItem.config_panY || bestTarget._freeform_scale !== activeItem.config_viewScale) {
+ bestTarget._freeform_panX = activeItem.config_panX ?? bestTarget._freeform_panX;
+ bestTarget._freeform_panY = activeItem.config_panY ?? bestTarget._freeform_panY;
+ bestTarget._freeform_scale = activeItem.config_viewScale ?? bestTarget._freeform_scale;
+ changed = true;
}
}
if (changed) {
return bestTargetView?.setViewTransition('all', transTime);
}
- }
-
- /// copies values from the targetDoc (which is the prototype of the pinDoc) to
- /// reserved fields on the pinDoc so that those values can be restored to the
- /// target doc when navigating to it.
- @action
- static pinDocView(pinDoc: Doc, pinProps: PinProps, targetDoc: Doc) {
- pinDoc.presentation = true;
- pinDoc.config = '';
- if (pinProps.pinDocLayout) {
- pinDoc.config_pinLayout = true;
- pinDoc.config_x = NumCast(targetDoc.x);
- pinDoc.config_y = NumCast(targetDoc.y);
- pinDoc.config_rotation = NumCast(targetDoc.rotation);
- pinDoc.config_width = NumCast(targetDoc.width);
- pinDoc.config_height = NumCast(targetDoc.height);
- }
- if (pinProps.pinAudioPlay) pinDoc.presPlayAudio = true;
- if (pinProps.pinData) {
- pinDoc.config_pinData =
- pinProps.pinData.scrollable ||
- pinProps.pinData.temporal ||
- pinProps.pinData.pannable ||
- pinProps.pinData.type_collection ||
- pinProps.pinData.clippable ||
- pinProps.pinData.datarange ||
- pinProps.pinData.dataview ||
- pinProps.pinData.poslayoutview ||
- pinProps?.activeFrame !== undefined;
- const fkey = Doc.LayoutFieldKey(targetDoc);
- if (pinProps.pinData.dataview) {
- pinDoc.config_usePath = targetDoc[fkey + '_usePath'];
- pinDoc.config_data = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data;
- }
- if (pinProps.pinData.dataannos) {
- const fkey = Doc.LayoutFieldKey(targetDoc);
- pinDoc.config_annotations = new List<Doc>(DocListCast(targetDoc[DocData][fkey + '_annotations']).filter(doc => !doc.layout_unrendered));
- }
- if (pinProps.pinData.inkable) {
- pinDoc.config_fillColor = targetDoc.fillColor;
- pinDoc.config_color = targetDoc.color;
- pinDoc.config_width = targetDoc._width;
- pinDoc.config_height = targetDoc._height;
- }
- if (pinProps.pinData.scrollable) pinDoc.config_scrollTop = targetDoc._layout_scrollTop;
- if (pinProps.pinData.clippable) {
- const fkey = Doc.LayoutFieldKey(targetDoc);
- pinDoc.config_clipWidth = targetDoc[fkey + '_clipWidth'];
- }
- if (pinProps.pinData.datarange) {
- pinDoc.config_xRange = undefined; //targetDoc?.xrange;
- pinDoc.config_yRange = undefined; //targetDoc?.yrange;
- }
- if (pinProps.pinData.map) {
- // pinDoc.config_latitude = targetDoc?.latitude;
- // pinDoc.config_longitude = targetDoc?.longitude;
- pinDoc.config_map_zoom = targetDoc?.map_zoom;
- pinDoc.config_map_type = targetDoc?.map_type;
- //...
- }
- if (pinProps.pinData.poslayoutview)
- pinDoc.config_pinLayoutData = new List<string>(
- DocListCast(targetDoc[fkey] as ObjectField).map(d =>
- JSON.stringify({
- id: d[Id],
- x: NumCast(d.x),
- y: NumCast(d.y),
- w: NumCast(d._width),
- h: NumCast(d._height),
- fill: StrCast(d._fillColor),
- back: StrCast(d._backgroundColor),
- data: SerializationHelper.Serialize(d.data instanceof ObjectField ? d.data[Copy]() : ''),
- text: SerializationHelper.Serialize(d.text instanceof ObjectField ? d.text[Copy]() : ''),
- })
- )
- );
- if (pinProps.pinData.type_collection) pinDoc.config_viewType = targetDoc._type_collection;
- if (pinProps.pinData.filters) pinDoc.config_docFilters = ObjectField.MakeCopy(targetDoc.childFilters as ObjectField);
- if (pinProps.pinData.pivot) pinDoc.config_pivotField = targetDoc._pivotField;
- if (pinProps.pinData.pannable) {
- pinDoc.config_panX = NumCast(targetDoc._freeform_panX);
- pinDoc.config_panY = NumCast(targetDoc._freeform_panY);
- pinDoc.config_viewScale = NumCast(targetDoc._freeform_scale, 1);
- }
- if (pinProps.pinData.temporal) {
- pinDoc.config_clipStart = targetDoc._layout_currentTimecode;
- const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], NumCast(targetDoc.config_clipStart) + 0.1);
- pinDoc.config_clipEnd = NumCast(pinDoc.config_clipStart) + NumCast(targetDoc.clipEnd, duration);
- }
- }
- if (pinProps?.pinViewport) {
- // If pinWithView option set then update scale and x / y props of slide
- const bounds = pinProps.pinViewport;
- pinDoc.config_pinView = true;
- pinDoc.config_viewScale = NumCast(targetDoc._freeform_scale, 1);
- pinDoc.config_panX = bounds.left + bounds.width / 2;
- pinDoc.config_panY = bounds.top + bounds.height / 2;
- pinDoc.config_viewBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
- }
- }
-
- @action
- static reversePin(pinDoc: Doc, targetDoc: Doc) {
- // const fkey = Doc.LayoutFieldKey(targetDoc);
- pinDoc.config_data = targetDoc.data;
-
- console.log(pinDoc.presData);
+ return undefined;
}
/**
@@ -735,8 +632,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
* on the right.
*/
navigateToActiveItem = (afterNav?: () => void) => {
- const activeItem: Doc = this.activeItem;
- const targetDoc: Doc = this.targetDoc;
+ const { activeItem, targetDoc } = this;
const finished = () => {
afterNav?.();
targetDoc[Animation] = undefined;
@@ -746,8 +642,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const eleViewCache = Array.from(this._eleArray);
const resetSelection = action(() => {
if (!this._props.isSelected()) {
- const presDocView = DocumentManager.Instance.getDocumentView(this.Document);
- if (presDocView) SelectionManager.SelectView(presDocView, false);
+ const presDocView = DocumentView.getDocumentView(this.Document);
+ if (presDocView) DocumentView.SelectView(presDocView, false);
this.clearSelectedArray();
selViewCache.forEach(doc => this.addToSelectedArray(doc));
this._dragArray.splice(0, this._dragArray.length, ...dragViewCache);
@@ -762,7 +658,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: () => void) {
if (activeItem.presentation_movement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) {
- (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.();
+ (DocumentView.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.();
return;
}
const effect = activeItem.presentation_effect && activeItem.presentation_effect !== PresEffect.None ? activeItem.presentation_effect : undefined;
@@ -775,7 +671,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
effect: activeItem,
noSelect: true,
openLocation: targetDoc.type === DocumentType.PRES ? ((OpenWhere.replace + ':' + PresBox.PanelName) as OpenWhere) : OpenWhere.addLeft,
- anchorDoc: activeItem,
easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any,
zoomTextSelections: BoolCast(activeItem.presentation_zoomText),
playAudio: BoolCast(activeItem.presPlayAudio),
@@ -783,19 +678,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
if (activeItem.presentation_openInLightbox) {
const context = DocCast(targetDoc.annotationOn) ?? targetDoc;
- if (!DocumentManager.Instance.getLightboxDocumentView(context)) {
+ if (!DocumentView.getLightboxDocumentView(context)) {
LightboxView.Instance.SetLightboxDoc(context);
}
}
if (targetDoc) {
if (activeItem.presentation_targetDoc instanceof Doc) activeItem.presentation_targetDoc[Animation] = undefined;
- DocumentManager.Instance.AddViewRenderedCb(LightboxView.LightboxDoc, dv => {
+ DocumentView.addViewRenderedCb(LightboxView.LightboxDoc, () => {
// if target or the doc it annotates is not in the lightbox, then close the lightbox
- if (!DocumentManager.Instance.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) {
+ if (!DocumentView.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) {
LightboxView.Instance.SetLightboxDoc(undefined);
}
- DocumentManager.Instance.showDocument(targetDoc, options, finished);
+ DocumentView.showDocument(targetDoc, options, finished);
});
} else finished?.();
}
@@ -822,7 +717,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
opacity = 0;
} else if (index === this.itemIndex || !curDoc.presentation_hideAfter) {
opacity = 1;
- setTimeout(() => (tagDoc._dataTransition = undefined), 1000);
+ setTimeout(() => {
+ tagDoc._dataTransition = undefined;
+ }, 1000);
}
}
const hidingIndAft =
@@ -848,16 +745,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
_exitTrail: Opt<() => void>;
- PlayTrail = (docs: Doc[]) => {
+ playTrail = (docs: Doc[]) => {
const savedStates = docs.map(doc => {
switch (doc.type) {
case DocumentType.COL:
- if (doc._type_collection === CollectionViewType.Freeform) return { type: CollectionViewType.Freeform, doc, x: NumCast(doc.freeform_panX), y: NumCast(doc.freeform_panY), s: NumCast(doc.freeform_scale) };
+ if (doc._type_collection === CollectionViewType.Freeform) {
+ return { type: CollectionViewType.Freeform, doc, x: NumCast(doc.freeform_panX), y: NumCast(doc.freeform_panY), s: NumCast(doc.freeform_scale) };
+ }
break;
case DocumentType.INK:
if (doc.data instanceof InkField) {
return { type: doc.type, doc, data: doc.data?.[Copy](), fillColor: doc.fillColor, color: doc.color, x: NumCast(doc.x), y: NumCast(doc.y) };
}
+ break;
+ default:
}
return undefined;
});
@@ -865,7 +766,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._exitTrail = () => {
savedStates
.filter(savedState => savedState)
- .map(savedState => {
+ .forEach(savedState => {
switch (savedState?.type) {
case CollectionViewType.Freeform:
{
@@ -885,6 +786,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
doc.color = color;
}
break;
+ default:
}
});
LightboxView.Instance.SetLightboxDoc(undefined);
@@ -903,8 +805,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
};
- //The function that resets the presentation by removing every action done by it. It also
- //stops the presentaton.
+ // The function that resets the presentation by removing every action done by it. It also
+ // stops the presentaton.
resetPresentation = () => {
this.childDocs
.map(doc => PresBox.targetRenderedDoc(doc))
@@ -921,12 +823,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// The function allows for viewing the pres path on toggle
@action togglePath = (off?: boolean) => {
this._pathBoolean = off ? false : !this._pathBoolean;
- CollectionFreeFormView.ShowPresPaths = this._pathBoolean;
+ SnappingManager.SetShowPresPaths(this._pathBoolean);
};
// The function allows for expanding the view of pres on toggle
@action toggleExpandMode = () => {
- runInAction(() => (this._expandBoolean = !this._expandBoolean));
+ runInAction(() => {
+ this._expandBoolean = !this._expandBoolean;
+ });
this.Document.expandBoolean = this._expandBoolean;
this.childDocs.forEach(doc => {
doc.presentation_expandInlineButton = this._expandBoolean;
@@ -942,7 +846,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const startInd = NumCast(doc.presentation_indexedStart);
this.progressivizedItems(doc)
?.slice(startInd)
- .forEach(indexedDoc => (indexedDoc.opacity = 0));
+ .forEach(indexedDoc => {
+ indexedDoc.opacity = 0;
+ });
doc.presentation_indexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, startInd);
}
// if (doc.presentation_hide && this.childDocs.indexOf(doc) === startIndex) tagDoc.opacity = 0;
@@ -993,13 +899,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static minimizedWidth = 198;
public static OpenPresMinimized(doc: Doc, pt: number[]) {
- doc.overlayX = pt[0];
- doc.overlayY = pt[1];
+ [doc.overlayX, doc.overlayY] = pt;
doc._height = 30;
doc._width = PresBox.minimizedWidth;
Doc.AddToMyOverlay(doc);
PresBox.Instance?.initializePresState(PresBox.Instance.itemIndex);
- return (doc.presentation_status = PresStatus.Manual);
+ doc.presentation_status = PresStatus.Manual;
+ return doc.presentation_status;
}
/**
@@ -1008,12 +914,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
*/
@undoBatch
viewChanged = action((e: React.ChangeEvent) => {
- //@ts-ignore
- const type_collection = e.target.selectedOptions[0].value as CollectionViewType;
- this.layoutDoc.presFieldKey = this.fieldKey + (type_collection === CollectionViewType.Tree ? '-linearized' : '');
+ const typeCollection = (e.target as any).selectedOptions[0].value as CollectionViewType;
+ this.layoutDoc.presFieldKey = this.fieldKey + (typeCollection === CollectionViewType.Tree ? '-linearized' : '');
// pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
- [CollectionViewType.Tree || CollectionViewType.Stacking].includes(type_collection) && (this.Document._pivotField = undefined);
- this.Document._type_collection = type_collection;
+ [CollectionViewType.Tree || CollectionViewType.Stacking].includes(typeCollection) && (this.Document._pivotField = undefined);
+ this.Document._type_collection = typeCollection;
if (this.isTreeOrStack) {
this.layoutDoc._gridGap = 0;
}
@@ -1025,10 +930,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
*/
// @undoBatch
mediaStopChanged = action((e: React.ChangeEvent) => {
- const activeItem: Doc = this.activeItem;
- //@ts-ignore
- const stopDoc = e.target.selectedOptions[0].value as string;
- const stopDocIndex: number = Number(stopDoc[0]);
+ const { activeItem } = this;
+ const stopDoc = (e.target as any).selectedOptions[0].value as string;
+ const stopDocIndex = Number(stopDoc[0]);
activeItem.mediaStopDoc = stopDocIndex;
if (this.childDocs[stopDocIndex - 1].mediaStopTriggerList) {
const list = DocListCast(this.childDocs[stopDocIndex - 1].mediaStopTriggerList);
@@ -1049,10 +953,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return StrCast(activeItem.presentation_movement);
});
- whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isChildActive = isActive)));
+ whenChildContentsActiveChanged = action((isActive: boolean) => {
+ this._props.whenChildContentsActiveChanged((this._isChildActive = isActive));
+ });
// For dragging documents into the presentation trail
addDocumentFilter = (docs: Doc[]) => {
- docs.forEach((doc, i) => {
+ const results = docs.map(doc => {
if (doc.presentation_targetDoc) return true;
if (doc.type === DocumentType.LABEL) {
const audio = Cast(doc.annotationOn, Doc, null);
@@ -1065,17 +971,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return false;
}
} else if (doc.type !== DocumentType.PRES) {
+ // eslint-disable-next-line operator-assignment
if (!doc.presentation_targetDoc) doc.title = doc.title + ' - Slide';
doc.presentation_targetDoc = doc.createdFrom ?? doc; // dropped document will be a new embedding of an embedded document somewhere else.
doc.presentation_movement = PresMovement.Zoom;
if (this._expandBoolean) doc.presentation_expandInlineButton = true;
}
+ return false;
});
- return true;
+ return !results.some(r => !r);
};
childLayoutTemplate = () => Docs.Create.PresElementBoxDocument();
- removeDocument = (doc: Doc | Doc[]) => !(doc instanceof Doc ? [doc] : doc).map(d => Doc.RemoveDocFromList(this.Document, this.fieldKey, d)).some(p => !p);
+ removeDocument = (doc: Doc | Doc[]) =>
+ !toList(doc)
+ .map(d => Doc.RemoveDocFromList(this.Document, this.fieldKey, d))
+ .some(p => !p);
getTransform = () => this.ScreenToLocalBoxXf().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
panelHeight = () => this._props.PanelHeight() - 40;
/**
@@ -1092,42 +1003,46 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const tagDoc = Cast(curDoc.presentation_targetDoc, Doc, null);
if (curDoc && curDoc === this.activeItem)
return (
+ // eslint-disable-next-line react/no-array-index-key
<div key={index} className="selectedList-items">
<b>
{index + 1}. {curDoc.title}
</b>
</div>
);
- else if (tagDoc)
+ if (tagDoc)
return (
+ // eslint-disable-next-line react/no-array-index-key
<div key={index} className="selectedList-items">
{index + 1}. {curDoc.title}
</div>
);
- else if (curDoc)
+ if (curDoc)
return (
+ // eslint-disable-next-line react/no-array-index-key
<div key={index} className="selectedList-items">
{index + 1}. {curDoc.title}
</div>
);
+ return null;
});
}
@action
selectPres = () => {
- const presDocView = DocumentManager.Instance.getDocumentView(this.Document);
- presDocView && SelectionManager.SelectView(presDocView, false);
+ const presDocView = DocumentView.getDocumentView(this.Document);
+ presDocView && DocumentView.SelectView(presDocView, false);
};
- focusElement = (doc: Doc, options: FocusViewOptions) => {
+ focusElement = (doc: Doc) => {
this.selectElement(doc);
return undefined;
};
- //Regular click
+ // Regular click
@action
selectElement = (doc: Doc, noNav = false) => {
- CollectionStackedTimeline.CurrentlyPlaying?.map((clip, i) => clip?.ComponentView?.Pause?.());
+ DocumentView.CurrentlyPlaying?.map(clip => clip?.ComponentView?.Pause?.());
if (noNav) {
const index = this.childDocs.indexOf(doc);
if (index >= 0 && index < this.childDocs.length) {
@@ -1139,7 +1054,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.updateCurrentPresentation(DocCast(doc.embedContainer));
};
- //Command click
+ // Command click
@action
multiSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => {
if (!this.selectedArray.has(doc)) {
@@ -1154,7 +1069,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.selectPres();
};
- //Shift click
+ // Shift click
@action
shiftSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => {
this.clearSelectedArray();
@@ -1169,7 +1084,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.selectPres();
};
- //regular click
+ // regular click
@action
regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, noNav: boolean, selectPres = true) => {
this.clearSelectedArray();
@@ -1200,9 +1115,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (this.layoutDoc.presentation_status === 'edit') {
undoBatch(
action(() => {
- for (const doc of this.selectedArray) {
- this.removeDocument(doc);
- }
+ Array.from(this.selectedArray).forEach(doc => this.removeDocument(doc));
this.clearSelectedArray();
this._eleArray.length = 0;
this._dragArray.length = 0;
@@ -1269,8 +1182,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.childDocs.forEach(doc => this.addToSelectedArray(doc));
handled = true;
}
- default:
break;
+ default:
}
if (handled) {
e.stopPropagation();
@@ -1285,11 +1198,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const order: JSX.Element[] = [];
const docs = new Set<Doc>();
const presCollection = collection;
- const dv = DocumentManager.Instance.getDocumentView(presCollection);
+ const dv = DocumentView.getDocumentView(presCollection);
this.childDocs.forEach((doc, index) => {
const tagDoc = PresBox.targetRenderedDoc(doc);
const srcContext = Cast(tagDoc.embedContainer, Doc, null);
const labelCreator = (top: number, left: number, edge: number, fontSize: number) => (
+ // eslint-disable-next-line react/no-array-index-key
<div className="pathOrder" key={tagDoc.id + 'pres' + index} style={{ top, left, width: edge, height: edge, fontSize }} onClick={() => this.selectElement(doc)}>
<div className="pathOrder-frame">{index + 1}</div>
</div>
@@ -1322,7 +1236,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
order.push(
<>
{labelCreator(top - indEdge / 2, left - indEdge / 2, indEdge, indFontSize)}
- <div className="pathOrder-presPinView" style={{ top, left, width, height, borderWidth: indEdge / 10 }}></div>
+ <div className="pathOrder-presPinView" style={{ top, left, width, height, borderWidth: indEdge / 10 }} />
</>
);
}
@@ -1345,17 +1259,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
.filter(doc => PresBox.targetRenderedDoc(doc)?.embedContainer === collection)
.forEach((doc, index) => {
const tagDoc = PresBox.targetRenderedDoc(doc);
- if (tagDoc) {
- const n1x = NumCast(tagDoc.x) + NumCast(tagDoc._width) / 2;
- const n1y = NumCast(tagDoc.y) + NumCast(tagDoc._height) / 2;
- if ((index = 0)) pathPoints = n1x + ',' + n1y;
- else pathPoints = pathPoints + ' ' + n1x + ',' + n1y;
- } else if (doc.config_pinView) {
- const n1x = NumCast(doc.config_panX);
- const n1y = NumCast(doc.config_panY);
- if ((index = 0)) pathPoints = n1x + ',' + n1y;
- else pathPoints = pathPoints + ' ' + n1x + ',' + n1y;
- }
+ const [n1x, n1y] = tagDoc //
+ ? [NumCast(tagDoc.x) + NumCast(tagDoc._width) / 2, NumCast(tagDoc.y) + NumCast(tagDoc._height) / 2]
+ : [NumCast(doc.config_panX), NumCast(doc.config_panY)];
+
+ if (index === 0) pathPoints = n1x + ',' + n1y;
+ else pathPoints = pathPoints + ' ' + n1x + ',' + n1y;
});
return (
<>
@@ -1401,7 +1310,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@undoBatch
updateTransitionTime = (number: String, change?: number) => {
- PresBox.SetTransitionTime(number, (timeInMS: number) => this.selectedArray.forEach(doc => (doc.presentation_transition = timeInMS)), change);
+ PresBox.SetTransitionTime(
+ number,
+ (timeInMS: number) =>
+ this.selectedArray.forEach(doc => {
+ doc.presentation_transition = timeInMS;
+ }),
+ change
+ );
};
// Converts seconds to ms and updates presentation_transition
@@ -1411,7 +1327,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (change) scale += change;
if (scale < 0.01) scale = 0.01;
if (scale > 1) scale = 1;
- this.selectedArray.forEach(doc => (doc.config_zoom = scale));
+ this.selectedArray.forEach(doc => {
+ doc.config_zoom = scale;
+ });
};
/*
@@ -1423,76 +1341,96 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (change) timeInMS += change;
if (timeInMS < 100) timeInMS = 100;
if (timeInMS > 20000) timeInMS = 20000;
- this.selectedArray.forEach(doc => (doc.presentation_duration = timeInMS));
+ this.selectedArray.forEach(doc => {
+ doc.presentation_duration = timeInMS;
+ });
};
@undoBatch
- updateMovement = action((movement: PresMovement, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_movement = movement)));
+ updateMovement = action((movement: PresMovement, all?: boolean) =>
+ (all ? this.childDocs : this.selectedArray).forEach(doc => {
+ doc.presentation_movement = movement;
+ })
+ );
@undoBatch
updateHideBefore = (activeItem: Doc) => {
activeItem.presentation_hideBefore = !activeItem.presentation_hideBefore;
- this.selectedArray.forEach(doc => (doc.presentation_hideBefore = activeItem.presentation_hideBefore));
+ this.selectedArray.forEach(doc => {
+ doc.presentation_hideBefore = activeItem.presentation_hideBefore;
+ });
};
@undoBatch
updateHide = (activeItem: Doc) => {
activeItem.presentation_hide = !activeItem.presentation_hide;
- this.selectedArray.forEach(doc => (doc.presentation_hide = activeItem.presentation_hide));
+ this.selectedArray.forEach(doc => {
+ doc.presentation_hide = activeItem.presentation_hide;
+ });
};
@undoBatch
updateHideAfter = (activeItem: Doc) => {
activeItem.presentation_hideAfter = !activeItem.presentation_hideAfter;
- this.selectedArray.forEach(doc => (doc.presentation_hideAfter = activeItem.presentation_hideAfter));
+ this.selectedArray.forEach(doc => {
+ doc.presentation_hideAfter = activeItem.presentation_hideAfter;
+ });
};
@undoBatch
updateOpenDoc = (activeItem: Doc) => {
activeItem.presentation_openInLightbox = !activeItem.presentation_openInLightbox;
- this.selectedArray.forEach(doc => (doc.presentation_openInLightbox = activeItem.presentation_openInLightbox));
+ this.selectedArray.forEach(doc => {
+ doc.presentation_openInLightbox = activeItem.presentation_openInLightbox;
+ });
};
@undoBatch
updateEaseFunc = (activeItem: Doc) => {
activeItem.presEaseFunc = activeItem.presEaseFunc === 'linear' ? 'ease' : 'linear';
- this.selectedArray.forEach(doc => (doc.presEaseFunc = activeItem.presEaseFunc));
+ this.selectedArray.forEach(doc => {
+ doc.presEaseFunc = activeItem.presEaseFunc;
+ });
};
@undoBatch
- updateEffectDirection = (effect: PresEffectDirection, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_effectDirection = effect));
+ updateEffectDirection = (effect: PresEffectDirection, all?: boolean) =>
+ (all ? this.childDocs : this.selectedArray).forEach(doc => {
+ doc.presentation_effectDirection = effect;
+ });
@undoBatch
- updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presentation_effect = effect)));
+ updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) =>
+ (all ? this.childDocs : this.selectedArray).forEach(doc => {
+ bullet ? (doc.presBulletEffect = effect) : (doc.presentation_effect = effect);
+ });
static _sliderBatch: any;
static endBatch = () => {
PresBox._sliderBatch.end();
document.removeEventListener('pointerup', PresBox.endBatch, true);
};
- public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => {
- return (
- <input
- type="range"
- step={step}
- min={min}
- max={max}
- value={value}
- readOnly={true}
- style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)`, background: SettingsManager.userColor, color: SettingsManager.userVariantColor }}
- className={`toolbar-slider ${active ? '' : 'none'}`}
- onPointerDown={e => {
- PresBox._sliderBatch = UndoManager.StartBatch('pres slider');
- document.addEventListener('pointerup', PresBox.endBatch, true);
- e.stopPropagation();
- }}
- onChange={e => {
- e.stopPropagation();
- change(e.target.value);
- }}
- />
- );
- };
+ public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => (
+ <input
+ type="range"
+ step={step}
+ min={min}
+ max={max}
+ value={value}
+ readOnly
+ style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)`, background: SnappingManager.userColor, color: SnappingManager.userVariantColor }}
+ className={`toolbar-slider ${active ? '' : 'none'}`}
+ onPointerDown={e => {
+ PresBox._sliderBatch = UndoManager.StartBatch('pres slider');
+ document.addEventListener('pointerup', PresBox.endBatch, true);
+ e.stopPropagation();
+ }}
+ onChange={e => {
+ e.stopPropagation();
+ change(e.target.value);
+ }}
+ />
+ );
@undoBatch
applyTo = (array: Doc[]) => {
@@ -1500,17 +1438,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.updateEffect(this.activeItem.presentation_effect as PresEffect, false, true);
this.updateEffect(this.activeItem.presBulletEffect as PresEffect, true, true);
this.updateEffectDirection(this.activeItem.presentation_effectDirection as PresEffectDirection, true);
- const { presentation_transition, presentation_duration, presentation_hideBefore, presentation_hideAfter } = this.activeItem;
+ // eslint-disable-next-line camelcase
+ const { presentation_transition: pt, presentation_duration: pd, presentation_hideBefore: ph, presentation_hideAfter: pa } = this.activeItem;
array.forEach(curDoc => {
- curDoc.presentation_transition = presentation_transition;
- curDoc.presentation_duration = presentation_duration;
- curDoc.presentation_hideBefore = presentation_hideBefore;
- curDoc.presentation_hideAfter = presentation_hideAfter;
+ curDoc.presentation_transition = pt;
+ curDoc.presentation_duration = pd;
+ curDoc.presentation_hideBefore = ph;
+ curDoc.presentation_hideAfter = pa;
});
};
@computed get visibilityDurationDropdown() {
- const activeItem = this.activeItem;
+ const { activeItem } = this;
if (activeItem && this.targetDoc) {
const targetType = this.targetDoc.type;
let duration = activeItem.presentation_duration ? NumCast(activeItem.presentation_duration) / 1000 : 0;
@@ -1521,36 +1460,36 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<Tooltip title={<div className="dash-tooltip">Hide before presented</div>}>
<div
className={`ribbon-toggle ${activeItem.presentation_hideBefore ? 'active' : ''}`}
- style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hideBefore ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
+ style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presentation_hideBefore ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }}
onClick={() => this.updateHideBefore(activeItem)}>
Hide before
</div>
</Tooltip>
- <Tooltip title={<div className="dash-tooltip">{'Hide while presented'}</div>}>
+ <Tooltip title={<div className="dash-tooltip">Hide while presented</div>}>
<div
className={`ribbon-toggle ${activeItem.presentation_hide ? 'active' : ''}`}
- style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hide ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
+ style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presentation_hide ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }}
onClick={() => this.updateHide(activeItem)}>
Hide
</div>
</Tooltip>
- <Tooltip title={<div className="dash-tooltip">{'Hide after presented'}</div>}>
+ <Tooltip title={<div className="dash-tooltip">Hide after presented</div>}>
<div
className={`ribbon-toggle ${activeItem.presentation_hideAfter ? 'active' : ''}`}
- style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hideAfter ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
+ style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presentation_hideAfter ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }}
onClick={() => this.updateHideAfter(activeItem)}>
Hide after
</div>
</Tooltip>
- <Tooltip title={<div className="dash-tooltip">{'Open in lightbox view'}</div>}>
+ <Tooltip title={<div className="dash-tooltip">Open in lightbox view</div>}>
<div
className="ribbon-toggle"
style={{
- border: `solid 1px ${SettingsManager.userColor}`,
- color: SettingsManager.userColor,
- background: activeItem.presentation_openInLightbox ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+ border: `solid 1px ${SnappingManager.userColor}`,
+ color: SnappingManager.userColor,
+ background: activeItem.presentation_openInLightbox ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
}}
onClick={() => this.updateOpenDoc(activeItem)}>
Lightbox
@@ -1559,30 +1498,30 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<Tooltip title={<div className="dash-tooltip">Transition movement style</div>}>
<div
className="ribbon-toggle"
- style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presEaseFunc === 'ease' ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
+ style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presEaseFunc === 'ease' ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }}
onClick={() => this.updateEaseFunc(activeItem)}>
{`${StrCast(activeItem.presEaseFunc, 'ease')}`}
</div>
</Tooltip>
</div>
- {[DocumentType.AUDIO, DocumentType.VID].includes(targetType as any as DocumentType) ? null : (
+ {[DocumentType.AUDIO, DocumentType.VID].find(dt => dt === targetType) ? null : (
<>
<div className="ribbon-doubleButton">
<div className="presBox-subheading">Slide Duration</div>
- <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
- <input className="presBox-input" type="number" readOnly={true} value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s
+ <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
+ <input className="presBox-input" type="number" readOnly value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s
</div>
- <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}>
+ <div className="ribbon-propertyUpDown" style={{ color: SnappingManager.userBackgroundColor, background: SnappingManager.userColor }}>
<div className="ribbon-propertyUpDownItem" onClick={() => this.updateDurationTime(String(duration), 1000)}>
- <FontAwesomeIcon icon={'caret-up'} />
+ <FontAwesomeIcon icon="caret-up" />
</div>
<div className="ribbon-propertyUpDownItem" onClick={() => this.updateDurationTime(String(duration), -1000)}>
- <FontAwesomeIcon icon={'caret-down'} />
+ <FontAwesomeIcon icon="caret-down" />
</div>
</div>
</div>
{PresBox.inputter('0.1', '0.1', '20', duration, targetType !== DocumentType.AUDIO, this.updateDurationTime)}
- <div className={'slider-headers'} style={{ display: targetType === DocumentType.AUDIO ? 'none' : 'grid' }}>
+ <div className="slider-headers" style={{ display: targetType === DocumentType.AUDIO ? 'none' : 'grid' }}>
<div className="slider-text">Short</div>
<div className="slider-text">Medium</div>
<div className="slider-text">Long</div>
@@ -1592,17 +1531,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
);
}
+ return undefined;
}
@computed get progressivizeDropdown() {
- const activeItem = this.activeItem;
+ const { activeItem } = this;
if (activeItem && this.targetDoc) {
const effect = activeItem.presBulletEffect ? activeItem.presBulletEffect : PresMovement.None;
- const bulletEffect = (effect: PresEffect) => (
+ const bulletEffect = (presEffect: PresEffect) => (
<div
- className={`presBox-dropdownOption ${activeItem.presentation_effect === effect || (effect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`}
+ className={`presBox-dropdownOption ${activeItem.presentation_effect === presEffect || (presEffect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`}
onPointerDown={StopEvent}
- onClick={() => this.updateEffect(effect, true)}>
- {effect}
+ onClick={() => this.updateEffect(presEffect, true)}>
+ {presEffect}
</div>
);
return (
@@ -1611,7 +1551,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="presBox-subheading">Progressivize Collection</div>
<input
className="presBox-checkbox"
- style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
+ style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
type="checkbox"
onChange={() => {
activeItem.presentation_indexed = activeItem.presentation_indexed === undefined ? 0 : undefined;
@@ -1622,21 +1562,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// a progressivized slide doesn't have sub-slides, but rather iterates over the data list of the target being progressivized.
// to avoid creating a new slide to correspond to each of the target's data list, we create a computedField to refernce the target's data list.
let dataField = Doc.LayoutFieldKey(tagDoc);
- if (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField = dataField + '_annotations';
+ if (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField += '_annotations';
if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc.annotationOn?.["${dataField}"]`);
else activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc?.["${dataField}"]`);
}}
- checked={Cast(activeItem.presentation_indexed, 'number', null) !== undefined ? true : false}
+ checked={Cast(activeItem.presentation_indexed, 'number', null) !== undefined}
/>
</div>
<div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
<div className="presBox-subheading">Progressivize First Bullet</div>
<input
className="presBox-checkbox"
- style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
+ style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
type="checkbox"
- onChange={() => (activeItem.presentation_indexedStart = activeItem.presentation_indexedStart ? 0 : 1)}
+ onChange={() => {
+ activeItem.presentation_indexedStart = activeItem.presentation_indexedStart ? 0 : 1;
+ }}
checked={!NumCast(activeItem.presentation_indexedStart)}
/>
</div>
@@ -1644,9 +1586,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="presBox-subheading">Expand Current Bullet</div>
<input
className="presBox-checkbox"
- style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
+ style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
type="checkbox"
- onChange={() => (activeItem.presBulletExpand = !activeItem.presBulletExpand)}
+ onChange={() => {
+ activeItem.presBulletExpand = !activeItem.presBulletExpand;
+ }}
checked={BoolCast(activeItem.presBulletExpand)}
/>
</div>
@@ -1660,20 +1604,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._openBulletEffectDropdown = !this._openBulletEffectDropdown;
})}
style={{
- color: SettingsManager.userColor,
- background: SettingsManager.userVariantColor,
+ color: SnappingManager.userColor,
+ background: SnappingManager.userVariantColor,
borderBottomLeftRadius: this._openBulletEffectDropdown ? 0 : 5,
- border: this._openBulletEffectDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`,
+ border: this._openBulletEffectDropdown ? `solid 2px ${SnappingManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`,
}}>
{effect?.toString()}
- <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openBulletEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
+ <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openBulletEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon="angle-down" />
<div
- className={'presBox-dropdownOptions'}
- style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none', color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}
+ className="presBox-dropdownOptions"
+ style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}
onPointerDown={e => e.stopPropagation()}>
{Object.values(PresEffect)
.filter(v => isNaN(Number(v)))
- .map(effect => bulletEffect(effect))}
+ .map(peffect => bulletEffect(peffect))}
</div>
</div>
</div>
@@ -1683,7 +1627,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return null;
}
@computed get transitionDropdown() {
- const activeItem = this.activeItem;
+ const { activeItem } = this;
const preseEffect = (effect: PresEffect) => (
<div
className={`presBox-dropdownOption ${activeItem.presentation_effect === effect || (effect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`}
@@ -1698,7 +1642,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
);
const presDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => {
- const color = activeItem.presentation_effectDirection === direction || (direction === PresEffectDirection.Center && !activeItem.presentation_effectDirection) ? SettingsManager.userVariantColor : SettingsManager.userColor;
+ const color = activeItem.presentation_effectDirection === direction || (direction === PresEffectDirection.Center && !activeItem.presentation_effectDirection) ? SnappingManager.userVariantColor : SnappingManager.userColor;
return (
<Tooltip title={<div className="dash-tooltip">{direction}</div>}>
<div
@@ -1733,20 +1677,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._openMovementDropdown = !this._openMovementDropdown;
})}
style={{
- color: SettingsManager.userColor,
- background: SettingsManager.userVariantColor,
+ color: SnappingManager.userColor,
+ background: SnappingManager.userVariantColor,
borderBottomLeftRadius: this._openMovementDropdown ? 0 : 5,
- border: this._openMovementDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`,
+ border: this._openMovementDropdown ? `solid 2px ${SnappingManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`,
}}>
{this.movementName(activeItem)}
- <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
+ <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon="angle-down" />
<div
className="presBox-dropdownOptions"
- id={'presBoxMovementDropdown'}
+ id="presBoxMovementDropdown"
onPointerDown={StopEvent}
style={{
- color: SettingsManager.userColor,
- background: SettingsManager.userBackgroundColor,
+ color: SnappingManager.userColor,
+ background: SnappingManager.userBackgroundColor,
display: this._openMovementDropdown ? 'grid' : 'none',
}}>
{presMovement(PresMovement.None)}
@@ -1758,35 +1702,35 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
<div className="ribbon-doubleButton" style={{ display: activeItem.presentation_movement === PresMovement.Zoom ? 'inline-flex' : 'none' }}>
<div className="presBox-subheading">Zoom (% screen filled)</div>
- <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
- <input className="presBox-input" type="number" readOnly={true} value={zoom} onChange={e => this.updateZoom(e.target.value)} />%
+ <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
+ <input className="presBox-input" type="number" readOnly value={zoom} onChange={e => this.updateZoom(e.target.value)} />%
</div>
- <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}>
+ <div className="ribbon-propertyUpDown" style={{ color: SnappingManager.userBackgroundColor, background: SnappingManager.userColor }}>
<div className="ribbon-propertyUpDownItem" onClick={() => this.updateZoom(String(zoom), 0.1)}>
- <FontAwesomeIcon icon={'caret-up'} />
+ <FontAwesomeIcon icon="caret-up" />
</div>
<div className="ribbon-propertyUpDownItem" onClick={() => this.updateZoom(String(zoom), -0.1)}>
- <FontAwesomeIcon icon={'caret-down'} />
+ <FontAwesomeIcon icon="caret-down" />
</div>
</div>
</div>
{PresBox.inputter('0', '1', '100', zoom, activeItem.presentation_movement === PresMovement.Zoom, this.updateZoom)}
<div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
<div className="presBox-subheading">Transition Time</div>
- <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
- <input className="presBox-input" type="number" readOnly={true} value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s
+ <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
+ <input className="presBox-input" type="number" readOnly value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s
</div>
- <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}>
+ <div className="ribbon-propertyUpDown" style={{ color: SnappingManager.userBackgroundColor, background: SnappingManager.userColor }}>
<div className="ribbon-propertyUpDownItem" onClick={() => this.updateTransitionTime(String(transitionSpeed), 1000)}>
- <FontAwesomeIcon icon={'caret-up'} />
+ <FontAwesomeIcon icon="caret-up" />
</div>
<div className="ribbon-propertyUpDownItem" onClick={() => this.updateTransitionTime(String(transitionSpeed), -1000)}>
- <FontAwesomeIcon icon={'caret-down'} />
+ <FontAwesomeIcon icon="caret-down" />
</div>
</div>
</div>
{PresBox.inputter('0.1', '0.1', '100', transitionSpeed, true, this.updateTransitionTime)}
- <div className={'slider-headers'}>
+ <div className="slider-headers">
<div className="slider-text">Fast</div>
<div className="slider-text">Medium</div>
<div className="slider-text">Slow</div>
@@ -1798,9 +1742,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="presBox-subheading">Play Audio Annotation</div>
<input
className="presBox-checkbox"
- style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
+ style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
type="checkbox"
- onChange={() => (activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio))}
+ onChange={() => {
+ activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio);
+ }}
checked={BoolCast(activeItem.presPlayAudio)}
/>
</div>
@@ -1808,9 +1754,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="presBox-subheading">Zoom Text Selections</div>
<input
className="presBox-checkbox"
- style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
+ style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
type="checkbox"
- onChange={() => (activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText))}
+ onChange={() => {
+ activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText);
+ }}
checked={BoolCast(activeItem.presentation_zoomText)}
/>
</div>
@@ -1821,30 +1769,30 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._openEffectDropdown = !this._openEffectDropdown;
})}
style={{
- color: SettingsManager.userColor,
- background: SettingsManager.userVariantColor,
+ color: SnappingManager.userColor,
+ background: SnappingManager.userVariantColor,
borderBottomLeftRadius: this._openEffectDropdown ? 0 : 5,
- border: this._openEffectDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`,
+ border: this._openEffectDropdown ? `solid 2px ${SnappingManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`,
}}>
{effect?.toString()}
- <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
+ <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon="angle-down" />
<div
className="presBox-dropdownOptions"
- id={'presBoxMovementDropdown'}
+ id="presBoxMovementDropdown"
style={{
- color: SettingsManager.userColor,
- background: SettingsManager.userBackgroundColor,
+ color: SnappingManager.userColor,
+ background: SnappingManager.userBackgroundColor,
display: this._openEffectDropdown ? 'grid' : 'none',
}}
onPointerDown={e => e.stopPropagation()}>
{Object.values(PresEffect)
.filter(v => isNaN(Number(v)))
- .map(effect => preseEffect(effect))}
+ .map(presEffect => preseEffect(presEffect))}
</div>
</div>
<div className="ribbon-doubleButton" style={{ display: effect === PresEffectDirection.None ? 'none' : 'inline-flex' }}>
<div className="presBox-subheading">Effect direction</div>
- <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
+ <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
{StrCast(this.activeItem.presentation_effectDirection)}
</div>
</div>
@@ -1864,33 +1812,36 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
);
}
+ return undefined;
}
@computed get mediaOptionsDropdown() {
- const activeItem = this.activeItem;
+ const { activeItem } = this;
if (activeItem && this.targetDoc) {
const renderTarget = PresBox.targetRenderedDoc(this.activeItem);
const clipStart = NumCast(renderTarget.clipStart);
const clipEnd = NumCast(renderTarget.clipEnd, clipStart + NumCast(renderTarget[Doc.LayoutFieldKey(renderTarget) + '_duration']));
- const config_clipEnd = NumCast(activeItem.config_clipEnd) < NumCast(activeItem.config_clipStart) ? clipEnd - clipStart : NumCast(activeItem.config_clipEnd);
+ const configClipEnd = NumCast(activeItem.config_clipEnd) < NumCast(activeItem.config_clipStart) ? clipEnd - clipStart : NumCast(activeItem.config_clipEnd);
return (
- <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+ <div className="presBox-ribbon" onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
<div>
<div className="ribbon-box">
- Start {'&'} End Time
- <div className={'slider-headers'}>
+ Start & End Time
+ <div className="slider-headers">
<div className="slider-block">
<div className="slider-text" style={{ fontWeight: 500 }}>
Start time (s)
</div>
- <div id="startTime" className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}>
+ <div id="startTime" className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}>
<input
className="presBox-input"
style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
type="number"
- readOnly={true}
+ readOnly
value={NumCast(activeItem.config_clipStart).toFixed(2)}
onKeyDown={e => e.stopPropagation()}
- onChange={action(e => (activeItem.config_clipStart = Number(e.target.value)))}
+ onChange={action(e => {
+ activeItem.config_clipStart = Number(e.target.value);
+ })}
/>
</div>
</div>
@@ -1898,23 +1849,25 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="slider-text" style={{ fontWeight: 500 }}>
Duration (s)
</div>
- <div className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}>
- {Math.round((config_clipEnd - NumCast(activeItem.config_clipStart)) * 10) / 10}
+ <div className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}>
+ {Math.round((configClipEnd - NumCast(activeItem.config_clipStart)) * 10) / 10}
</div>
</div>
<div className="slider-block">
<div className="slider-text" style={{ fontWeight: 500 }}>
End time (s)
</div>
- <div id="endTime" className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}>
+ <div id="endTime" className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}>
<input
className="presBox-input"
onKeyDown={e => e.stopPropagation()}
style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
type="number"
- readOnly={true}
- value={config_clipEnd.toFixed(2)}
- onChange={action(e => (activeItem.config_clipEnd = Number(e.target.value)))}
+ readOnly
+ value={configClipEnd.toFixed(2)}
+ onChange={action(e => {
+ activeItem.config_clipEnd = Number(e.target.value);
+ })}
/>
</div>
</div>
@@ -1925,15 +1878,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
step="0.1"
min={clipStart}
max={clipEnd}
- value={config_clipEnd}
- style={{ gridColumn: 1, gridRow: 1, background: SettingsManager.userColor, color: SettingsManager.userVariantColor }}
+ value={configClipEnd}
+ style={{ gridColumn: 1, gridRow: 1, background: SnappingManager.userColor, color: SnappingManager.userVariantColor }}
className={`toolbar-slider ${'end'}`}
id="toolbar-slider"
onPointerDown={e => {
this._batch = UndoManager.StartBatch('config_clipEnd');
const endBlock = document.getElementById('endTime');
if (endBlock) {
- endBlock.style.backgroundColor = SettingsManager.userVariantColor;
+ endBlock.style.backgroundColor = SnappingManager.userVariantColor ?? '';
}
e.stopPropagation();
}}
@@ -1941,7 +1894,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._batch?.end();
const endBlock = document.getElementById('endTime');
if (endBlock) {
- endBlock.style.backgroundColor = SettingsManager.userBackgroundColor;
+ endBlock.style.backgroundColor = SnappingManager.userBackgroundColor ?? '';
}
}}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -1962,7 +1915,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._batch = UndoManager.StartBatch('config_clipStart');
const startBlock = document.getElementById('startTime');
if (startBlock) {
- startBlock.style.backgroundColor = SettingsManager.userVariantColor;
+ startBlock.style.backgroundColor = SnappingManager.userVariantColor ?? '';
}
e.stopPropagation();
}}
@@ -1970,7 +1923,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._batch?.end();
const startBlock = document.getElementById('startTime');
if (startBlock) {
- startBlock.style.backgroundColor = SettingsManager.userBackgroundColor;
+ startBlock.style.backgroundColor = SnappingManager.userBackgroundColor ?? '';
}
}}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -1981,7 +1934,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
<div className="slider-headers">
<div className="slider-text">{clipStart.toFixed(2)} s</div>
- <div className="slider-text"></div>
+ <div className="slider-text" />
<div className="slider-text">{clipEnd.toFixed(2)} s</div>
</div>
</div>
@@ -1993,8 +1946,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input
className="presBox-checkbox"
type="checkbox"
- style={{ border: `solid 1px ${SettingsManager.userColor}` }}
- onChange={() => (activeItem.presentation_mediaStart = 'manual')}
+ style={{ border: `solid 1px ${SnappingManager.userColor}` }}
+ onChange={() => {
+ activeItem.presentation_mediaStart = 'manual';
+ }}
checked={activeItem.presentation_mediaStart === 'manual'}
/>
<div>On click</div>
@@ -2002,9 +1957,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="checkbox-container">
<input
className="presBox-checkbox"
- style={{ border: `solid 1px ${SettingsManager.userColor}` }}
+ style={{ border: `solid 1px ${SnappingManager.userColor}` }}
type="checkbox"
- onChange={() => (activeItem.presentation_mediaStart = 'auto')}
+ onChange={() => {
+ activeItem.presentation_mediaStart = 'auto';
+ }}
checked={activeItem.presentation_mediaStart === 'auto'}
/>
<div>Automatically</div>
@@ -2016,8 +1973,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input
className="presBox-checkbox"
type="checkbox"
- style={{ border: `solid 1px ${SettingsManager.userColor}` }}
- onChange={() => (activeItem.presentation_mediaStop = 'manual')}
+ style={{ border: `solid 1px ${SnappingManager.userColor}` }}
+ onChange={() => {
+ activeItem.presentation_mediaStop = 'manual';
+ }}
checked={activeItem.presentation_mediaStop === 'manual'}
/>
<div>At media end time</div>
@@ -2026,8 +1985,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input
className="presBox-checkbox"
type="checkbox"
- style={{ border: `solid 1px ${SettingsManager.userColor}` }}
- onChange={() => (activeItem.presentation_mediaStop = 'auto')}
+ style={{ border: `solid 1px ${SnappingManager.userColor}` }}
+ onChange={() => {
+ activeItem.presentation_mediaStop = 'auto';
+ }}
checked={activeItem.presentation_mediaStop === 'auto'}
/>
<div>On slide change</div>
@@ -2055,6 +2016,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
);
}
+ return undefined;
}
@computed get newDocumentToolbarDropdown() {
return (
@@ -2118,9 +2080,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed get newDocumentDropdown() {
return (
- <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+ <div className="presBox-ribbon" onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
<div className="ribbon-box">
- Slide Title: <br></br>
+ Slide Title: <br />
<input
className="ribbon-textInput"
placeholder="..."
@@ -2129,16 +2091,31 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
onChange={e => {
e.stopPropagation();
e.preventDefault();
- runInAction(() => (this.title = e.target.value));
- }}></input>
+ runInAction(() => {
+ this.title = e.target.value;
+ });
+ }}
+ />
</div>
<div className="ribbon-box">
Choose type:
<div className="ribbon-doubleButton">
- <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? '' : Colors.LIGHT_BLUE }} onClick={action(() => (this.addFreeform = !this.addFreeform))}>
+ <div
+ title="Text"
+ className="ribbon-toggle"
+ style={{ background: this.addFreeform ? '' : Colors.LIGHT_BLUE }}
+ onClick={action(() => {
+ this.addFreeform = !this.addFreeform;
+ })}>
Text
</div>
- <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : '' }} onClick={action(() => (this.addFreeform = !this.addFreeform))}>
+ <div
+ title="Freeform"
+ className="ribbon-toggle"
+ style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : '' }}
+ onClick={action(() => {
+ this.addFreeform = !this.addFreeform;
+ })}>
Freeform
</div>
</div>
@@ -2146,23 +2123,49 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="ribbon-box" style={{ display: this.addFreeform ? 'grid' : 'none' }}>
Preset layouts:
<div className="layout-container" style={{ height: this.openLayouts ? 'max-content' : '75px' }}>
- <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'blank'))} />
- <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'title'))}>
+ <div
+ className="layout"
+ style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+ onClick={action(() => {
+ this.layout = 'blank';
+ })}
+ />
+ <div
+ className="layout"
+ style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+ onClick={action(() => {
+ this.layout = 'title';
+ })}>
<div className="title">Title</div>
<div className="subtitle">Subtitle</div>
</div>
- <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'header'))}>
+ <div
+ className="layout"
+ style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+ onClick={action(() => {
+ this.layout = 'header';
+ })}>
<div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>
Section header
</div>
</div>
- <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'content'))}>
+ <div
+ className="layout"
+ style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+ onClick={action(() => {
+ this.layout = 'content';
+ })}>
<div className="title" style={{ alignSelf: 'center' }}>
Title
</div>
<div className="content">Text goes here</div>
</div>
- <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'twoColumns'))}>
+ <div
+ className="layout"
+ style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+ onClick={action(() => {
+ this.layout = 'twoColumns';
+ })}>
<div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}>
Title
</div>
@@ -2174,8 +2177,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
</div>
</div>
- <div className="open-layout" onClick={action(() => (this.openLayouts = !this.openLayouts))}>
- <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'caret-down'} size={'lg'} />
+ <div
+ className="open-layout"
+ onClick={action(() => {
+ this.openLayouts = !this.openLayouts;
+ })}>
+ <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon="caret-down" size="lg" />
</div>
</div>
<div className="ribbon-final-box">
@@ -2190,17 +2197,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
createNewSlide = (layout?: string, title?: string, freeform?: boolean) => {
- let doc = undefined;
+ let doc;
if (layout) doc = this.createTemplate(layout);
if (freeform && layout) doc = this.createTemplate(layout, title);
if (!freeform && !layout) doc = Docs.Create.TextDocument('', { _nativeWidth: 400, _width: 225, title: title });
if (doc) {
const tabMap = CollectionDockingView.Instance?.tabMap;
- const tab = tabMap && Array.from(tabMap).find(tab => tab.DashDoc.type === DocumentType.COL)?.DashDoc;
- const presCollection = DocumentManager.GetContextPath(this.activeItem).reverse().lastElement().presentation_targetDoc ?? tab;
+ const docTab = tabMap && Array.from(tabMap).find(tab => tab.DashDoc.type === DocumentType.COL)?.DashDoc;
+ const presCollection = DocumentView.getContextPath(this.activeItem).reverse().lastElement().presentation_targetDoc ?? docTab;
const data = Cast(presCollection?.data, listSpec(Doc));
- const config_data = Cast(this.Document.data, listSpec(Doc));
- if (data && config_data) {
+ const configData = Cast(this.Document.data, listSpec(Doc));
+ if (data && configData) {
data.push(doc);
this._props.pinToPres(doc, {});
this.gotoDocument(this.childDocs.length, this.activeItem);
@@ -2222,12 +2229,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const content2 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _text_fontSize: '14pt' });
// prettier-ignore
switch (layout) {
- case 'blank': return Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x, y });
- case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input ? input : 'Title slide', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
- case 'header': return Docs.Create.FreeformDocument([header()], { title: input ? input : 'Section header', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
- case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input ? input : 'Title and content', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
- case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y })
+ case 'blank': return Docs.Create.FreeformDocument([], { title: input || 'Blank slide', _width: 400, _height: 225, x, y });
+ case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input || 'Title slide', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
+ case 'header': return Docs.Create.FreeformDocument([header()], { title: input || 'Section header', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
+ case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input || 'Title and content', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
+ case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input || 'Title and two columns', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y })
+ default:
}
+ return undefined;
};
// Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view)
@@ -2270,17 +2279,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
@action
- toggleProperties = () => (SettingsManager.Instance.propertiesWidth = SettingsManager.Instance.propertiesWidth > 0 ? 0 : 250);
+ toggleProperties = () => {
+ SnappingManager.SetPropertiesWidth(SnappingManager.PropertiesWidth > 0 ? 0 : 250);
+ };
@computed get toolbar() {
- const propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
- const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel';
+ const propIcon = SnappingManager.PropertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
+ const propTitle = SnappingManager.PropertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel';
const mode = StrCast(this.Document._type_collection) as CollectionViewType;
const isMini: boolean = this.toolbarWidth <= 100;
- const activeColor = SettingsManager.userVariantColor;
- const inactiveColor = lightOrDark(SettingsManager.userBackgroundColor) === Colors.WHITE ? Colors.WHITE : SettingsManager.userBackgroundColor;
+ const activeColor = SnappingManager.userVariantColor;
+ const inactiveColor = lightOrDark(SnappingManager.userBackgroundColor) === Colors.WHITE ? Colors.WHITE : SnappingManager.userBackgroundColor;
return mode === CollectionViewType.Carousel3D || Doc.IsInMyOverlay(this.Document) ? null : (
- <div id="toolbarContainer" className={'presBox-toolbar'}>
+ <div id="toolbarContainer" className="presBox-toolbar">
{/* <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}>
<FontAwesomeIcon icon={"plus"} />
<FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} />
@@ -2290,7 +2301,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
style={{ opacity: this.childDocs.length > 1 ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? '100%' : undefined }}
className="toolbar-button"
onClick={this.childDocs.length > 1 ? () => this.togglePath() : undefined}>
- <FontAwesomeIcon icon={'exchange-alt'} />
+ <FontAwesomeIcon icon="exchange-alt" />
</div>
</Tooltip>
{isMini ? null : (
@@ -2298,12 +2309,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="toolbar-divider" />
<Tooltip title={<div className="dash-tooltip">{this._presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}</div>}>
<div className="toolbar-button" style={{ cursor: this._presKeyEvents ? 'default' : 'pointer', position: 'absolute', right: 30, fontSize: 16 }}>
- <FontAwesomeIcon className={'toolbar-thumbtack'} icon={'keyboard'} style={{ color: this._presKeyEvents ? activeColor : inactiveColor }} />
+ <FontAwesomeIcon className="toolbar-thumbtack" icon="keyboard" style={{ color: this._presKeyEvents ? activeColor : inactiveColor }} />
</div>
</Tooltip>
<Tooltip title={<div className="dash-tooltip">{propTitle}</div>}>
<div className="toolbar-button" style={{ position: 'absolute', right: 4, fontSize: 16 }} onClick={this.toggleProperties}>
- <FontAwesomeIcon className={'toolbar-thumbtack'} icon={propIcon} style={{ color: SettingsManager.Instance.propertiesWidth > 0 ? activeColor : inactiveColor }} />
+ <FontAwesomeIcon className="toolbar-thumbtack" icon={propIcon} style={{ color: SnappingManager.PropertiesWidth > 0 ? activeColor : inactiveColor }} />
</div>
</Tooltip>
</>
@@ -2348,7 +2359,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.gotoDocument(this.itemIndex, this.activeItem);
}
})}>
- <FontAwesomeIcon icon={'play-circle'} />
+ <FontAwesomeIcon icon="play-circle" />
<div style={{ display: this._props.PanelWidth() > 200 ? 'inline-flex' : 'none' }}>&nbsp; Present</div>
</div>
{mode === CollectionViewType.Carousel3D || isMini ? null : (
@@ -2357,7 +2368,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
onClick={action(() => {
if (this.childDocs.length) this._presentTools = !this._presentTools;
})}>
- <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this._presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'angle-down'} />
+ <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this._presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon="angle-down" />
{this.presentDropdown}
</div>
)}
@@ -2378,12 +2389,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// Case 1: There are still other frames and should go through all frames before going to next slide
return (
<div className="presPanelOverlay" style={{ display: this.layoutDoc.presentation_status !== 'edit' ? 'inline-flex' : 'none' }}>
- <Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}>
+ <Tooltip title={<div className="dash-tooltip">Loop</div>}>
<div
className="presPanel-button"
style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : 'white' }}
- onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop), false, false)}>
- <FontAwesomeIcon icon={'redo-alt'} />
+ onPointerDown={e =>
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ emptyFunction,
+ () => {
+ this.layoutDoc.presLoop = !this.layoutDoc.presLoop;
+ },
+ false,
+ false
+ )
+ }>
+ <FontAwesomeIcon icon="redo-alt" />
</div>
</Tooltip>
<div className="presPanel-divider" />
@@ -2408,7 +2431,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
false
)
}>
- <FontAwesomeIcon icon={'arrow-left'} />
+ <FontAwesomeIcon icon="arrow-left" />
</div>
<Tooltip title={<div className="dash-tooltip">{this.layoutDoc.presentation_status === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>}>
<div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.startOrPause(true), false, false)}>
@@ -2436,10 +2459,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
false
)
}>
- <FontAwesomeIcon icon={'arrow-right'} />
+ <FontAwesomeIcon icon="arrow-right" />
</div>
- <div className="presPanel-divider"></div>
- <Tooltip title={<div className="dash-tooltip">{'Click to return to 1st slide'}</div>}>
+ <div className="presPanel-divider" />
+ <Tooltip title={<div className="dash-tooltip">Click to return to 1st slide</div>}>
<div
className="presPanel-button"
style={{ border: 'solid 1px white' }}
@@ -2463,7 +2486,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
{inOverlay ? '' : 'Slide'} {this.itemIndex + 1}
{this.activeItem?.presentation_indexed !== undefined ? `(${this.activeItem.presentation_indexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length}
</div>
- <div className="presPanel-divider"></div>
+ <div className="presPanel-divider" />
{this._props.PanelWidth() > 250 ? (
<div
className="presPanel-button-text"
@@ -2477,7 +2500,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
) : (
<div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.exitClicked, false, false)}>
- <FontAwesomeIcon icon={'times'} />
+ <FontAwesomeIcon icon="times" />
</div>
)}
</div>
@@ -2492,7 +2515,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
@action
- prevClicked = (e: PointerEvent) => {
+ prevClicked = () => {
this.back();
if (this._presTimer) {
clearTimeout(this._presTimer);
@@ -2501,7 +2524,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
@action
- nextClicked = (e: PointerEvent) => {
+ nextClicked = () => {
this.next();
if (this._presTimer) {
clearTimeout(this._presTimer);
@@ -2517,7 +2540,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
AddToMap = (treeViewDoc: Doc, index: number[]) => {
if (!treeViewDoc.presentation_targetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element.
- var indexNum = 0;
+ let indexNum = 0;
for (let i = 0; i < index.length; i++) {
indexNum += index[i] * 10 ** -i;
}
@@ -2529,19 +2552,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.dataDoc[this.presFieldKey] = new List<Doc>(sorted); // this is a flat array of Docs
}
}
+ return undefined;
};
SlideIndex = (slideDoc: Doc) => DocListCast(this.dataDoc[this.presFieldKey]).indexOf(slideDoc);
- RemFromMap = (treeViewDoc: Doc, index: number[]) => {
+ RemFromMap = (treeViewDoc: Doc) => {
if (!treeViewDoc.presentation_targetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element.
if (!this._unmounting && this.isTree) {
this._treeViewMap.delete(treeViewDoc);
this.dataDoc[this.presFieldKey] = new List<Doc>(this.sort(this._treeViewMap));
}
+ return undefined;
};
- sort = (treeView_Map: Map<Doc, number>) => [...treeView_Map.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]);
+ sort = (treeViewMap: Map<Doc, number>) => [...treeViewMap.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]);
render() {
// needed to ensure that the childDocs are loaded for looking up fields
@@ -2553,21 +2578,38 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
(this.activeItem.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
const presStart = !this.layoutDoc.presLoop && this.itemIndex === 0;
return this._props.addDocTab === returnFalse ? ( // bcz: hack!! - addDocTab === returnFalse only when this is being rendered by the OverlayView which means the doc is a mini player
- <div className="miniPres" onClick={e => e.stopPropagation()} onPointerEnter={action(e => (this._forceKeyEvents = true))}>
+ <div
+ className="miniPres"
+ onClick={e => e.stopPropagation()}
+ onPointerEnter={action(() => {
+ this._forceKeyEvents = true;
+ })}>
<div
className="presPanelOverlay"
style={{ display: 'inline-flex', height: 30, background: Doc.ActivePresentation === this.Document ? 'green' : '#323232', top: 0, zIndex: 3000000, boxShadow: this._presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}>
- <Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}>
+ <Tooltip title={<div className="dash-tooltip">Loop</div>}>
<div
className="presPanel-button"
style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }}
- onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop), false, false)}>
- <FontAwesomeIcon icon={'redo-alt'} />
+ onPointerDown={e =>
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ returnFalse,
+ () => {
+ this.layoutDoc.presLoop = !this.layoutDoc.presLoop;
+ },
+ false,
+ false
+ )
+ }>
+ <FontAwesomeIcon icon="redo-alt" />
</div>
</Tooltip>
- <div className="presPanel-divider"></div>
+ <div className="presPanel-divider" />
<div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.prevClicked, false, false)}>
- <FontAwesomeIcon icon={'arrow-left'} />
+ <FontAwesomeIcon icon="arrow-left" />
</div>
<Tooltip title={<div className="dash-tooltip">{this.layoutDoc.presentation_status === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>}>
<div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.startOrPause(true), false, false)}>
@@ -2575,10 +2617,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
</Tooltip>
<div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.nextClicked, false, false)}>
- <FontAwesomeIcon icon={'arrow-right'} />
+ <FontAwesomeIcon icon="arrow-right" />
</div>
- <div className="presPanel-divider"></div>
- <Tooltip title={<div className="dash-tooltip">{'Click to return to 1st slide'}</div>}>
+ <div className="presPanel-divider" />
+ <Tooltip title={<div className="dash-tooltip">Click to return to 1st slide</div>}>
<div className="presPanel-button" style={{ border: 'solid 1px white' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.gotoDocument(0, this.activeItem), false, false)}>
<b>1</b>
</div>
@@ -2602,27 +2644,28 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="Slide">
{mode !== CollectionViewType.Invalid ? (
<CollectionView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
PanelWidth={this._props.PanelWidth}
PanelHeight={this.panelHeight}
- childIgnoreNativeSize={true}
+ childIgnoreNativeSize
moveDocument={returnFalse}
- ignoreUnrendered={true}
+ ignoreUnrendered
childDragAction={dropActionType.move}
setContentViewBox={emptyFunction}
- //childLayoutFitWidth={returnTrue}
+ // childLayoutFitWidth={returnTrue}
childOpacity={returnOne}
childClickScript={PresBox.navigateToDocScript}
childLayoutTemplate={this.childLayoutTemplate}
childXPadding={Doc.IsComicStyle(this.Document) ? 20 : undefined}
filterAddDocument={this.addDocumentFilter}
removeDocument={returnFalse}
- dontRegisterView={true}
+ dontRegisterView
focus={this.focusElement}
ScreenToLocalTransform={this.getTransform}
AddToMap={this.AddToMap}
RemFromMap={this.RemFromMap}
- hierarchyIndex={emptyPath as any as number[]}
+ hierarchyIndex={emptyPath}
/>
) : null}
</div>
@@ -2641,6 +2684,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function navigateToDoc(bestTarget: Doc, activeItem: Doc) {
PresBox.NavigateToTarget(bestTarget, activeItem);
});
+
+Docs.Prototypes.TemplateMap.set(DocumentType.PRES, {
+ layout: { view: PresBox, dataField: 'data' },
+ options: { acl: '', defaultDoubleClick: 'ignore', hideClickBehaviors: true, layout_hideLinkAnchors: true },
+});
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 28139eb14..306b98190 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -1,27 +1,30 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
-import { CollectionViewType } from '../../../documents/DocumentTypes';
-import { DocumentManager } from '../../../util/DocumentManager';
+import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { DragManager } from '../../../util/DragManager';
-import { SettingsManager } from '../../../util/SettingsManager';
+import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { undoable, undoBatch } from '../../../util/UndoManager';
import { TreeView } from '../../collections/TreeView';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { EditableView } from '../../EditableView';
import { Colors } from '../../global/globalEnums';
-import { DocumentView } from '../../nodes/DocumentView';
-import { FieldView, FieldViewProps } from '../../nodes/FieldView';
-import { StyleProp } from '../../StyleProvider';
+import { PinDocView } from '../../PinFuncs';
+import { StyleProp } from '../../StyleProp';
+import { DocumentView } from '../DocumentView';
+import { FieldView, FieldViewProps } from '../FieldView';
import { PresBox } from './PresBox';
import './PresElementBox.scss';
import { PresMovement } from './PresEnums';
@@ -71,7 +74,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
// computes index of this presentation slide in the presBox list
@computed get indexInPres() {
- return this.presBoxView?.SlideIndex(this.slideDoc);
+ return this.presBoxView?.SlideIndex(this.slideDoc) ?? 0;
}
@computed get selectedArray() {
@@ -86,7 +89,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.layoutDoc.layout_hideLinkButton = true;
this._heightDisposer = reaction(
() => ({ expand: this.slideDoc.presentation_expandInlineButton, height: this.collapsedHeight }),
- ({ expand, height }) => (this.layoutDoc._height = height + (expand ? this.expandViewHeight : 0)),
+ ({ expand, height }) => {
+ this.layoutDoc._height = height + (expand ? this.expandViewHeight : 0);
+ },
{ fireImmediately: true }
);
}
@@ -94,12 +99,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._heightDisposer?.();
}
- presExpandDocumentClick = () => (this.slideDoc.presentation_expandInlineButton = !this.slideDoc.presentation_expandInlineButton);
+ presExpandDocumentClick = () => {
+ this.slideDoc.presentation_expandInlineButton = !this.slideDoc.presentation_expandInlineButton;
+ };
embedHeight = () => this.collapsedHeight + this.expandViewHeight;
embedWidth = () => this._props.PanelWidth() / 2;
- styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
- return property === StyleProp.Opacity ? 1 : this._props.styleProvider?.(doc, props, property);
- };
+ // prettier-ignore
+ styleProvider = ( doc: Doc | undefined, props: Opt<FieldViewProps>, property: string ): any =>
+ (property === StyleProp.Opacity ? 1 : this._props.styleProvider?.(doc, props, property));
/**
* The function that is responsible for rendering a preview or not for this
* presentation element.
@@ -113,7 +120,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
PanelHeight={this.embedHeight}
isContentActive={this._props.isContentActive}
styleProvider={this.styleProvider}
- hideLinkButton={true}
+ hideLinkButton
ScreenToLocalTransform={Transform.Identity}
renderDepth={this._props.renderDepth + 1}
containerViewPath={returnEmptyDoclist}
@@ -150,7 +157,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
ref={this._titleRef}
editing={undefined}
contents={doc.title}
- overflow={'ellipsis'}
+ overflow="ellipsis"
GetValue={() => StrCast(doc.title)}
SetValue={(value: string) => {
doc.title = !value.trim().length ? '-untitled-' : value;
@@ -177,10 +184,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
e.preventDefault();
if (element && !(e.ctrlKey || e.metaKey || e.button === 2)) {
this.presBoxView?.regularSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, true, false);
- setupMoveUpEvents(this, e, this.startDrag, emptyFunction, e => {
- e.stopPropagation();
- e.preventDefault();
- this.presBoxView?.modifierSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, e.shiftKey || e.ctrlKey || e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
+ setupMoveUpEvents(this, e, this.startDrag, emptyFunction, clickEv => {
+ clickEv.stopPropagation();
+ clickEv.preventDefault();
+ this.presBoxView?.modifierSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, clickEv.shiftKey || clickEv.ctrlKey || clickEv.metaKey, clickEv.ctrlKey || clickEv.metaKey, clickEv.shiftKey);
this.presBoxView?.activeItem && this.showRecording(this.presBoxView?.activeItem);
});
}
@@ -209,7 +216,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
} else if (dragArray.length >= 1) {
const doc = document.createElement('div');
doc.className = 'presItem-multiDrag';
- doc.innerText = 'Move ' + this.selectedArray?.size + ' slides';
+ doc.innerText = 'Move ' + (this.selectedArray?.size ?? 0) + ' slides';
doc.style.position = 'absolute';
doc.style.top = e.clientY + 'px';
doc.style.left = e.clientX - 50 + 'px';
@@ -217,7 +224,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
if (activeItem) {
- runInAction(() => (this._dragging = true));
+ runInAction(() => {
+ this._dragging = true;
+ });
DragManager.StartDocumentDrag(
dragItem.map(ele => ele),
dragData,
@@ -225,7 +234,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
e.clientY,
undefined,
action(() => {
- Array.from(classesToRestore).forEach(pair => (pair[0].className = pair[1]));
+ Array.from(classesToRestore).forEach(pair => {
+ // eslint-disable-next-line prefer-destructuring
+ pair[0].className = pair[1];
+ });
this._dragging = false;
})
);
@@ -234,7 +246,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
return false;
};
- onPointerOver = (e: any) => {
+ onPointerOver = () => {
document.removeEventListener('pointermove', this.onPointerMove);
document.addEventListener('pointermove', this.onPointerMove);
};
@@ -244,7 +256,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
const dragIsPresItem = DragManager.docsBeingDragged.some(d => d.presentation_targetDoc);
if (slide && dragIsPresItem) {
const rect = slide.getBoundingClientRect();
- const y = e.clientY - rect.top; //y position within the element.
+ const y = e.clientY - rect.top; // y position within the element.
const height = slide.clientHeight;
const halfLine = height / 2;
if (y <= halfLine) {
@@ -258,7 +270,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
document.removeEventListener('pointermove', this.onPointerMove);
};
- onPointerLeave = (e: any) => {
+ onPointerLeave = () => {
const slide = this._itemRef.current;
if (slide) {
slide.style.borderTop = '0px';
@@ -269,8 +281,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
toggleProperties = () => {
- if (SettingsManager.Instance.propertiesWidth < 5) {
- SettingsManager.Instance.propertiesWidth = 250;
+ if (SnappingManager.PropertiesWidth < 5) {
+ SnappingManager.SetPropertiesWidth(250);
}
};
@@ -323,7 +335,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
updateCapturedViewContents = undoable(
action((presTargetDoc: Doc, activeItem: Doc) => {
const target = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc;
- PresBox.pinDocView(activeItem, { pinData: PresBox.pinDataTypes(target) }, target);
+ PinDocView(activeItem, { pinData: PresBox.pinDataTypes(target) }, target);
}),
'updated captured view contents'
);
@@ -340,7 +352,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
hideRecording = undoable(
- action((e: React.MouseEvent, iconClick: boolean = false) => {
+ action((e: React.MouseEvent) => {
e.stopPropagation();
this.removeAllRecordingInOverlay();
}),
@@ -395,7 +407,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
lfg = (e: React.MouseEvent) => {
e.stopPropagation();
// TODO: fix this bug
- const { toggleChildrenRun } = this.slideDoc;
+ // const { toggleChildrenRun } = this.slideDoc;
TreeView.ToggleChildrenRun.get(this.slideDoc)?.();
// call this.slideDoc.recurChildren() to get all the children
@@ -404,17 +416,15 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed
get toolbarWidth(): number {
- const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox);
+ const presBoxDocView = DocumentView.getDocumentView(this.presBox);
const width = NumCast(this.presBox?._width);
- return presBoxDocView ? presBoxDocView._props.PanelWidth() : width ? width : 300;
+ return presBoxDocView ? presBoxDocView._props.PanelWidth() : width || 300;
}
@computed get presButtons() {
- const presBox = this.presBox;
+ const { presBox, targetDoc, slideDoc: activeItem } = this;
const presBoxColor = StrCast(presBox?._backgroundColor);
const presColorBool = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
- const targetDoc = this.targetDoc;
- const activeItem = this.slideDoc;
const hasChildren = BoolCast(this.slideDoc?.hasChildren);
const items: JSX.Element[] = [];
@@ -441,7 +451,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
items.push(
<Tooltip key="slash" title={<div className="dash-tooltip">{this.videoRecordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}>
- <div className="slideButton" onClick={e => (this.videoRecordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}>
+ <div className="slideButton" onClick={e => (this.videoRecordingIsInOverlay ? this.hideRecording(e) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}>
<FontAwesomeIcon icon={`video${this.videoRecordingIsInOverlay ? '-slash' : ''}`} onPointerDown={e => e.stopPropagation()} />
</div>
</Tooltip>
@@ -461,7 +471,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
}>
<div
className="slideButton"
- onClick={() => (activeItem.presentation_groupWithUp = (NumCast(activeItem.presentation_groupWithUp) + 1) % 3)}
+ onClick={() => {
+ activeItem.presentation_groupWithUp = (NumCast(activeItem.presentation_groupWithUp) + 1) % 3;
+ }}
style={{
zIndex: 1000 - this.indexInPres,
fontWeight: 700,
@@ -471,7 +483,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
transform: activeItem.presentation_groupWithUp ? 'translate(0, -17px)' : undefined,
}}>
<div style={{ transform: activeItem.presentation_groupWithUp ? 'rotate(180deg) translate(0, -17.5px)' : 'rotate(0deg)' }}>
- <FontAwesomeIcon icon={'arrow-up'} onPointerDown={e => e.stopPropagation()} />
+ <FontAwesomeIcon icon="arrow-up" onPointerDown={e => e.stopPropagation()} />
</div>
</div>
</Tooltip>
@@ -500,15 +512,15 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.lfg(e);
}}
style={{ fontWeight: 700 }}>
- <FontAwesomeIcon icon={'circle-play'} onPointerDown={e => e.stopPropagation()} />
+ <FontAwesomeIcon icon="circle-play" onPointerDown={e => e.stopPropagation()} />
</div>
</Tooltip>
);
}
items.push(
<Tooltip key="trash" title={<div className="dash-tooltip">Remove from presentation</div>}>
- <div className={'slideButton'} onClick={this.removePresentationItem}>
- <FontAwesomeIcon icon={'trash'} onPointerDown={e => e.stopPropagation()} />
+ <div className="slideButton" onClick={this.removePresentationItem}>
+ <FontAwesomeIcon icon="trash" onPointerDown={e => e.stopPropagation()} />
</div>
</Tooltip>
);
@@ -516,18 +528,17 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
@computed get mainItem() {
- const isSelected: boolean = this.selectedArray?.has(this.slideDoc) ? true : false;
+ const { presBox, slideDoc: activeItem } = this;
+ const isSelected: boolean = !!this.selectedArray?.has(activeItem);
const isCurrent: boolean = this.presBox?._itemIndex === this.indexInPres;
const miniView: boolean = this.toolbarWidth <= 110;
- const presBox = this.presBox; //presBox
const presBoxColor: string = StrCast(presBox?._backgroundColor);
const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
- const activeItem: Doc = this.slideDoc;
return (
<div
className="presItem-container"
- key={this.slideDoc[Id] + this.indexInPres}
+ key={activeItem[Id] + this.indexInPres}
ref={this._itemRef}
style={{
backgroundColor: presColorBool ? (isSelected ? 'rgba(250,250,250,0.3)' : 'transparent') : isSelected ? Colors.LIGHT_BLUE : 'transparent',
@@ -537,9 +548,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
paddingTop: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
paddingBottom: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
}}
- onDoubleClick={action(e => {
+ onDoubleClick={action(() => {
this.toggleProperties();
- this.presBoxView?.regularSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, false);
+ this.presBoxView?.regularSelect(activeItem, this._itemRef.current!, this._dragRef.current!, false);
})}
onPointerOver={this.onPointerOver}
onPointerLeave={this.onPointerLeave}
@@ -551,11 +562,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
) : (
<div
ref={this._dragRef}
- className={`presItem-slide ${isCurrent ? 'active' : ''}${this.slideDoc.runProcess ? ' testingv2' : ''}`}
+ className={`presItem-slide ${isCurrent ? 'active' : ''}${activeItem.runProcess ? ' testingv2' : ''}`}
style={{
display: 'infline-block',
backgroundColor: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor),
- //layout_boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined,
+ // layout_boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined,
border: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? presBoxColor + ' solid 2.5px' : undefined) : undefined,
}}>
<div
@@ -563,7 +574,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
style={{
display: 'inline-flex',
pointerEvents: isSelected ? undefined : 'none',
- width: `calc(100% ${this.slideDoc.presentation_expandInlineButton ? '- 50%' : ''} - ${this.presButtons.length * 22}px`,
+ width: `calc(100% ${activeItem.presentation_expandInlineButton ? '- 50%' : ''} - ${this.presButtons.length * 22}px`,
cursor: isSelected ? 'text' : 'grab',
}}>
<div
@@ -576,7 +587,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
}}
onClick={e => e.stopPropagation()}>{`${this.indexInPres + 1}. `}</div>
- <EditableView ref={this._titleRef} oneLine={true} editing={!isSelected ? false : undefined} contents={activeItem.title} overflow={'ellipsis'} GetValue={() => StrCast(activeItem.title)} SetValue={this.onSetValue} />
+ <EditableView ref={this._titleRef} oneLine editing={!isSelected ? false : undefined} contents={activeItem.title} overflow="ellipsis" GetValue={() => StrCast(activeItem.title)} SetValue={this.onSetValue} />
</div>
{/* <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.transition}</div></Tooltip> */}
{/* <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.duration}</div></Tooltip> */}
@@ -594,3 +605,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
return !(this.slideDoc instanceof Doc) || this.targetDoc instanceof Promise ? null : this.mainItem;
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.PRESELEMENT, {
+ layout: { view: PresElementBox, dataField: 'data' },
+ options: { acl: '', title: 'pres element template', _layout_fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: 'data' },
+});
diff --git a/src/client/views/nodes/trails/index.ts b/src/client/views/nodes/trails/index.ts
index 8f3f7b03a..7b18974df 100644
--- a/src/client/views/nodes/trails/index.ts
+++ b/src/client/views/nodes/trails/index.ts
@@ -1,3 +1,3 @@
-export * from "./PresBox";
-export * from "./PresElementBox";
-export * from "./PresEnums"; \ No newline at end of file
+export * from './PresBox';
+export * from './PresElementBox';
+export * from './PresEnums';
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 0b3ba81d3..a837969aa 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -4,18 +4,20 @@ import { IReactionDisposer, ObservableMap, action, computed, makeObservable, obs
import { observer } from 'mobx-react';
import * as React from 'react';
import { ColorResult } from 'react-color';
-import { Utils, returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../Utils';
+import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction, unimplementedFunction } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
import { DocumentType } from '../../documents/DocumentTypes';
-import { SelectionManager } from '../../util/SelectionManager';
import { SettingsManager } from '../../util/SettingsManager';
import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
import { LinkPopup } from '../linking/LinkPopup';
import './AnchorMenu.scss';
-import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup';
+import { GPTPopup } from './GPTPopup/GPTPopup';
+import { DocumentView } from '../nodes/DocumentView';
@observer
export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
+ // eslint-disable-next-line no-use-before-define
static Instance: AnchorMenu;
private _disposer: IReactionDisposer | undefined;
@@ -36,7 +38,9 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
// GPT additions
@observable private selectedText: string = '';
@action
- public setSelectedText = (txt: string) => (this.selectedText = txt);
+ public setSelectedText = (txt: string) => {
+ this.selectedText = txt;
+ };
public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
@@ -45,8 +49,8 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
- public Highlight: (color: string) => Opt<Doc> = (color: string) => undefined;
- public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
+ public Highlight: (color: string) => Opt<Doc> = (/* color: string */) => undefined;
+ public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = emptyFunction;
public Delete: () => void = unimplementedFunction;
public PinToPres: () => void = unimplementedFunction;
public MakeTargetToggle: () => void = unimplementedFunction;
@@ -62,8 +66,8 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
componentDidMount() {
this._disposer = reaction(
- () => SelectionManager.Views.slice(),
- sel => AnchorMenu.Instance.fadeOut(true)
+ () => DocumentView.Selected().slice(),
+ () => AnchorMenu.Instance.fadeOut(true)
);
}
@@ -71,7 +75,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
* Invokes the API with the selected text and stores it in the summarized text.
* @param e pointer down event
*/
- gptSummarize = async (e: React.PointerEvent) => {
+ gptSummarize = async () => {
GPTPopup.Instance?.setSelectedText(this.selectedText);
GPTPopup.Instance.generateSummary();
};
@@ -80,29 +84,29 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
setupMoveUpEvents(
this,
e,
- (e: PointerEvent) => {
- this.StartDrag(e, this._commentRef.current!);
+ (moveEv: PointerEvent) => {
+ this.StartDrag(moveEv, this._commentRef.current!);
return true;
},
returnFalse,
- e => this.OnClick?.(e)
+ clickEv => this.OnClick?.(clickEv)
);
};
audioDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, returnFalse, returnFalse, e => this.OnAudio?.(e));
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, clickEv => this.OnAudio?.(clickEv));
};
cropDown = (e: React.PointerEvent) => {
setupMoveUpEvents(
this,
e,
- (e: PointerEvent) => {
- this.StartCropDrag(e, this._cropRef.current!);
+ (moveEv: PointerEvent) => {
+ this.StartCropDrag(moveEv, this._cropRef.current!);
return true;
},
returnFalse,
- e => this.OnCrop?.(e)
+ clickev => this.OnCrop?.(clickev)
);
};
@@ -117,7 +121,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
<Group>
<IconButton
icon={<FontAwesomeIcon icon="highlighter" style={{ transition: 'transform 0.1s', transform: 'rotate(-45deg)' }} />}
- tooltip={'Click to Highlight'}
+ tooltip="Click to Highlight"
onClick={this.highlightClicked}
colorPicker={this.highlightColor}
color={SettingsManager.userColor}
@@ -133,7 +137,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
hsl: { a: 0, h: 0, s: 0, l: 0 },
rgb: { a: 0, r: 0, b: 0, g: 0 },
};
- this.highlightColor = Utils.colorString(col);
+ this.highlightColor = ClientUtils.colorString(col);
};
/**
@@ -141,7 +145,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 = () => DocumentView.SelectedDocs().some(doc => [DocumentType.PDF, DocumentType.WEB].includes(doc.type as any));
render() {
const buttons =
@@ -156,7 +160,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
color={SettingsManager.userColor}
/>
</div>
- {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/}
+ {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection */}
{AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && (
<IconButton
tooltip="Summarize with AI" //
@@ -176,7 +180,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
<Popup
tooltip="Find document to link to selected text" //
type={Type.PRIM}
- icon={<FontAwesomeIcon icon={'search'} />}
+ icon={<FontAwesomeIcon icon="search" />}
popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />}
color={SettingsManager.userColor}
/>
@@ -219,7 +223,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
)}
{this.IsTargetToggler !== returnFalse && (
<Toggle
- tooltip={'Make target visibility toggle on click'}
+ tooltip="Make target visibility toggle on click"
type={Type.PRIM}
toggleType={ToggleType.BUTTON}
toggleStatus={this.IsTargetToggler()}
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index a1f5ce703..7dd4047c1 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,24 +1,50 @@
import { action, computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
-import { Id } from '../../../fields/FieldSymbols';
+import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
+import { Highlight } from '../../../fields/DocSymbols';
import { List } from '../../../fields/List';
-import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { LinkFollower } from '../../util/LinkFollower';
+import { BoolCast, DocCast, NumCast, StrCast } from '../../../fields/Types';
import { LinkManager } from '../../util/LinkManager';
-import { undoBatch } from '../../util/UndoManager';
-import { OpenWhere } from '../nodes/DocumentView';
+import { undoable } from '../../util/UndoManager';
+import { ObservableReactComponent } from '../ObservableReactComponent';
+import { DocumentView } from '../nodes/DocumentView';
import { FieldViewProps } from '../nodes/FieldView';
+import { OpenWhere } from '../nodes/OpenWhere';
import { AnchorMenu } from './AnchorMenu';
import './Annotation.scss';
-import { ObservableReactComponent } from '../ObservableReactComponent';
+
+interface IRegionAnnotationProps {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ opacity: () => number;
+ background: () => string;
+ outline: () => string | undefined;
+}
+
+const RegionAnnotation = function (props: IRegionAnnotationProps) {
+ return (
+ <div
+ className="htmlAnnotation"
+ style={{
+ left: NumCast(props.x),
+ top: NumCast(props.y),
+ width: NumCast(props.width),
+ height: NumCast(props.height),
+ opacity: props.opacity(),
+ outline: props.outline(),
+ backgroundColor: props.background(),
+ }}
+ />
+ );
+};
interface IAnnotationProps extends FieldViewProps {
- anno: Doc;
- dataDoc: Doc;
+ annoDoc: Doc;
+ containerDataDoc: Doc;
fieldKey: string;
- showInfo?: (anno: Opt<Doc>) => void;
pointerEvents?: () => Opt<string>;
}
@observer
@@ -27,62 +53,45 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
super(props);
makeObservable(this);
}
- render() {
- return (
- <div style={{ display: this._props.anno.textCopied && !Doc.GetBrushHighlightStatus(this._props.anno) ? 'none' : undefined }}>
- {DocListCast(this._props.anno.text_inlineAnnotations).map(a => (
- <RegionAnnotation pointerEvents={this._props.pointerEvents} {...this._props} document={a} key={a[Id]} />
- ))}
- </div>
- );
- }
-}
-interface IRegionAnnotationProps extends IAnnotationProps {
- document: Doc;
- pointerEvents?: () => Opt<string>;
-}
-@observer
-class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps> {
- private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
-
- @computed get annoTextRegion() {
- return Cast(this._props.document.annoTextRegion, Doc, null) || this._props.document;
+ @computed get linkHighlighted() {
+ const found = LinkManager.Instance.getAllDirectLinks(this._props.annoDoc).find(link => {
+ const a1 = Doc.getOppositeAnchor(link, this._props.annoDoc);
+ return a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, a1));
+ });
+ return found;
}
- @undoBatch
- deleteAnnotation = () => {
- const docAnnotations = DocListCast(this._props.dataDoc[this._props.fieldKey]);
- this._props.dataDoc[this._props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion));
+ deleteAnnotation = undoable(() => {
+ const docAnnotations = DocListCast(this._props.containerDataDoc[this._props.fieldKey]);
+ this._props.containerDataDoc[this._props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this._props.annoDoc));
AnchorMenu.Instance.fadeOut(true);
this._props.select(false);
- };
+ }, 'delete annotation');
+
+ pinToPres = undoable(() => this._props.pinToPres(this._props.annoDoc, {}), 'pin to pres');
- @undoBatch
- pinToPres = () => this._props.pinToPres(this.annoTextRegion, {});
+ makeTargetToggle = undoable(() => { this._props.annoDoc.followLinkToggle = !this._props.annoDoc.followLinkToggle }, "set link toggle"); // prettier-ignore
- @undoBatch
- makeTargretToggle = () => (this.annoTextRegion.followLinkToggle = !this.annoTextRegion.followLinkToggle);
+ isTargetToggler = () => BoolCast(this._props.annoDoc.followLinkToggle);
- isTargetToggler = () => BoolCast(this.annoTextRegion.followLinkToggle);
- @undoBatch
- showTargetTrail = (anchor: Doc) => {
+ showTargetTrail = undoable((anchor: Doc) => {
const trail = DocCast(anchor.presentationTrail);
if (trail) {
Doc.ActivePresentation = trail;
this._props.addDocTab(trail, OpenWhere.replaceRight);
}
- };
+ }, 'show target trail');
@action
onContextMenu = (e: React.MouseEvent) => {
AnchorMenu.Instance.Status = 'annotation';
- AnchorMenu.Instance.Delete = this.deleteAnnotation.bind(this);
+ AnchorMenu.Instance.Delete = this.deleteAnnotation;
AnchorMenu.Instance.Pinned = false;
AnchorMenu.Instance.PinToPres = this.pinToPres;
- AnchorMenu.Instance.MakeTargetToggle = this.makeTargretToggle;
+ AnchorMenu.Instance.MakeTargetToggle = this.makeTargetToggle;
AnchorMenu.Instance.IsTargetToggler = this.isTargetToggler;
- AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this.annoTextRegion);
+ AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this._props.annoDoc);
AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
e.stopPropagation();
e.preventDefault();
@@ -94,44 +103,43 @@ class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps>
e.preventDefault();
} else if (e.button === 0) {
e.stopPropagation();
- LinkFollower.FollowLink(undefined, this.annoTextRegion, false);
+ DocumentView.FollowLink(undefined, this._props.annoDoc, false);
}
};
-
- @computed get linkHighlighted() {
- for (const link of LinkManager.Instance.getAllDirectLinks(this._props.document)) {
- const a1 = LinkManager.getOppositeAnchor(link, this._props.document);
- if (a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, this._props.document))) return true;
- }
- }
-
+ brushed = () => this._props.annoDoc && Doc.GetBrushHighlightStatus(this._props.annoDoc);
+ opacity = () => (this.brushed() === Doc.DocBrushStatus.highlighted ? 0.5 : 1);
+ outline = () => (this.linkHighlighted ? 'solid 1px lightBlue' : undefined);
+ background = () => (this._props.annoDoc[Highlight] ? 'orange' : StrCast(this._props.annoDoc.backgroundColor));
render() {
- const brushed = this.annoTextRegion && Doc.GetBrushHighlightStatus(this.annoTextRegion);
return (
- <div
- className="htmlAnnotation"
- ref={this._mainCont}
- onPointerEnter={action(() => {
- Doc.BrushDoc(this._props.anno);
- this._props.showInfo?.(this._props.anno);
- })}
- onPointerLeave={action(() => {
- Doc.UnBrushDoc(this._props.anno);
- this._props.showInfo?.(undefined);
- })}
- onPointerDown={this.onPointerDown}
- onContextMenu={this.onContextMenu}
- style={{
- left: NumCast(this._props.document.x),
- top: NumCast(this._props.document.y),
- width: NumCast(this._props.document._width),
- height: NumCast(this._props.document._height),
- opacity: brushed === Doc.DocBrushStatus.highlighted ? 0.5 : undefined,
- pointerEvents: this._props.pointerEvents?.() as any,
- outline: brushed === Doc.DocBrushStatus.unbrushed && this.linkHighlighted ? 'solid 1px lightBlue' : undefined,
- backgroundColor: brushed === Doc.DocBrushStatus.highlighted ? 'orange' : StrCast(this._props.document.backgroundColor),
- }}
- />
+ <div style={{ display: this._props.annoDoc.textCopied && !Doc.GetBrushHighlightStatus(this._props.annoDoc) ? 'none' : undefined }}>
+ {StrListCast(this._props.annoDoc.text_inlineAnnotations)
+ .map(a => a.split?.(':'))
+ .filter(fields => fields)
+ .map(([x, y, width, height]) => (
+ <div
+ key={'' + x + y + width + height}
+ style={{ pointerEvents: this._props.pointerEvents?.() as any }}
+ onPointerDown={this.onPointerDown}
+ onContextMenu={this.onContextMenu}
+ onPointerEnter={() => {
+ Doc.BrushDoc(this._props.annoDoc);
+ }}
+ onPointerLeave={() => {
+ Doc.UnBrushDoc(this._props.annoDoc);
+ }}>
+ <RegionAnnotation //
+ x={Number(x)}
+ y={Number(y)}
+ width={Number(width)}
+ height={Number(height)}
+ outline={this.outline}
+ background={this.background}
+ opacity={this.opacity}
+ />
+ </div>
+ ))}
+ </div>
);
}
}
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 29b1ca365..c1bfdf176 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -1,17 +1,19 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, EditableText, IconButton, Size, Type } from 'browndash-components';
+import { Button, IconButton, Type } from 'browndash-components';
import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { CgClose } from 'react-icons/cg';
import ReactLoading from 'react-loading';
import { TypeAnimation } from 'react-type-animation';
-import { Utils } from '../../../../Utils';
+import { ClientUtils } from '../../../../ClientUtils';
import { Doc } from '../../../../fields/Doc';
import { NumCast, StrCast } from '../../../../fields/Types';
import { Networking } from '../../../Network';
import { GPTCallType, gptAPICall, gptImageCall } from '../../../apis/gpt/GPT';
-import { DocUtils, Docs } from '../../../documents/Documents';
+import { Docs } from '../../../documents/Documents';
+import { DocUtils } from '../../../documents/DocUtils';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { AnchorMenu } from '../AnchorMenu';
import './GPTPopup.scss';
@@ -27,6 +29,7 @@ interface GPTPopupProps {}
@observer
export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
+ // eslint-disable-next-line no-use-before-define
static Instance: GPTPopup;
@observable private chatMode: boolean = false;
@@ -59,7 +62,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
public dataChatPrompt: string | null = null;
@action
public setDataJson = (text: string) => {
- if (text=="") this.dataChatPrompt = "";
+ if (text === '') this.dataChatPrompt = '';
this.dataJson = text;
};
@@ -87,8 +90,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
@observable
public highlightRange: number[] = [];
@action callSummaryApi = () => {};
- @action callEditApi = () => {};
- @action replaceText = (replacement: string) => {};
@observable
private done: boolean = false;
@@ -127,26 +128,27 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
* Generates a Dalle image and uploads it to the server.
*/
generateImage = async () => {
- if (this.imgDesc === '') return;
+ if (this.imgDesc === '') return undefined;
this.setImgUrls([]);
this.setMode(GPTPopupMode.IMAGE);
this.setVisible(true);
this.setLoading(true);
try {
- let image_urls = await gptImageCall(this.imgDesc);
- console.log('Image urls: ', image_urls);
- if (image_urls && image_urls[0]) {
- const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] });
+ const imageUrls = await gptImageCall(this.imgDesc);
+ console.log('Image urls: ', imageUrls);
+ if (imageUrls && imageUrls[0]) {
+ const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [imageUrls[0]] });
console.log('Upload result: ', result);
- const source = Utils.prepend(result.accessPaths.agnostic.client);
+ const source = ClientUtils.prepend(result.accessPaths.agnostic.client);
console.log('Upload source: ', source);
- this.setImgUrls([[image_urls[0], source]]);
+ this.setImgUrls([[imageUrls[0], source]]);
}
} catch (err) {
console.error(err);
}
this.setLoading(false);
+ return undefined;
};
generateSummary = async () => {
@@ -161,19 +163,19 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
console.error(err);
}
GPTPopup.Instance.setLoading(false);
- }
+ };
generateDataAnalysis = async () => {
GPTPopup.Instance.setVisible(true);
GPTPopup.Instance.setLoading(true);
try {
- let res = await gptAPICall(this.dataJson, GPTCallType.DATA, this.dataChatPrompt);
+ const res = await gptAPICall(this.dataJson, GPTCallType.DATA, this.dataChatPrompt);
GPTPopup.Instance.setText(res || 'Something went wrong.');
} catch (err) {
console.error(err);
}
GPTPopup.Instance.setLoading(false);
- }
+ };
/**
* Transfers the summarization text to a sidebar annotation text document.
@@ -224,7 +226,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
*/
private chatWithAI = () => {
this.chatMode = true;
- }
+ };
dataPromptChanged = action((e: React.ChangeEvent<HTMLInputElement>) => {
this.dataChatPrompt = e.target.value;
});
@@ -243,30 +245,24 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
}
};
- imageBox = () => {
- return (
- <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
- {this.heading('GENERATED IMAGE')}
- <div className="image-content-wrapper">
- {this.imgUrls.map(rawSrc => (
- <div className="img-wrapper">
- <div className="img-container">
- <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" />
- </div>
- <div className="btn-container">
- <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
- </div>
+ imageBox = () => (
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
+ {this.heading('GENERATED IMAGE')}
+ <div className="image-content-wrapper">
+ {this.imgUrls.map(rawSrc => (
+ <div className="img-wrapper">
+ <div className="img-container">
+ <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" />
</div>
- ))}
- </div>
- {!this.loading && (
- <>
- <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
- </>
- )}
+ <div className="btn-container">
+ <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
+ </div>
+ </div>
+ ))}
</div>
- );
- };
+ {!this.loading && <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />}
+ </div>
+ );
summaryBox = () => (
<>
@@ -320,7 +316,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
dataAnalysisBox = () => (
<>
<div>
- {this.heading("ANALYSIS")}
+ {this.heading('ANALYSIS')}
<div className="content-wrapper">
{!this.loading &&
(!this.done ? (
@@ -342,8 +338,8 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
</div>
{!this.loading && (
<div className="btns-wrapper">
- {this.done?
- this.chatMode?(
+ {this.done ? (
+ this.chatMode ? (
<input
defaultValue=""
autoComplete="off"
@@ -356,19 +352,26 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
placeholder="Ask GPT a question about the data..."
id="search-input"
className="searchBox-input"
- style={{width: "100%"}}
+ style={{ width: '100%' }}
/>
- )
- :(
- <>
- <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
- <Button tooltip="Chat with AI" text="Chat with AI" onClick={this.chatWithAI} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
- </>
+ ) : (
+ <>
+ <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
+ <Button tooltip="Chat with AI" text="Chat with AI" onClick={this.chatWithAI} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
+ </>
+ )
) : (
<div className="summarizing">
<span>Summarizing</span>
<ReactLoading type="bubbles" color="#bcbcbc" width={20} height={20} />
- <Button text="Stop Animation" onClick={() => {this.setDone(true);}} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT}/>
+ <Button
+ text="Stop Animation"
+ onClick={() => {
+ this.setDone(true);
+ }}
+ color={StrCast(Doc.UserDoc().userVariantColor)}
+ type={Type.TERT}
+ />
</div>
)}
</div>
@@ -382,9 +385,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
<FontAwesomeIcon icon="exclamation-circle" size="sm" style={{ paddingRight: '5px' }} />
AI generated responses can contain inaccurate or misleading content.
</div>
- ) : (
- <></>
- );
+ ) : null;
heading = (headingText: string) => (
<div className="summary-heading">
@@ -396,7 +397,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
render() {
return (
<div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}>
- {this.mode === GPTPopupMode.SUMMARY? this.summaryBox() : this.mode === GPTPopupMode.DATA? this.dataAnalysisBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : <></>}
+ {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.DATA ? this.dataAnalysisBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : null}
</div>
);
}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index aaff2a342..45b1d727e 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,34 +1,37 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as Pdfjs from 'pdfjs-dist';
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 { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, returnAll, returnFalse, returnNone, returnZero, smoothScroll } from '../../../ClientUtils';
+import { CreateLinkToActiveAudio, Doc, DocListCast, Opt } from '../../../fields/Doc';
import { DocData, Height } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnAll, returnFalse, returnNone, returnZero, smoothScroll, Utils } from '../../../Utils';
-import { DocUtils } from '../../documents/Documents';
-import { SelectionManager } from '../../util/SelectionManager';
+import { emptyFunction } from '../../../Utils';
+import { DocUtils } from '../../documents/DocUtils';
import { SnappingManager } from '../../util/SnappingManager';
import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
-import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView';
+import { DocumentView } from '../nodes/DocumentView';
+import { FieldViewProps } from '../nodes/FieldView';
+import { FocusViewOptions } from '../nodes/FocusViewOptions';
import { LinkInfo } from '../nodes/LinkDocPreview';
import { PDFBox } from '../nodes/PDFBox';
import { ObservableReactComponent } from '../ObservableReactComponent';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
import { AnchorMenu } from './AnchorMenu';
import { Annotation } from './Annotation';
import { GPTPopup } from './GPTPopup/GPTPopup';
import './PDFViewer.scss';
-const _global = (window /* browser */ || global) /* node */ as any;
-//pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
+// pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
// The workerSrc property shall be specified.
Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.1.392/build/pdf.worker.mjs';
@@ -42,6 +45,7 @@ interface IViewerProps extends FieldViewProps {
url: string;
sidebarAddDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean;
loaded?: (nw: number, nh: number, np: number) => void;
+ // eslint-disable-next-line no-use-before-define
setPdfViewer: (view: PDFViewer) => void;
anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
crop: (region: Doc | undefined, addCrop?: boolean) => Doc | undefined;
@@ -98,14 +102,18 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
}
componentDidMount() {
- runInAction(() => (this._showWaiting = true));
+ runInAction(() => {
+ this._showWaiting = true;
+ });
this.setupPdfJsViewer();
- this._mainCont.current?.addEventListener('scroll', e => ((e.target as any).scrollLeft = 0));
+ this._mainCont.current?.addEventListener('scroll', e => {
+ (e.target as any).scrollLeft = 0;
+ });
this._disposers.layout_autoHeight = reaction(
() => this._props.layoutDoc._layout_autoHeight,
- layout_autoHeight => {
- if (layout_autoHeight) {
+ layoutAutoHeight => {
+ if (layoutAutoHeight) {
this._props.layoutDoc._nativeHeight = NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']);
this._props.setHeight?.(NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
}
@@ -114,7 +122,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
this._disposers.selected = reaction(
() => this._props.isSelected(),
- selected => SelectionManager.Views.length === 1 && this.setupPdfJsViewer(),
+ () => DocumentView.Selected().length === 1 && this.setupPdfJsViewer(),
{ fireImmediately: true }
);
this._disposers.curPage = reaction(
@@ -164,7 +172,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
)
);
}
- runInAction(() => (this._scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72));
+ runInAction(() => {
+ this._scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72;
+ });
};
_scrollStopper: undefined | (() => void);
@@ -176,7 +186,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
let focusSpeed: Opt<number>;
if (doc !== this._props.Document && mainCont) {
const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
- const scrollTo = Utils.scrollIntoView(scrollTop, doc[Height](), NumCast(this._props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
+ const scrollTo = ClientUtils.scrollIntoView(scrollTop, doc[Height](), NumCast(this._props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
if (scrollTo !== undefined && scrollTo !== this._props.layoutDoc._layout_scrollTop) {
if (!this._pdfViewer) this._initialScroll = { loc: scrollTo, easeFunc: options.easeFunc };
else if (!options.instant) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper);
@@ -202,14 +212,18 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
pagesinit = () => {
if (this._pdfViewer._setDocumentViewerElement?.offsetParent) {
- runInAction(() => (this._pdfViewer.currentScaleValue = this._props.layoutDoc._freeform_scale = 1));
+ runInAction(() => {
+ this._pdfViewer.currentScaleValue = this._props.layoutDoc._freeform_scale = 1;
+ });
this.gotoPage(NumCast(this._props.Document._layout_curPage, 1));
}
document.removeEventListener('pagesinit', this.pagesinit);
- var quickScroll: { loc?: string; easeFunc?: 'ease' | 'linear' } | undefined = { loc: this._initialScroll ? this._initialScroll.loc?.toString() : '', easeFunc: this._initialScroll ? this._initialScroll.easeFunc : undefined };
+ let quickScroll: { loc?: string; easeFunc?: 'ease' | 'linear' } | undefined = { loc: this._initialScroll ? this._initialScroll.loc?.toString() : '', easeFunc: this._initialScroll ? this._initialScroll.easeFunc : undefined };
this._disposers.scale = reaction(
() => NumCast(this._props.layoutDoc._freeform_scale, 1),
- scale => (this._pdfViewer.currentScaleValue = scale),
+ scale => {
+ this._pdfViewer.currentScaleValue = scale;
+ },
{ fireImmediately: true }
);
this._disposers.scroll = reaction(
@@ -226,7 +240,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
setTimeout(
() => {
this._mainCont.current && (this._scrollStopper = smoothScroll(duration, this._mainCont.current, pos, this._initialScroll?.easeFunc ?? 'ease', this._scrollStopper));
- setTimeout(() => (this._forcedScroll = false), duration);
+ setTimeout(() => {
+ this._forcedScroll = false;
+ }, duration);
},
this._mainCont.current ? 0 : 250
); // wait for mainCont and try again to scroll
@@ -262,7 +278,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
eventBus._on('pagesinit', this.pagesinit);
eventBus._on(
'pagerendered',
- action(() => (this._showWaiting = false))
+ action(() => {
+ this._showWaiting = false;
+ })
);
const pdfLinkService = new PDFJSViewer.PDFLinkService({ eventBus });
const pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, eventBus });
@@ -305,7 +323,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
@observable private _scrollTimer: any = undefined;
- onScroll = (e: React.UIEvent<HTMLElement>) => {
+ onScroll = () => {
if (this._mainCont.current && !this._forcedScroll) {
this._ignoreScroll = true; // the pdf scrolled, so we need to tell the Doc to scroll but we don't want the doc to then try to set the PDF scroll pos (which would interfere with the smooth scroll animation)
if (!LinkInfo.Instance?.LinkInfo) {
@@ -314,7 +332,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
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.pdfBox.getAnchor(true)!, false);
+ CreateLinkToActiveAudio(() => this._props.pdfBox.getAnchor(true)!, false);
this._scrollTimer = undefined;
}, 200);
}
@@ -340,7 +358,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
query: searchString,
};
if (clear) {
- this._pdfViewer?.eventBus.dispatch('reset', {});
+ this._pdfViewer?.eventBus.dispatch('findbarclose', {});
} else if (!searchString) {
bwd ? this.prevAnnotation() : this.nextAnnotation();
} else if (this._pdfViewer?.pageViewsReady) {
@@ -371,7 +389,6 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![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;
const target = e.target as any;
if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) {
@@ -383,11 +400,12 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, 'htmlAnnotation', { 'pointer-events': 'none' });
document.addEventListener('pointerup', this.onSelectEnd);
}
+ this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
}
};
@action
- finishMarquee = (x?: number, y?: number) => {
+ finishMarquee = (/* x?: number, y?: number */) => {
this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this.isAnnotating = false;
this._marqueeref.current?.onTerminateSelection();
@@ -456,13 +474,15 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
onClick = (e: React.MouseEvent) => {
this._scrollStopper?.();
- if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+ if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < ClientUtils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < ClientUtils.DRAG_THRESHOLD) {
this._setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document);
}
// e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks
};
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => {
+ this._setPreviewCursor = func;
+ };
@action
onZoomWheel = (e: React.WheelEvent) => {
@@ -485,7 +505,8 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
return (
<div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this._props.Document), transform: `scale(${NumCast(this._props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
{inlineAnnos.map(anno => (
- <Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} dataDoc={this._props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ <Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} containerDataDoc={this._props.dataDoc} annoDoc={anno} key={`${anno[Id]}-annotation`} />
))}
</div>
);
@@ -496,8 +517,8 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
overlayTransform = () => this.scrollXf().scale(1 / NumCast(this._props.layoutDoc._freeform_scale, 1));
panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1);
panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
- transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
- opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [Utils.OpaqueBackgroundFilter])];
+ transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter];
+ opaqueFilter = () => [...this._props.childFilters(), ClientUtils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [ClientUtils.OpaqueBackgroundFilter])];
childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
if (doc instanceof Doc && property === StyleProp.PointerEvents) {
if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none';
@@ -517,6 +538,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
pointerEvents: Doc.ActiveTool !== InkTool.None ? 'all' : undefined,
}}>
<CollectionFreeFormView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
NativeWidth={returnZero}
NativeHeight={returnZero}
@@ -524,7 +546,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
pointerEvents={this._props.isContentActive() && (SnappingManager.IsDragging || Doc.ActiveTool !== InkTool.None) ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it.
childPointerEvents={this.childPointerEvents} // but freeform children need to get events to allow text editing, etc
renderDepth={this._props.renderDepth + 1}
- isAnnotationOverlay={true}
+ isAnnotationOverlay
fieldKey={this._props.fieldKey + '_annotations'}
getScrollHeight={this.getScrollHeight}
setPreviewCursor={this.setPreviewCursor}
@@ -532,7 +554,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
PanelWidth={this.panelWidth}
ScreenToLocalTransform={this.overlayTransform}
isAnyChildContentActive={returnFalse}
- isAnnotationOverlayScrollable={true}
+ isAnnotationOverlayScrollable
childFilters={childFilters}
select={emptyFunction}
styleProvider={this.childStyleProvider}
@@ -577,7 +599,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
{this.pdfViewerDiv}
{this.annotationLayer}
{this.overlayLayer}
- {this._showWaiting ? <img className="pdfViewerDash-waiting" src={'/assets/loading.gif'} /> : null}
+ {this._showWaiting ? <img alt="" className="pdfViewerDash-waiting" src="/assets/loading.gif" /> : null}
{!this._mainCont.current || !this._annotationLayer.current ? null : (
<MarqueeAnnotator
ref={this._marqueeref}
@@ -585,7 +607,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
getPageFromScroll={this.getPageFromScroll}
anchorMenuClick={this._props.anchorMenuClick}
scrollTop={0}
- isNativeScaled={true}
+ isNativeScaled
annotationLayerScrollTop={NumCast(this._props.Document._layout_scrollTop)}
addDocument={this.addDocumentWrapper}
docView={this._props.pdfBox.DocumentView!}
@@ -594,7 +616,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
selectionText={this.selectionText}
annotationLayer={this._annotationLayer.current}
marqueeContainer={this._mainCont.current}
- anchorMenuCrop={this._textSelecting ? undefined : this.crop}
+ anchorMenuCrop={this.crop}
/>
)}
</div>
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 9f153e86d..56552c952 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -1,22 +1,26 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { Tooltip } from '@mui/material';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCastAsync, Field } from '../../../fields/Doc';
+import { ClientUtils } from '../../../ClientUtils';
+import { Doc, DocListCastAsync, Field, FieldType } from '../../../fields/Doc';
import { DirectLinks, DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { DocCast, StrCast } from '../../../fields/Types';
+import { DocUtils } from '../../documents/DocUtils';
import { DocumentType } from '../../documents/DocumentTypes';
-import { DocUtils } from '../../documents/Documents';
-import { DocumentManager } from '../../util/DocumentManager';
-import { LinkManager } from '../../util/LinkManager';
+import { Docs } from '../../documents/Documents';
import { SearchUtil } from '../../util/SearchUtil';
-import { SettingsManager } from '../../util/SettingsManager';
+import { SnappingManager } from '../../util/SnappingManager';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxBaseComponent } from '../DocComponent';
+import { ObservableReactComponent } from '../ObservableReactComponent';
import { CollectionDockingView } from '../collections/CollectionDockingView';
import { IRecommendation, Recommendation } from '../newlightbox/components';
import { fetchRecommendations } from '../newlightbox/utils';
+import { DocumentView } from '../nodes/DocumentView';
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import './SearchBox.scss';
@@ -24,6 +28,96 @@ const DAMPENING_FACTOR = 0.9;
const MAX_ITERATIONS = 25;
const ERROR = 0.03;
+export interface SearchBoxItemProps {
+ Document: Doc;
+ searchString: string;
+ isLinkSearch: boolean;
+ matchedKeys: string[];
+ className: string;
+ linkFrom: Doc | undefined;
+ selectItem: (doc: Doc) => void;
+ linkCreateAnchor?: () => Doc | undefined;
+ linkCreated?: (link: Doc) => void;
+}
+@observer
+export class SearchBoxItem extends ObservableReactComponent<SearchBoxItemProps> {
+ constructor(props: SearchBoxItemProps) {
+ super(props);
+ makeObservable(this);
+ }
+
+ /**
+ * @param {Doc} doc - doc to be selected
+ *
+ * This method selects a doc by either jumping to it (centering/zooming in on it)
+ * or opening it in a new tab.
+ */
+ selectElement = async (doc: Doc, finishFunc: () => void) => {
+ await DocumentView.showDocument(doc, { willZoomCentered: true }, finishFunc);
+ };
+
+ /**
+ * @param {Doc} doc - doc of the search result that has been clicked on
+ *
+ * This method is called when the user clicks on a search result. The _selectedResult is
+ * updated accordingly and the doc is highlighted with the selectElement method.
+ */
+ onResultClick = action(async (doc: Doc) => {
+ this._props.selectItem(doc);
+ this.selectElement(doc, () => DocumentView.getFirstDocumentView(doc)?.ComponentView?.search?.(this._props.searchString, undefined, false));
+ });
+
+ componentWillUnmount(): void {
+ const doc = this._props.Document;
+ DocumentView.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true);
+ }
+
+ @undoBatch
+ makeLink = action((linkTo: Doc) => {
+ const linkFrom = this._props.linkCreateAnchor?.();
+ if (linkFrom) {
+ const link = DocUtils.MakeLink(linkFrom, linkTo, {});
+ link && this._props.linkCreated?.(link);
+ }
+ });
+
+ render() {
+ // eslint-disable-next-line no-use-before-define
+ const formattedType = SearchBox.formatType(StrCast(this._props.Document.type), StrCast(this._props.Document.type_collection));
+ const { title } = this._props.Document;
+
+ return (
+ <Tooltip placement="right" title={<div className="dash-tooltip">{title as string}</div>}>
+ <div
+ onClick={
+ this._props.isLinkSearch
+ ? () => this.makeLink(this._props.Document)
+ : e => {
+ this.onResultClick(this._props.Document);
+ e.stopPropagation();
+ }
+ }
+ style={{
+ fontWeight: Doc.Links(this._props.linkFrom).find(
+ link => Doc.AreProtosEqual(Doc.getOppositeAnchor(link, this._props.linkFrom!), this._props.Document) || Doc.AreProtosEqual(DocCast(Doc.getOppositeAnchor(link, this._props.linkFrom!)?.annotationOn), this._props.Document)
+ )
+ ? 'bold'
+ : '',
+ }}
+ className={this._props.className}>
+ <div className="searchBox-result-title">{title as string}</div>
+ <div className="searchBox-result-type" style={{ color: SnappingManager.userVariantColor }}>
+ {formattedType}
+ </div>
+ <div className="searchBox-result-keys" style={{ color: SnappingManager.userVariantColor }}>
+ {this._props.matchedKeys.join(', ')}
+ </div>
+ </div>
+ </Tooltip>
+ );
+ }
+}
+
export interface SearchBoxProps extends FieldViewProps {
linkSearch: boolean;
linkFrom?: (() => Doc | undefined) | undefined;
@@ -40,6 +134,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(SearchBox, fieldKey);
}
+ // eslint-disable-next-line no-use-before-define
public static Instance: SearchBox;
private _inputRef = React.createRef<HTMLInputElement>();
@@ -109,46 +204,29 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
});
/**
- * @param {Doc} doc - doc of the search result that has been clicked on
- *
- * This method is called when the user clicks on a search result. The _selectedResult is
- * updated accordingly and the doc is highlighted with the selectElement method.
- */
- onResultClick = action(async (doc: Doc) => {
- this._selectedResult = doc;
- this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, false));
- });
-
- @undoBatch
- makeLink = action((linkTo: Doc) => {
- const linkFrom = this._props.linkCreateAnchor?.();
- if (linkFrom) {
- const link = DocUtils.MakeLink(linkFrom, linkTo, {});
- link && this._props.linkCreated?.(link);
- }
- });
-
- /**
* @param {Doc[]} docs - docs to be searched through recursively
* @param {number, Doc => void} func - function to be called on each doc
*
* This method iterates asynchronously through an array of docs and all docs within those
* docs, calling the function func on each doc.
*/
- static async foreachRecursiveDocAsync(docs: Doc[], func: (depth: number, doc: Doc) => void) {
+ static async foreachRecursiveDocAsync(docsIn: Doc[], func: (depth: number, doc: Doc) => void) {
+ let docs = docsIn;
let newarray: Doc[] = [];
- var depth = 0;
+ let depth = 0;
while (docs.length > 0) {
newarray = [];
+ // eslint-disable-next-line no-await-in-loop
await Promise.all(
docs
.filter(d => d)
+ // eslint-disable-next-line no-loop-func
.map(async d => {
const fieldKey = Doc.LayoutFieldKey(d);
- const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView');
+ const annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes('CollectionView');
const data = d[annos ? fieldKey + '_annotations' : fieldKey];
- const docs = await DocListCastAsync(data);
- docs && newarray.push(...docs);
+ const dataDocs = await DocListCastAsync(data);
+ dataDocs && newarray.push(...dataDocs);
func(depth, d);
})
);
@@ -170,6 +248,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
case DocumentType.IMG : return 'Img';
case DocumentType.RTF : return 'Rtf';
case DocumentType.COL : return 'Col:'+colType.substring(0,3);
+ default:
} // prettier-ignore
return type.charAt(0).toUpperCase() + type.substring(1, 3);
@@ -210,7 +289,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
if (doc[DocData][DirectLinks].size === 0) {
this._linkedDocsOut.set(doc, new Set(this._results.keys()));
- this._results.forEach((_, linkedDoc) => {
+ this._results.forEach((__, linkedDoc) => {
this._linkedDocsIn.get(linkedDoc)?.add(doc);
});
} else {
@@ -244,7 +323,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
pageRankIteration(): boolean {
let converged = true;
const pageRankFromAll = (1 - DAMPENING_FACTOR) / this._results.size;
-
const nextPageRanks = new Map<Doc, number>();
this._results.forEach((_, doc) => {
@@ -292,41 +370,33 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
const query = StrCast(this._searchString);
Doc.SetSearchQuery(query);
- if (!this._props.linkSearch) Array.from(this._results.keys()).forEach(doc => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true));
+ if (!this._props.linkSearch) Array.from(this._results.keys()).forEach(doc => DocumentView.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true));
this._results.clear();
if (query) {
this.searchCollection(query);
const response = await fetchRecommendations('', query, [], true);
- const recs = response.recommendations;
+ const recs = response.recommendations as any[];
const recommendations: IRecommendation[] = [];
- for (const key in recs) {
- const title = recs[key].title;
- const url = recs[key].url;
- const type = recs[key].type;
- const text = recs[key].text;
- const transcript = recs[key].transcript;
- const previewUrl = recs[key].previewUrl;
- const embedding = recs[key].embedding;
- const distance = recs[key].distance;
- const source = recs[key].source;
- const related_concepts = recs[key].related_concepts;
- const docId = recs[key].doc_id;
+ recs.forEach(rec => {
+ const { title, url, type, text, transcript, previewUrl, embedding, distance, source, related_concepts: relatedConcepts, doc_id: docId } = rec;
recommendations.push({
- title: title,
+ title,
data: url,
- type: type,
- text: text,
- transcript: transcript,
- previewUrl: previewUrl,
- embedding: embedding,
+ type,
+ text,
+ transcript,
+ previewUrl,
+ embedding,
distance: Math.round(distance * 100) / 100,
source: source,
- related_concepts: related_concepts,
- docId: docId,
+ related_concepts: relatedConcepts,
+ docId,
});
- }
- const setRecommendations = action(() => (this._recommendations = recommendations));
+ });
+ const setRecommendations = action(() => {
+ this._recommendations = recommendations;
+ });
setRecommendations();
}
};
@@ -339,7 +409,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
this._timeout && clearTimeout(this._timeout);
this._timeout = undefined;
this._results.forEach((_, doc) => {
- DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true);
+ DocumentView.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true);
Doc.UnBrushDoc(doc);
Doc.UnHighlightDoc(doc);
Doc.ClearSearchMatches();
@@ -347,16 +417,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
});
/**
- * @param {Doc} doc - doc to be selected
- *
- * This method selects a doc by either jumping to it (centering/zooming in on it)
- * or opening it in a new tab.
- */
- selectElement = async (doc: Doc, finishFunc: () => void) => {
- await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, finishFunc);
- };
-
- /**
* This method returns a JSX list of the options in the select drop-down menu, which
* is used to filter the types of documents that appear in the search results.
*/
@@ -366,7 +426,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
return selectValues.map(value => (
<option key={value} value={value}>
- {SearchBox.formatType(value, '')}
+ {ClientUtils.cleanDocumentTypeExt(value as DocumentType)}
</option>
));
}
@@ -375,64 +435,45 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
* This method renders the search input box, select drop-down menu, and search results.
*/
render() {
- var validResults = 0;
-
const isLinkSearch: boolean = this._props.linkSearch;
-
const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)); // sorted by page rank
+ const resultsJSX = [] as any[];
+ const linkFrom = this._props.linkFrom?.();
- const resultsJSX = Array();
-
- const fromDoc = this._props.linkFrom?.();
+ let validResults = 0;
+ sortedResults.forEach(([Document, matchedKeys]) => {
+ let className = 'searchBox-results-scroll-view-result';
- sortedResults.forEach(result => {
- var className = 'searchBox-results-scroll-view-result';
-
- if (this._selectedResult === result[0]) {
+ if (this._selectedResult === Document) {
className += ' searchBox-results-scroll-view-result-selected';
}
- const formattedType = SearchBox.formatType(StrCast(result[0].type), StrCast(result[0].type_collection));
- const title = result[0].title;
-
- if (this._docTypeString === 'keys' || this._docTypeString === 'all' || this._docTypeString === result[0].type) {
+ if (this._docTypeString === 'keys' || this._docTypeString === 'all' || this._docTypeString === Document.type) {
validResults++;
resultsJSX.push(
- <Tooltip key={result[0][Id]} placement={'right'} title={<div className="dash-tooltip">{title as string}</div>}>
- <div
- onClick={
- isLinkSearch
- ? () => this.makeLink(result[0])
- : e => {
- this.onResultClick(result[0]);
- e.stopPropagation();
- }
- }
- style={{
- fontWeight: LinkManager.Links(fromDoc).find(
- link => Doc.AreProtosEqual(LinkManager.getOppositeAnchor(link, fromDoc!), result[0] as Doc) || Doc.AreProtosEqual(DocCast(LinkManager.getOppositeAnchor(link, fromDoc!)?.annotationOn), result[0] as Doc)
- )
- ? 'bold'
- : '',
- }}
- className={className}>
- <div className="searchBox-result-title">{title as string}</div>
- <div className="searchBox-result-type" style={{ color: SettingsManager.userVariantColor }}>
- {formattedType}
- </div>
- <div className="searchBox-result-keys" style={{ color: SettingsManager.userVariantColor }}>
- {result[1].join(', ')}
- </div>
- </div>
- </Tooltip>
+ <SearchBoxItem
+ key={Document[Id]}
+ Document={Document}
+ selectItem={action((doc: Doc) => {
+ this._selectedResult = doc;
+ })}
+ isLinkSearch={isLinkSearch}
+ searchString={this._searchString}
+ matchedKeys={matchedKeys}
+ linkFrom={linkFrom}
+ className={className}
+ linkCreateAnchor={this._props.linkCreateAnchor}
+ linkCreated={this._props.linkCreated}
+ />
);
}
});
+ // eslint-disable-next-line react/jsx-props-no-spreading
const recommendationsJSX: JSX.Element[] = this._recommendations.map(props => <Recommendation {...props} />);
return (
- <div className="searchBox-container" style={{ pointerEvents: 'all', color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}>
+ <div className="searchBox-container" style={{ pointerEvents: 'all', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}>
<div className="searchBox-bar">
{isLinkSearch ? null : (
<select name="type" id="searchBox-type" className="searchBox-type" onChange={this.onSelectChange}>
@@ -457,18 +498,18 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
</div>
{resultsJSX.length > 0 && (
<div className="searchBox-results-container">
- <div className="section-header" style={{ background: SettingsManager.userVariantColor }}>
+ <div className="section-header" style={{ background: SnappingManager.userVariantColor }}>
<div className="section-title">Results</div>
- <div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div>
+ <div className="section-subtitle">{`${validResults} result` + (validResults === 1 ? '' : 's')}</div>
</div>
<div className="searchBox-results-view">{resultsJSX}</div>
</div>
)}
{recommendationsJSX.length > 0 && (
<div className="searchBox-recommendations-container">
- <div className="section-header" style={{ background: SettingsManager.userVariantColor }}>
+ <div className="section-header" style={{ background: SnappingManager.userVariantColor }}>
<div className="section-title">Recommendations</div>
- <div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div>
+ <div className="section-subtitle">{`${validResults} result` + (validResults === 1 ? '' : 's')}</div>
</div>
<div className="searchBox-recommendations-view">{recommendationsJSX}</div>
</div>
@@ -477,3 +518,8 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
);
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.SEARCH, {
+ layout: { view: SearchBox, dataField: 'data' },
+ options: { acl: '', _width: 400 },
+});
diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx
index c9c01189e..78a1a92f7 100644
--- a/src/client/views/selectedDoc/SelectedDocView.tsx
+++ b/src/client/views/selectedDoc/SelectedDocView.tsx
@@ -6,9 +6,9 @@ import * as React from 'react';
import { emptyFunction } from '../../../Utils';
import { Doc } from '../../../fields/Doc';
import { StrCast } from '../../../fields/Types';
-import { DocumentManager } from '../../util/DocumentManager';
-import { SettingsManager } from '../../util/SettingsManager';
-import { FocusViewOptions } from '../nodes/FieldView';
+import { SnappingManager } from '../../util/SnappingManager';
+import { DocumentView } from '../nodes/DocumentView';
+import { FocusViewOptions } from '../nodes/FocusViewOptions';
export interface SelectedDocViewProps {
selectedDocs: Doc[];
@@ -33,14 +33,14 @@ export class SelectedDocView extends React.Component<SelectedDocViewProps> {
return {
text: StrCast(doc.title),
val: StrCast(doc._id),
- color: SettingsManager.userColor,
- background: SettingsManager.userBackgroundColor,
- icon: <FontAwesomeIcon size={'1x'} icon={Doc.toIcon(doc)} />,
- onClick: () => DocumentManager.Instance.showDocument(doc, options, emptyFunction),
+ color: SnappingManager.userColor,
+ background: SnappingManager.userBackgroundColor,
+ icon: <FontAwesomeIcon size="1x" icon={Doc.toIcon(doc)} />,
+ onClick: () => DocumentView.showDocument(doc, options, emptyFunction),
};
})}
- color={SettingsManager.userColor}
- background={SettingsManager.userBackgroundColor}
+ color={SnappingManager.userColor}
+ background={SnappingManager.userBackgroundColor}
/>
</div>
);
diff --git a/src/client/views/selectedDoc/index.ts b/src/client/views/selectedDoc/index.ts
index 1f1db91f6..968e9c2d4 100644
--- a/src/client/views/selectedDoc/index.ts
+++ b/src/client/views/selectedDoc/index.ts
@@ -1 +1 @@
-export * from './SelectedDocView' \ No newline at end of file
+export * from './SelectedDocView';
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index eab33114e..9f97efcce 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -5,14 +5,13 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { Flip } from 'react-awesome-reveal';
import { FaBug } from 'react-icons/fa';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils';
import { Doc, DocListCast } from '../../../fields/Doc';
import { AclAdmin, DashVersion } from '../../../fields/DocSymbols';
import { StrCast } from '../../../fields/Types';
import { GetEffectiveAcl } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
-import { CurrentUserUtils } from '../../util/CurrentUserUtils';
-import { DocumentManager } from '../../util/DocumentManager';
-import { dropActionType } from '../../util/DragManager';
+import { emptyFunction } from '../../../Utils';
+import { dropActionType } from '../../util/DropActionTypes';
import { PingManager } from '../../util/PingManager';
import { ReportManager } from '../../util/reportManager/ReportManager';
import { ServerStats } from '../../util/ServerStats';
@@ -24,7 +23,7 @@ import { CollectionDockingView } from '../collections/CollectionDockingView';
import { CollectionLinearView } from '../collections/collectionLinear';
import { DashboardView } from '../DashboardView';
import { Colors } from '../global/globalEnums';
-import { DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView';
import { ObservableReactComponent } from '../ObservableReactComponent';
import { DefaultStyleProvider } from '../StyleProvider';
import './TopBar.scss';
@@ -35,6 +34,7 @@ import './TopBar.scss';
*/
@observer
export class TopBar extends ObservableReactComponent<{}> {
+ // eslint-disable-next-line no-use-before-define
static Instance: TopBar;
@observable private _flipDocumentation = 0;
constructor(props: any) {
@@ -44,10 +44,12 @@ export class TopBar extends ObservableReactComponent<{}> {
}
navigateToHome = () => {
- (CollectionDockingView.Instance?.CaptureThumbnail() ?? new Promise<void>(res => res())).then(() => {
+ (CollectionDockingView.Instance?.CaptureThumbnail() ??
+ new Promise<void>(res => { res(); })) .then(() =>
+ {
Doc.ActivePage = 'home';
DashboardView.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use
- });
+ }); // prettier-ignore
};
@computed get color() {
@@ -61,7 +63,9 @@ export class TopBar extends ObservableReactComponent<{}> {
}
@observable happyHeart: boolean = PingManager.Instance.IsBeating;
- setHappyHeart = action((status: boolean) => (this.happyHeart = status));
+ setHappyHeart = action((status: boolean) => {
+ this.happyHeart = status;
+ });
dispose = reaction(
() => PingManager.Instance.IsBeating,
isBeating => this.setHappyHeart(isBeating)
@@ -85,7 +89,7 @@ export class TopBar extends ObservableReactComponent<{}> {
/>
) : (
<div className="logo-container">
- <img className="logo" src="/assets/medium-blue-light-blue-circle.png" alt="dash logo"></img>
+ <img className="logo" src="/assets/medium-blue-light-blue-circle.png" alt="dash logo" />
<span style={{ color: isDark(this.backgroundColor) ? Colors.LIGHT_GRAY : Colors.DARK_GRAY, fontWeight: 200 }}>brown</span>
<span style={{ color: isDark(this.backgroundColor) ? Colors.LIGHT_BLUE : Colors.MEDIUM_BLUE, fontWeight: 500 }}>dash</span>
</div>
@@ -164,7 +168,7 @@ export class TopBar extends ObservableReactComponent<{}> {
color={this.color}
style={{ fontWeight: 700, fontSize: '1rem' }}
onClick={(e: React.MouseEvent) => {
- const dashView = Doc.ActiveDashboard && DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard);
+ const dashView = Doc.ActiveDashboard && DocumentView.getDocumentView(Doc.ActiveDashboard);
dashView?.showContextMenu(e.clientX + 20, e.clientY + 30);
}}
/>
@@ -178,7 +182,7 @@ export class TopBar extends ObservableReactComponent<{}> {
* and allows the user to access their account settings etc.
*/
@computed get topbarRight() {
- const upToDate = DashVersion === CurrentUserUtils.ServerVersion;
+ const upToDate = DashVersion === SnappingManager.ServerVersion;
return (
<div className="topbar-right">
{Doc.ActiveDashboard ? (
@@ -192,11 +196,11 @@ export class TopBar extends ObservableReactComponent<{}> {
}}
/>
) : null}
- <IconButton tooltip={'Issue Reporter ⌘I'} size={Size.SMALL} color={this.color} onClick={ReportManager.Instance.open} icon={<FaBug />} />
+ <IconButton tooltip="Issue Reporter ⌘I" size={Size.SMALL} color={this.color} onClick={ReportManager.Instance.open} icon={<FaBug />} />
<Flip key={this._flipDocumentation}>
- <IconButton tooltip={'Documentation ⌘D'} size={Size.SMALL} color={this.color} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} />
+ <IconButton tooltip="Documentation ⌘D" size={Size.SMALL} color={this.color} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} />
</Flip>
- <IconButton tooltip={'Settings ⌘⇧S'} size={Size.SMALL} color={this.color} onClick={SettingsManager.Instance.open} icon={<FontAwesomeIcon icon="cog" />} />
+ <IconButton tooltip="Settings ⌘⇧S" size={Size.SMALL} color={this.color} onClick={SettingsManager.Instance.openMgr} icon={<FontAwesomeIcon icon="cog" />} />
<IconButton
size={Size.SMALL}
onClick={ServerStats.Instance.open}
@@ -213,11 +217,13 @@ export class TopBar extends ObservableReactComponent<{}> {
/**
* Make the documentation icon flip around to draw attention to it.
*/
- FlipDocumentationIcon = action(() => (this._flipDocumentation = this._flipDocumentation + 1));
+ FlipDocumentationIcon = action(() => {
+ this._flipDocumentation += 1;
+ });
render() {
return (
- //TODO:glr Add support for light / dark mode
+ // TODO:glr Add support for light / dark mode
<div
style={{
pointerEvents: 'all',
diff --git a/src/client/views/webcam/DashWebRTCVideo.scss b/src/client/views/webcam/DashWebRTCVideo.scss
deleted file mode 100644
index 5744ebbcd..000000000
--- a/src/client/views/webcam/DashWebRTCVideo.scss
+++ /dev/null
@@ -1,82 +0,0 @@
-@import '../global/globalCssVariables.module.scss';
-
-.webcam-cont {
- background: whitesmoke;
- color: grey;
- border-radius: 15px;
- box-shadow: #9c9396 0.2vw 0.2vw 0.4vw;
- border: solid #bbbbbbbb 5px;
- pointer-events: all;
- display: flex;
- flex-direction: column;
- overflow: hidden;
-
- .webcam-header {
- height: 50px;
- text-align: center;
- text-transform: uppercase;
- letter-spacing: 2px;
- font-size: 16px;
- width: 100%;
- margin-top: 20px;
- }
-
- .videoContainer {
- position: relative;
- width: calc(100% - 20px);
- height: 100%;
- /* border: 10px solid red; */
- margin-left: 10px;
- }
-
- .buttonContainer {
- display: flex;
- width: calc(100% - 20px);
- height: 50px;
- justify-content: center;
- text-align: center;
- /* border: 1px solid black; */
- margin-left: 10px;
- margin-top: 0;
- margin-bottom: 15px;
- }
-
- #roomName {
- outline: none;
- border-radius: inherit;
- border: 1px solid #bbbbbbbb;
- margin: 10px;
- padding: 10px;
- }
-
- .side {
- width: 25%;
- height: 20%;
- position: absolute;
- /* top: 65%; */
- z-index: 2;
- right: 0px;
- bottom: 18px;
- }
-
- .main {
- position: absolute;
- width: 100%;
- height: 100%;
- /* top: 20%; */
- align-self: center;
- }
-
- .videoButtons {
- border-radius: 50%;
- height: 30px;
- width: 30px;
- display: flex;
- justify-content: center;
- align-items: center;
- justify-self: center;
- align-self: center;
- margin: 5px;
- border: 1px solid black;
- }
-}
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
deleted file mode 100644
index 4e984f3d6..000000000
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { IconLookup } from '@fortawesome/fontawesome-svg-core';
-import { faPhoneSlash, faSync } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Doc } from '../../../fields/Doc';
-import { InkTool } from '../../../fields/InkField';
-import { SnappingManager } from '../../util/SnappingManager';
-import '../../views/nodes/WebBox.scss';
-import { FieldView, FieldViewProps } from '../nodes/FieldView';
-import './DashWebRTCVideo.scss';
-import { hangup, initialize, refreshVideos } from './WebCamLogic';
-
-/**
- * This models the component that will be rendered, that can be used as a doc that will reflect the video cams.
- */
-@observer
-export class DashWebRTCVideo extends React.Component<FieldViewProps> {
- private roomText: HTMLInputElement | undefined;
- @observable remoteVideoAdded: boolean = false;
-
- @action
- changeUILook = () => (this.remoteVideoAdded = true);
-
- /**
- * Function that submits the title entered by user on enter press.
- */
- private onEnterKeyDown = (e: React.KeyboardEvent) => {
- if (e.keyCode === 13) {
- const submittedTitle = this.roomText!.value;
- this.roomText!.value = '';
- this.roomText!.blur();
- initialize(submittedTitle, this.changeUILook);
- }
- };
-
- public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(DashWebRTCVideo, fieldKey);
- }
-
- onClickRefresh = () => refreshVideos();
-
- onClickHangUp = () => hangup();
-
- render() {
- const content = (
- <div className="webcam-cont" style={{ width: '100%', height: '100%' }}>
- <div className="webcam-header">DashWebRTC</div>
- <input id="roomName" type="text" placeholder="Enter room name" ref={e => (this.roomText = e!)} onKeyDown={this.onEnterKeyDown} />
- <div className="videoContainer">
- <video id="localVideo" className={'RTCVideo' + (this.remoteVideoAdded ? ' side' : ' main')} autoPlay playsInline muted ref={e => {}}></video>
- <video id="remoteVideo" className="RTCVideo main" autoPlay playsInline ref={e => {}}></video>
- </div>
- <div className="buttonContainer">
- <div className="videoButtons" style={{ background: 'red' }} onClick={this.onClickHangUp}>
- <FontAwesomeIcon icon={faPhoneSlash as IconLookup} color="white" />
- </div>
- <div className="videoButtons" style={{ background: 'green' }} onClick={this.onClickRefresh}>
- <FontAwesomeIcon icon={faSync as IconLookup} color="white" />
- </div>
- </div>
- </div>
- );
-
- const frozen = !this.props.isSelected() || SnappingManager.IsResizing;
- const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !SnappingManager.IsResizing ? '-interactive' : '');
-
- return (
- <>
- <div className={classname}>{content}</div>
- {!frozen ? null : <div className="webBox-overlay" />}
- </>
- );
- }
-}
diff --git a/src/client/views/webcam/WebCamLogic.js b/src/client/views/webcam/WebCamLogic.js
deleted file mode 100644
index 5f6202bc8..000000000
--- a/src/client/views/webcam/WebCamLogic.js
+++ /dev/null
@@ -1,292 +0,0 @@
-'use strict';
-import io from "socket.io-client";
-
-var socket;
-var isChannelReady = false;
-var isInitiator = false;
-var isStarted = false;
-var localStream;
-var pc;
-var remoteStream;
-var turnReady;
-var room;
-
-export function initialize(roomName, handlerUI) {
-
- var pcConfig = {
- 'iceServers': [{
- 'urls': 'stun:stun.l.google.com:19302'
- }]
- };
-
- // Set up audio and video regardless of what devices are present.
- var sdpConstraints = {
- offerToReceiveAudio: true,
- offerToReceiveVideo: true
- };
-
- /////////////////////////////////////////////
-
- room = roomName;
-
- socket = io.connect(`${window.location.protocol}//${window.location.hostname}:4321`);
-
- if (room !== '') {
- socket.emit('create or join', room);
- console.log('Attempted to create or join room', room);
- }
-
- socket.on('created', function (room) {
- console.log('Created room ' + room);
- isInitiator = true;
- });
-
- socket.on('full', function (room) {
- console.log('Room ' + room + ' is full');
- });
-
- socket.on('join', function (room) {
- console.log('Another peer made a request to join room ' + room);
- console.log('This peer is the initiator of room ' + room + '!');
- isChannelReady = true;
- });
-
- socket.on('joined', function (room) {
- console.log('joined: ' + room);
- isChannelReady = true;
- });
-
- socket.on('log', function (array) {
- console.log.apply(console, array);
- });
-
- ////////////////////////////////////////////////
-
-
- // This client receives a message
- socket.on('message', function (message) {
- console.log('Client received message:', message);
- if (message === 'got user media') {
- maybeStart();
- } else if (message.type === 'offer') {
- if (!isInitiator && !isStarted) {
- maybeStart();
- }
- pc.setRemoteDescription(new RTCSessionDescription(message));
- doAnswer();
- } else if (message.type === 'answer' && isStarted) {
- pc.setRemoteDescription(new RTCSessionDescription(message));
- } else if (message.type === 'candidate' && isStarted) {
- var candidate = new RTCIceCandidate({
- sdpMLineIndex: message.label,
- candidate: message.candidate
- });
- pc.addIceCandidate(candidate);
- } else if (message === 'bye' && isStarted) {
- handleRemoteHangup();
- }
- });
-
- ////////////////////////////////////////////////////
-
- var localVideo = document.querySelector('#localVideo');
- var remoteVideo = document.querySelector('#remoteVideo');
-
- const gotStream = (stream) => {
- console.log('Adding local stream.');
- localStream = stream;
- localVideo.srcObject = stream;
- sendMessage('got user media');
- if (isInitiator) {
- maybeStart();
- }
- }
-
-
- navigator.mediaDevices.getUserMedia({
- audio: true,
- video: true
- })
- .then(gotStream)
- .catch(function (e) {
- alert('getUserMedia() error: ' + e.name);
- });
-
-
-
- var constraints = {
- video: true
- };
-
- console.log('Getting user media with constraints', constraints);
-
- const requestTurn = (turnURL) => {
- var turnExists = false;
- for (var i in pcConfig.iceServers) {
- if (pcConfig.iceServers[i].urls.substr(0, 5) === 'turn:') {
- turnExists = true;
- turnReady = true;
- break;
- }
- }
- if (!turnExists) {
- console.log('Getting TURN server from ', turnURL);
- // No TURN server. Get one from computeengineondemand.appspot.com:
- var xhr = new XMLHttpRequest();
- xhr.onreadystatechange = function () {
- if (xhr.readyState === 4 && xhr.status === 200) {
- var turnServer = JSON.parse(xhr.responseText);
- console.log('Got TURN server: ', turnServer);
- pcConfig.iceServers.push({
- 'urls': 'turn:' + turnServer.username + '@' + turnServer.turn,
- 'credential': turnServer.password
- });
- turnReady = true;
- }
- };
- xhr.open('GET', turnURL, true);
- xhr.send();
- }
- }
-
-
-
-
- if (location.hostname !== 'localhost') {
- requestTurn(
- `${window.location.origin}/corsProxy/${encodeURIComponent("https://computeengineondemand.appspot.com/turn?username=41784574&key=4080218913")}`
- );
- }
-
- const maybeStart = () => {
- console.log('>>>>>>> maybeStart() ', isStarted, localStream, isChannelReady);
- if (!isStarted && typeof localStream !== 'undefined' && isChannelReady) {
- console.log('>>>>>> creating peer connection');
- createPeerConnection();
- pc.addStream(localStream);
- isStarted = true;
- console.log('isInitiator', isInitiator);
- if (isInitiator) {
- doCall();
- }
- }
- };
-
- window.onbeforeunload = function () {
- sendMessage('bye');
- };
-
- /////////////////////////////////////////////////////////
-
- const createPeerConnection = () => {
- try {
- pc = new RTCPeerConnection(null);
- pc.onicecandidate = handleIceCandidate;
- pc.onaddstream = handleRemoteStreamAdded;
- pc.onremovestream = handleRemoteStreamRemoved;
- console.log('Created RTCPeerConnnection');
- } catch (e) {
- console.log('Failed to create PeerConnection, exception: ' + e.message);
- alert('Cannot create RTCPeerConnection object.');
- return;
- }
- }
-
- const handleIceCandidate = (event) => {
- console.log('icecandidate event: ', event);
- if (event.candidate) {
- sendMessage({
- type: 'candidate',
- label: event.candidate.sdpMLineIndex,
- id: event.candidate.sdpMid,
- candidate: event.candidate.candidate
- });
- } else {
- console.log('End of candidates.');
- }
- }
-
- const handleCreateOfferError = (event) => {
- console.log('createOffer() error: ', event);
- }
-
- const doCall = () => {
- console.log('Sending offer to peer');
- pc.createOffer(setLocalAndSendMessage, handleCreateOfferError);
- }
-
- const doAnswer = () => {
- console.log('Sending answer to peer.');
- pc.createAnswer().then(
- setLocalAndSendMessage,
- onCreateSessionDescriptionError
- );
- }
-
- const setLocalAndSendMessage = (sessionDescription) => {
- pc.setLocalDescription(sessionDescription);
- console.log('setLocalAndSendMessage sending message', sessionDescription);
- sendMessage(sessionDescription);
- }
-
- const onCreateSessionDescriptionError = (error) => {
- trace('Failed to create session description: ' + error.toString());
- }
-
-
-
- const handleRemoteStreamAdded = (event) => {
- console.log('Remote stream added.');
- remoteStream = event.stream;
- remoteVideo.srcObject = remoteStream;
- handlerUI();
-
- };
-
- const handleRemoteStreamRemoved = (event) => {
- console.log('Remote stream removed. Event: ', event);
- }
-}
-
-export function hangup() {
- console.log('Hanging up.');
- stop();
- sendMessage('bye');
- if (localStream) {
- localStream.getTracks().forEach(track => track.stop());
- }
-}
-
-function stop() {
- isStarted = false;
- if (pc) {
- pc.close();
- }
- pc = null;
-}
-
-function handleRemoteHangup() {
- console.log('Session terminated.');
- stop();
- isInitiator = false;
- if (localStream) {
- localStream.getTracks().forEach(track => track.stop());
- }
-}
-
-function sendMessage(message) {
- console.log('Client sending message: ', message);
- socket.emit('message', message, room);
-};
-
-export function refreshVideos() {
- var localVideo = document.querySelector('#localVideo');
- var remoteVideo = document.querySelector('#remoteVideo');
- if (localVideo) {
- localVideo.srcObject = localStream;
- }
- if (remoteVideo) {
- remoteVideo.srcObject = remoteStream;
- }
-
-} \ No newline at end of file