aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx3
-rw-r--r--src/client/views/nodes/DocumentView.scss2
-rw-r--r--src/client/views/nodes/DocumentView.tsx17
-rw-r--r--src/client/views/nodes/FieldView.tsx2
-rw-r--r--src/client/views/nodes/FilterBox.tsx2
-rw-r--r--src/client/views/nodes/LinkBox.tsx2
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx2
-rw-r--r--src/client/views/nodes/MapBox/MapBox.scss87
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx661
-rw-r--r--src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx92
-rw-r--r--src/client/views/nodes/PDFBox.tsx2
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx2
-rw-r--r--src/client/views/nodes/VideoBox.scss14
-rw-r--r--src/client/views/nodes/VideoBox.tsx56
-rw-r--r--src/client/views/nodes/button/FontIconBox.tsx28
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx43
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx39
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx12
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx4
19 files changed, 988 insertions, 82 deletions
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index dbab5e762..005133eb0 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -40,6 +40,7 @@ import { VideoBox } from "./VideoBox";
import { WebBox } from "./WebBox";
import React = require("react");
import XRegExp = require("xregexp");
+import { MapBox } from "./MapBox/MapBox";
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -225,7 +226,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, EquationBox, SliderBox, FieldView,
CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox, FilterBox, FunctionPlotBox,
- ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, LinkBox, ScriptingBox,
+ ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, LinkBox, ScriptingBox, MapBox,
ScreenshotBox,
HTMLtag, ComparisonBox
}}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 1ec7bf72a..9fcd45e72 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -175,7 +175,7 @@
position: absolute;
bottom: 0;
width: 100%;
- overflow-y: scroll;
+ overflow-y: auto;
transform-origin: bottom left;
opacity: 0.1;
transition: opacity 0.5s;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f02375dd5..707c15bff 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -118,7 +118,7 @@ export interface DocumentViewSharedProps {
layerProvider: undefined | ((doc: Doc, assign?: boolean) => boolean);
styleProvider: Opt<StyleProviderFunc>;
focus: DocFocusFunc;
- fitWidth?: () => boolean;
+ fitWidth?: (doc: Doc) => boolean;
docFilters: () => string[];
docRangeFilters: () => string[];
searchFilterDocs: () => Doc[];
@@ -138,6 +138,7 @@ export interface DocumentViewSharedProps {
hideLinkButton?: boolean;
hideCaptions?: boolean;
ignoreAutoHeight?: boolean;
+ forceAutoHeight?: boolean;
disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
pointerEvents?: string;
scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
@@ -224,7 +225,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
componentWillUnmount() { this.cleanupHandlers(true); }
componentDidMount() { this.setupHandlers(); }
- componentDidUpdate() { this.setupHandlers(); }
+ //componentDidUpdate() { this.setupHandlers(); }
setupHandlers() {
this.cleanupHandlers(false);
if (this._mainCont.current) {
@@ -415,6 +416,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView()));
DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart) },
() => setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))); // this needs to happen after the drop event is processed.
+ ffview?.setupDragLines(false);
}
}
@@ -825,13 +827,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view);
isContentActive = (outsideReaction?: boolean) => {
- return CurrentUserUtils.SelectedTool !== InkTool.None ||
+ return this.props.isContentActive() === false ? false : (
+ CurrentUserUtils.SelectedTool !== InkTool.None ||
SnappingManager.GetIsDragging() ||
- this.props.rootSelected() ||
+ this.rootSelected() ||
this.props.Document.forceActive ||
this.props.isSelected(outsideReaction) ||
this._componentView?.isAnyChildContentActive?.() ||
- this.props.isContentActive() ? true : false;
+ this.props.isContentActive()) ? true : undefined;
}
@computed get contents() {
TraceMobx();
@@ -1138,7 +1141,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
get ComponentView() { return this.docView?._componentView; }
get allLinks() { return this.docView?.allLinks || []; }
get LayoutFieldKey() { return this.docView?.LayoutFieldKey || "layout"; }
- get fitWidth() { return this.props.fitWidth?.() || this.layoutDoc.fitWidth; }
+ get fitWidth() { return this.props.fitWidth?.(this.rootDoc) || this.layoutDoc.fitWidth; }
@computed get docViewPath() { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; }
@computed get layoutDoc() { return Doc.Layout(this.Document, this.props.LayoutTemplate?.()); }
@@ -1256,7 +1259,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
position: this.props.Document.isInkMask ? "absolute" : undefined,
transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
width: isButton ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
- height: isButton ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
+ height: isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
`${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`),
}}>
<DocumentViewInternal {...this.props}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index ee81e106a..943b9f153 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -18,7 +18,7 @@ export interface FieldViewProps extends DocumentViewSharedProps {
scrollOverflow?: boolean; // bcz: would like to think this can be avoided -- need to look at further
select: (isCtrlPressed: boolean) => void;
- isContentActive: (outsideReaction?: boolean) => boolean;
+ isContentActive: (outsideReaction?: boolean) => boolean | undefined;
isDocumentActive?: () => boolean;
isSelected: (outsideReaction?: boolean) => boolean;
scaling?: () => number;
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index 2041c7399..fb8e89da9 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -225,7 +225,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
treeViewExpandedView: "layout", _treeViewOpen: true, _forceActive: true, ignoreClick: true
});
Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
- newFacet._textBoxPadding = 4;
+ newFacet._textBoxPaddingX = newFacet._textBoxPaddingY = 4;
const scriptText = `setDocFilter(this?.target, "${facetHeader}", text, "match")`;
newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: "string" });
} else if (facetHeader !== "tags" && nonNumbers / facetValues.strings.length < .1) {
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index b82d16677..879a63248 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -15,7 +15,7 @@ const LinkDocument = makeInterface(documentSchema);
@observer
export class LinkBox extends ViewBoxBaseComponent<FieldViewProps, LinkDocument>(LinkDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkBox, fieldKey); }
- isContentActiveFunc = () => this.isContentActive() ? true : false;
+ isContentActiveFunc = () => this.isContentActive();
render() {
if (this.dataDoc.treeViewOpen === undefined) setTimeout(() => this.dataDoc.treeViewOpen = true);
return <div className={`linkBox-container${this.isContentActive() ? "-interactive" : ""}`}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 424083dac..2e29c0656 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -166,7 +166,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
docViewPath={returnEmptyDoclist}
ScreenToLocalTransform={Transform.Identity}
isDocumentActive={returnFalse}
- isContentActive={returnFalse}
+ isContentActive={emptyFunction}
addDocument={returnFalse}
removeDocument={returnFalse}
addDocTab={returnFalse}
diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss
new file mode 100644
index 000000000..854da5ed2
--- /dev/null
+++ b/src/client/views/nodes/MapBox/MapBox.scss
@@ -0,0 +1,87 @@
+@import "../../global/globalCssVariables.scss";
+.mapBox {
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ display: flex;
+
+ .mapBox-infoWindow {
+ background-color: white;
+ opacity: 0.75;
+ padding: 12;
+ font-size: 17;
+ }
+
+ .mapBox-overlayButton-sidebar {
+ background: #121721;
+ height: 25px;
+ width: 25px;
+ right: 5px;
+ display: flex;
+ position: absolute;
+ align-items: center;
+ justify-content: center;
+ border-radius: 3px;
+ pointer-events: all;
+ z-index: 1; // so it appears on top of the document's title, if shown
+
+ box-shadow: $standard-box-shadow;
+ transition: 0.2s;
+
+ &:hover{
+ filter: brightness(0.85);
+ }
+ }
+
+ .mapBox-wrapper {
+ width: 100%;
+ .searchbox {
+ box-sizing: border-box;
+ border: 1px solid transparent;
+ width: 240px;
+ height: 32px;
+ padding: 0 12px;
+ border-radius: 3px;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
+ font-size: 14px;
+ outline: none;
+ text-overflow: ellipses;
+ position: absolute;
+ left: 50%;
+ margin-left: -120px;
+ }
+ }
+
+ .mapBox-sidebar-handle {
+ top: 0;
+ //top: calc(50% - 17.5px); // use this to center vertically -- make sure it looks okay for slide views
+ width: 10px;
+ height: 100%;
+ max-height: 35px;
+ background: lightgray;
+ border-radius: 20px;
+ cursor:grabbing;
+ }
+ .mapBox-addMarker {
+ left: 50%;
+ margin-left: 120px;
+ right: unset !important;
+ margin-top: -10;
+ height: max-content;
+ }
+ .searchbox {
+ display:none;
+ }
+ .mapBox-addMarker {
+ display:none;
+ }
+}
+
+.mapBox:hover {
+ .mapBox-addMarker {
+ display:block;
+ }
+ .searchbox {
+ display :block;
+ }
+}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
new file mode 100644
index 000000000..e80ad8acd
--- /dev/null
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -0,0 +1,661 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Autocomplete, GoogleMap, GoogleMapProps, Marker } from '@react-google-maps/api';
+import * as dotenv from 'dotenv';
+import { action, computed, IReactionDisposer, observable, ObservableMap } from 'mobx';
+import { observer } from "mobx-react";
+import * as React from "react";
+import { Doc, DocListCast, Opt, WidthSym } from '../../../../fields/Doc';
+import { documentSchema } from '../../../../fields/documentSchemas';
+import { Id } from '../../../../fields/FieldSymbols';
+import { InkTool } from '../../../../fields/InkField';
+import { makeInterface } from '../../../../fields/Schema';
+import { NumCast, StrCast } from '../../../../fields/Types';
+import { TraceMobx } from '../../../../fields/util';
+import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { Docs } from '../../../documents/Documents';
+import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
+import { DragManager } from '../../../util/DragManager';
+import { SnappingManager } from '../../../util/SnappingManager';
+import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
+import { Colors } from '../../global/globalEnums';
+import { MarqueeAnnotator } from '../../MarqueeAnnotator';
+import { AnchorMenu } from '../../pdf/AnchorMenu';
+import { Annotation } from '../../pdf/Annotation';
+import { SidebarAnnos } from '../../SidebarAnnos';
+import { StyleProp } from '../../StyleProvider';
+import { FieldView, FieldViewProps } from '../FieldView';
+import "./MapBox.scss";
+import { MapBoxInfoWindow } from './MapBoxInfoWindow';
+
+/**
+ * MapBox architecture:
+ * Main component: MapBox.tsx
+ * Supporting Components: SidebarAnnos, CollectionStackingView
+ *
+ * MapBox is a node that extends the ViewBoxAnnotatableComponent. Similar to PDFBox and WebBox, it supports interaction between sidebar content and document content.
+ * The main body of MapBox uses Google Maps API to allow location retrieval, adding map markers, pan and zoom, and open street view.
+ * Dash Document architecture is integrated with Maps API: When drag and dropping documents with ExifData (gps Latitude and Longitude information) available,
+ * sidebarAddDocument function checks if the document contains lat & lng information, if it does, then the document is added to both the sidebar and the infowindow (a pop up corresponding to a map marker--pin on map).
+ * The lat and lng field of the document is filled when importing (spec see ConvertDMSToDD method and processFileUpload method in Documents.ts).
+ * A map marker is considered a document that contains a collection with stacking view of documents, it has a lat, lng location, which is passed to Maps API's custom marker (red pin) to be rendered on the google maps
+ */
+
+// const _global = (window /* browser */ || global /* node */) as any;
+
+type MapDocument = makeInterface<[typeof documentSchema]>;
+const MapDocument = makeInterface(documentSchema);
+
+const mapContainerStyle = {
+ height: '100%',
+};
+
+const defaultCenter = {
+ lat: 38.685,
+ lng: -115.234,
+};
+
+const mapOptions = {
+ fullscreenControl: false,
+}
+
+dotenv.config({ path: __dirname + '/.env' })
+const apiKey = process.env.GOOGLE_MAPS;
+
+const script = document.createElement('script');
+script.defer = true;
+script.async = true;
+script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places,drawing`;
+document.head.appendChild(script);
+
+/**
+ * Consider integrating later: allows for drawing, circling, making shapes on map
+ */
+// const drawingManager = new window.google.maps.drawing.DrawingManager({
+// drawingControl: true,
+// drawingControlOptions: {
+// position: google.maps.ControlPosition.TOP_RIGHT,
+// drawingModes: [
+// google.maps.drawing.OverlayType.MARKER,
+// // currently we are not supporting the following drawing mode on map, a thought for future development
+// google.maps.drawing.OverlayType.CIRCLE,
+// google.maps.drawing.OverlayType.POLYLINE,
+// ],
+// },
+// });
+
+
+// options for searchbox in Google Maps Places Autocomplete API
+const options = {
+ fields: ["formatted_address", "geometry", "name"], // note: level of details is charged by item per retrieval, not recommended to return all fields
+ strictBounds: false,
+ types: ["establishment"], // type pf places, subject of change according to user need
+} as google.maps.places.AutocompleteOptions;
+
+@observer
+export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps & Partial<GoogleMapProps>, MapDocument>(MapDocument) {
+
+ private _dropDisposer?: DragManager.DragDropDisposer;
+ private _disposers: { [name: string]: IReactionDisposer } = {};
+ private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
+ @observable private _overlayAnnoInfo: Opt<Doc>;
+ showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno);
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(MapBox, fieldKey); }
+ public get SidebarKey() { return this.fieldKey + "-sidebar"; }
+ private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void);
+ @computed get inlineTextAnnotations() { return this.allMapMarkers.filter(a => a.textInlineAnnotations); }
+
+ @observable private _map: google.maps.Map = null as unknown as google.maps.Map;
+ @observable private selectedPlace: Doc | undefined;
+ @observable private markerMap: { [id: string]: google.maps.Marker } = {};
+ @observable private center = navigator.geolocation ? navigator.geolocation.getCurrentPosition : defaultCenter;
+ @observable private _marqueeing: number[] | undefined;
+ @observable private _isAnnotating = false;
+ @observable private inputRef = React.createRef<HTMLInputElement>();
+ @observable private searchMarkers: google.maps.Marker[] = [];
+ @observable private searchBox = new window.google.maps.places.Autocomplete(this.inputRef.current!, options);
+ @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+ @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); };
+ @computed get allMapMarkers() { return DocListCast(this.dataDoc[this.annotationKey]); };
+ @observable private toggleAddMarker = false;
+ private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+
+
+ @observable _showSidebar = false;
+ @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
+
+ static _canAnnotate = true;
+ static _hadSelection: boolean = false;
+ private _sidebarRef = React.createRef<SidebarAnnos>();
+ private _ref: React.RefObject<HTMLDivElement> = React.createRef();
+
+ constructor(props: any) {
+ super(props);
+ }
+
+ @action
+ private setSearchBox = (searchBox: any) => {
+ this.searchBox = searchBox;
+ }
+
+ // iterate allMarkers to size, center, and zoom map to contain all markers
+ private fitBounds = (map: google.maps.Map) => {
+ const curBounds = map.getBounds() ?? new window.google.maps.LatLngBounds();
+ const isFitting = this.allMapMarkers.reduce((fits, place) =>
+ fits && curBounds?.contains({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), true as boolean);
+ !isFitting && map.fitBounds(this.allMapMarkers.reduce((bounds, place) =>
+ bounds.extend({ lat: NumCast(place.lat), lng: NumCast(place.lng) }),
+ new window.google.maps.LatLngBounds()));
+ }
+
+ /**
+ * Custom control for add marker button
+ * @param controlDiv
+ * @param map
+ */
+ private CenterControl = () => {
+ const controlDiv = document.createElement("div");
+ controlDiv.className = "mapBox-addMarker"
+ // Set CSS for the control border.
+ const controlUI = document.createElement("div");
+ controlUI.style.backgroundColor = "#fff";
+ controlUI.style.borderRadius = "3px";
+ controlUI.style.cursor = "pointer";
+ controlUI.style.marginTop = "10px";
+ controlUI.style.borderRadius = "4px";
+ controlUI.style.marginBottom = "22px";
+ controlUI.style.textAlign = "center";
+ controlUI.style.position = "absolute";
+ controlUI.style.width = "32px";
+ controlUI.style.height = "32px";
+ controlUI.title = "Click to toggle marker mode. In marker mode, click on map to place a marker.";
+
+ const plIcon = document.createElement("img");
+ plIcon.src = "https://cdn4.iconfinder.com/data/icons/wirecons-free-vector-icons/32/add-256.png";
+ plIcon.style.color = "rgb(25,25,25)";
+ plIcon.style.fontFamily = "Roboto,Arial,sans-serif";
+ plIcon.style.fontSize = "16px";
+ plIcon.style.lineHeight = "32px";
+ plIcon.style.left = "18";
+ plIcon.style.top = "15";
+ plIcon.style.position = "absolute";
+ plIcon.width = 14;
+ plIcon.height = 14;
+ plIcon.innerHTML = "Add";
+ controlUI.appendChild(plIcon);
+
+ // Set CSS for the control interior.
+ const markerIcon = document.createElement("img");
+ markerIcon.src = "https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678111-map-marker-1024.png";
+ markerIcon.style.color = "rgb(25,25,25)";
+ markerIcon.style.fontFamily = "Roboto,Arial,sans-serif";
+ markerIcon.style.fontSize = "16px";
+ markerIcon.style.lineHeight = "32px";
+ markerIcon.style.left = "-2";
+ markerIcon.style.top = "1";
+ markerIcon.width = 30;
+ markerIcon.height = 30;
+ markerIcon.style.position = "absolute";
+ markerIcon.innerHTML = "Add";
+ controlUI.appendChild(markerIcon);
+
+ // Setup the click event listeners
+ controlUI.addEventListener("click", () => {
+ if (this.toggleAddMarker == true) {
+ this.toggleAddMarker = false;
+ console.log("add marker button status:" + this.toggleAddMarker);
+ controlUI.style.backgroundColor = "#fff";
+ markerIcon.style.color = "rgb(25,25,25)";
+ } else {
+ this.toggleAddMarker = true;
+ console.log("add marker button status:" + this.toggleAddMarker);
+ controlUI.style.backgroundColor = "#4476f7";
+ markerIcon.style.color = "rgb(255,255,255)";
+ };
+ });
+ controlDiv.appendChild(controlUI);
+ return controlDiv;
+ }
+
+ /**
+ * Place the marker on google maps & store the empty marker as a MapMarker Document in allMarkers list
+ * @param position - the LatLng position where the marker is placed
+ * @param map
+ */
+ @action
+ private placeMarker = (position: google.maps.LatLng, map: google.maps.Map) => {
+ const marker = new google.maps.Marker({
+ position: position,
+ map: map
+ });
+ map.panTo(position);
+ const mapMarker = Docs.Create.MapMarkerDocument(NumCast(position.lat()), NumCast(position.lng()), false, [], {});
+ this.addDocument(mapMarker, this.annotationKey);
+ }
+
+ _loadPending = true;
+ /**
+ * store a reference to google map instance
+ * setup the drawing manager on the top right corner of map
+ * fit map bounds to contain all markers
+ * @param map
+ */
+ @action
+ private loadHandler = (map: google.maps.Map) => {
+ this._map = map;
+ this._loadPending = true;
+ const centerControlDiv = this.CenterControl();
+ map.controls[google.maps.ControlPosition.TOP_RIGHT].push(centerControlDiv);
+ //drawingManager.setMap(map);
+ // if (navigator.geolocation) {
+ // navigator.geolocation.getCurrentPosition(
+ // (position: Position) => {
+ // const pos = {
+ // lat: position.coords.latitude,
+ // lng: position.coords.longitude,
+ // };
+ // this._map.setCenter(pos);
+ // }
+ // );
+ // } else {
+ // alert("Your geolocation is not supported by browser.")
+ // };
+ map.setZoom(NumCast(this.dataDoc.mapZoom, 2.5));
+ map.setCenter(new google.maps.LatLng(NumCast(this.dataDoc.mapLat), NumCast(this.dataDoc.mapLng)));
+ setTimeout(() => {
+
+ if (this._loadPending && this._map.getBounds()) {
+ this._loadPending = false;
+ this.layoutDoc.fitToBox && this.fitBounds(this._map);
+ }
+ }, 250);
+ // listener to addmarker event
+ this._map.addListener('click', (e: MouseEvent) => {
+ if (this.toggleAddMarker == true) {
+ this.placeMarker((e as any).latLng, map);
+ }
+ })
+ }
+
+ @action
+ centered = () => {
+ if (this._loadPending && this._map.getBounds()) {
+ this._loadPending = false;
+ this.layoutDoc.fitToBox && this.fitBounds(this._map);
+ }
+ this.dataDoc.mapLat = this._map.getCenter()?.lat();
+ this.dataDoc.mapLng = this._map.getCenter()?.lng();
+ }
+
+ @action
+ zoomChanged = () => {
+ if (this._loadPending && this._map.getBounds()) {
+ this._loadPending = false;
+ this.layoutDoc.fitToBox && this.fitBounds(this._map);
+ }
+ this.dataDoc.mapZoom = this._map.getZoom();
+ }
+
+ /**
+ * Load and render all map markers
+ * @param marker
+ * @param place
+ */
+ @action
+ private markerLoadHandler = (marker: google.maps.Marker, place: Doc) => {
+ place[Id] ? this.markerMap[place[Id]] = marker : null;
+ }
+
+ /**
+ * on clicking the map marker, set the selected place to the marker document & set infowindowopen to be true
+ * @param e
+ * @param place
+ */
+ @action
+ private markerClickHandler = (e: google.maps.MapMouseEvent, place: Doc) => {
+ // set which place was clicked
+ this.selectedPlace = place;
+ place.infoWindowOpen = true;
+ }
+
+ /**
+ * Called when dragging documents into map sidebar or directly into infowindow; to create a map marker, ref to MapMarkerDocument in Documents.ts
+ * @param doc
+ * @param sidebarKey
+ * @returns
+ */
+ sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
+ console.log("print all sidebar Docs");
+ console.log(this.allSidebarDocs);
+ if (!this.layoutDoc._showSidebar) this.toggleSidebar();
+ const docs = doc instanceof Doc ? [doc] : doc
+ docs.forEach(doc => {
+ if (doc.lat !== undefined && doc.lng !== undefined) {
+ const existingMarker = this.allMapMarkers.find(marker => marker.lat === doc.lat && marker.lng == doc.lng);
+ doc.onClickBehavior = "enterPortal";
+ if (existingMarker) {
+ Doc.AddDocToList(existingMarker, "data", doc);
+ } else {
+ const marker = Docs.Create.MapMarkerDocument(NumCast(doc.lat), NumCast(doc.lng), false, [doc], {});
+ this.addDocument(marker, this.annotationKey);
+ }
+ }
+ }) //add to annotation list
+ console.log("sidebaraddDocument");
+ console.log(doc);
+
+ return this.addDocument(doc, sidebarKey); // add to sidebar list
+ }
+
+ /**
+ * Removing documents from the sidebar
+ * @param doc
+ * @param sidebarKey
+ * @returns
+ */
+ sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
+ if (this.layoutDoc._showSidebar) this.toggleSidebar();
+ const docs = doc instanceof Doc ? [doc] : doc;
+ docs.forEach(doc => {
+ console.log(this.allMapMarkers);
+ console.log(this.allSidebarDocs);
+ })
+ return this.removeDocument(doc, sidebarKey);
+ }
+
+ /**
+ * Toggle sidebar onclick the tiny comment button on the top right corner
+ * @param e
+ */
+ sidebarBtnDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, (e, down, delta) => {
+ const localDelta = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformDirection(delta[0], delta[1]);
+ const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
+ const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
+ const ratio = (curNativeWidth + localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth;
+ if (ratio >= 1) {
+ this.layoutDoc.nativeWidth = nativeWidth * ratio;
+ this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0];
+ this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+ }
+ return false;
+ }, emptyFunction, this.toggleSidebar);
+ }
+
+ sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
+ @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
+ @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); }
+
+
+ /**
+ * function that reads the place inputed from searchbox, then zoom in on the location that's been autocompleted;
+ * add a customized temporary marker on the map
+ */
+ @action
+ private handlePlaceChanged = () => {
+ console.log(this.searchBox);
+ const place = this.searchBox.getPlace();
+
+ if (!place.geometry || !place.geometry.location) {
+ // user entered the name of a place that wasn't suggested & pressed the enter key, or place details request failed
+ window.alert("No details available for input: '" + place.name + "'");
+ return;
+ }
+
+ // zoom in on the location of the search result
+ if (place.geometry.viewport) {
+ console.log(this._map);
+ this._map.fitBounds(place.geometry.viewport);
+ } else {
+ this._map.setCenter(place.geometry.location);
+ this._map.setZoom(17);
+ }
+
+ // customize icon => customized icon for the nature of the location selected
+ const icon = {
+ url: place.icon as string,
+ size: new google.maps.Size(71, 71),
+ origin: new google.maps.Point(0, 0),
+ anchor: new google.maps.Point(17, 34),
+ scaledSize: new google.maps.Size(25, 25),
+ };
+
+ // put temporary cutomized marker on searched location
+ this.searchMarkers.forEach((marker) => {
+ marker.setMap(null);
+ });
+ this.searchMarkers = [];
+ this.searchMarkers.push(
+ new window.google.maps.Marker({
+ map: this._map,
+ icon,
+ title: place.name,
+ position: place.geometry.location,
+ })
+ )
+ }
+
+ /**
+ * Handles toggle of sidebar on click the little comment button
+ */
+ @computed get sidebarHandle() {
+ TraceMobx();
+ const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
+ const color = !annotated ? Colors.WHITE : Colors.BLACK;
+ const backgroundColor = !annotated ? this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK : this.props.styleProvider?.(this.rootDoc, this.props as any, StyleProp.WidgetColor + (annotated ? ":annotated" : ""));
+ return (!annotated) ? (null) :
+ <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
+ style={{
+ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 17px))`,
+ backgroundColor: backgroundColor,
+ color: color,
+ opacity: annotated ? 1 : undefined
+ }} >
+ <FontAwesomeIcon icon={"comment-alt"} />
+ </div>;
+ }
+
+ // TODO: Adding highlight box layer to Maps
+ @action
+ toggleSidebar = () => {
+ const prevWidth = this.sidebarWidth();
+ this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%";
+ this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
+ }
+
+ sidebarDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false);
+ }
+ sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
+ const bounds = this._ref.current!.getBoundingClientRect();
+ this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%";
+ this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%";
+ e.preventDefault();
+ return false;
+ }
+
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => this._setPreviewCursor = func;
+
+ @action
+ onMarqueeDown = (e: React.PointerEvent) => {
+ if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+ setupMoveUpEvents(this, e, action(e => {
+ MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+ this._marqueeing = [e.clientX, e.clientY];
+ return true;
+ }), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false);
+ }
+ }
+ @action finishMarquee = (x?: number, y?: number) => {
+ this._marqueeing = undefined;
+ this._isAnnotating = false;
+ x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false);
+ }
+
+ addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => {
+ return this.addDocument(doc, annotationKey);
+ }
+
+ @computed get annotationLayer() {
+ const pe = this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" :
+ SnappingManager.GetIsDragging() ? undefined : "none"
+ return <div className="mapBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
+ {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
+ <Annotation key={`${anno[Id]}-annotation`} {...this.props}
+ fieldKey={this.annotationKey} pointerEvents={pe} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} />)}
+ </div>;
+ }
+
+ getAnchor = () => {
+ const anchor =
+ AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ??
+ this.rootDoc
+ return anchor;
+ }
+
+ /**
+ * render contents in allMapMarkers (e.g. images with exifData) into google maps as map marker
+ * @returns
+ */
+ private renderMarkers = () => {
+ return this.allMapMarkers.map(place => (
+ <Marker
+ key={place[Id]}
+ position={{ lat: NumCast(place.lat), lng: NumCast(place.lng) }}
+ onLoad={marker => this.markerLoadHandler(marker, place)}
+ onClick={(e: google.maps.MapMouseEvent) => this.markerClickHandler(e, place)}
+ />
+ ))
+ }
+
+ // TODO: auto center on select a document in the sidebar
+ private handleMapCenter = (map: google.maps.Map) => {
+ // console.log("print the selected views in selectionManager:")
+ // if (SelectionManager.Views().lastElement()) {
+ // console.log(SelectionManager.Views().lastElement());
+ // }
+ }
+
+ panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
+ panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
+ scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop));
+ transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
+ opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
+ infoWidth = () => this.props.PanelWidth() / 5;
+ infoHeight = () => this.props.PanelHeight() / 5;
+ anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
+
+ render() {
+ const renderAnnotations = (docFilters?: () => string[]) => (null);
+ // bcz: commmented this out. Otherwise, any documents that are rendered with an InfoWindow of a marker
+ // will also be rendered as freeform annotations on the map. However, it doesn't seem that rendering
+ // freeform documents on the map does anything anyway, so getting rid of it for now. Also, since documents
+ // are rendered twice, adding a new note to the InfoWindow loses focus immediately on creation since it gets
+ // shifted to the non-visible view of the document in this freeform view.
+ // <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
+ // renderDepth={this.props.renderDepth + 1}
+ // isAnnotationOverlay={true}
+ // fieldKey={this.annotationKey}
+ // CollectionView={undefined}
+ // setPreviewCursor={this.setPreviewCursor}
+ // PanelWidth={this.panelWidth}
+ // PanelHeight={this.panelHeight}
+ // ScreenToLocalTransform={this.scrollXf}
+ // scaling={returnOne}
+ // dropAction={"alias"}
+ // docFilters={docFilters || this.props.docFilters}
+ // dontRenderDocuments={docFilters ? false : true}
+ // select={emptyFunction}
+ // ContentScaling={returnOne}
+ // bringToFront={emptyFunction}
+ // whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ // removeDocument={this.removeDocument}
+ // moveDocument={this.moveDocument}
+ // addDocument={this.sidebarAddDocument}
+ // childPointerEvents={true}
+ // pointerEvents={CurrentUserUtils.SelectedTool !== InkTool.None || this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />;
+ return <div className="mapBox" ref={this._ref}>
+ {/*console.log(apiKey)*/}
+ {/* <LoadScript
+ googleMapsApiKey={apiKey!}
+ libraries={['places', 'drawing']}
+ > */}
+ <div className="mapBox-wrapper"
+ onWheel={e => e.stopPropagation()}
+ onPointerDown={e => (e.button === 0 && !e.ctrlKey) && e.stopPropagation()}
+ style={{ width: `calc(100% - ${this.sidebarWidthPercent})` }}>
+
+ <div style={{ mixBlendMode: "multiply" }}>
+ {renderAnnotations(this.transparentFilter)}
+ </div>
+ {renderAnnotations(this.opaqueFilter)}
+ {SnappingManager.GetIsDragging() ? (null) : renderAnnotations()}
+ {this.annotationLayer}
+ <GoogleMap
+ mapContainerStyle={mapContainerStyle}
+ onZoomChanged={this.zoomChanged}
+ onCenterChanged={this.centered}
+ onLoad={this.loadHandler}
+ options={mapOptions}
+ >
+ <Autocomplete
+ onLoad={this.setSearchBox}
+ onPlaceChanged={this.handlePlaceChanged}>
+ <input ref={this.inputRef} className="searchbox" type="text" placeholder="Search anywhere:" />
+ </Autocomplete>
+
+ {this.renderMarkers()}
+ {this.allMapMarkers.filter(marker => marker.infoWindowOpen).map(marker => <MapBoxInfoWindow key={marker[Id]}
+ {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
+ place={marker}
+ markerMap={this.markerMap}
+ PanelWidth={this.infoWidth}
+ PanelHeight={this.infoHeight}
+ moveDocument={this.moveDocument}
+ isAnyChildContentActive={this.isAnyChildContentActive}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ />)}
+ {this.handleMapCenter(this._map)}
+ </GoogleMap>
+ {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
+ <MarqueeAnnotator rootDoc={this.rootDoc}
+ anchorMenuClick={this.anchorMenuClick}
+ scrollTop={0}
+ down={this._marqueeing} scaling={returnOne}
+ addDocument={this.addDocumentWrapper}
+ docView={this.props.docViewPath().lastElement()}
+ finishMarquee={this.finishMarquee}
+ savedAnnotations={this._savedAnnotations}
+ annotationLayer={this._annotationLayer.current}
+ mainCont={this._mainCont.current} />}
+ </div>
+ {/* </LoadScript > */}
+ <div className="mapBox-sidebar"
+ style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+ <SidebarAnnos ref={this._sidebarRef}
+ {...this.props}
+ fieldKey={this.fieldKey}
+ rootDoc={this.rootDoc}
+ layoutDoc={this.layoutDoc}
+ dataDoc={this.dataDoc}
+ showSidebar={this.SidebarShown}
+ nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ PanelWidth={this.sidebarWidth}
+ sidebarAddDocument={this.sidebarAddDocument}
+ moveDocument={this.moveDocument}
+ removeDocument={this.sidebarRemoveDocument}
+ />
+ </div>
+ <div className="mapBox-overlayButton-sidebar" key="sidebar" title="Toggle Sidebar"
+ style={{
+ display: !this.props.isContentActive() ? "none" : undefined,
+ top: StrCast(this.rootDoc._showTitle) === "title" ? 20 : 5,
+ backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK
+ }}
+ onPointerDown={this.sidebarBtnDown} >
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={"comment-alt"} size="sm" />
+ </div>
+ </div>;
+ }
+}
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
new file mode 100644
index 000000000..0d5fedb7b
--- /dev/null
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -0,0 +1,92 @@
+import { InfoWindow } from '@react-google-maps/api';
+import { action, computed } from 'mobx';
+import { observer } from "mobx-react";
+import * as React from "react";
+import { Doc } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
+import { emptyFunction, OmitKeys, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents } from '../../../../Utils';
+import { Docs } from '../../../documents/Documents';
+import { DocumentType } from '../../../documents/DocumentTypes';
+import { CollectionStackingView } from '../../collections/CollectionStackingView';
+import { CollectionViewType } from '../../collections/CollectionView';
+import { ViewBoxAnnotatableProps } from '../../DocComponent';
+import { FieldViewProps } from '../FieldView';
+import { FormattedTextBox } from '../formattedText/FormattedTextBox';
+import "./MapBox.scss";
+
+
+interface MapBoxInfoWindowProps {
+ place: Doc;
+ renderDepth: number;
+ markerMap: { [id: string]: google.maps.Marker };
+ isAnyChildContentActive: () => boolean;
+}
+@observer
+export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & ViewBoxAnnotatableProps & FieldViewProps>{
+
+ @action
+ private handleInfoWindowClose = () => {
+ if (this.props.place.infoWindowOpen) {
+ this.props.place.infoWindowOpen = false;
+ }
+ this.props.place.infoWindowOpen = false;
+ }
+
+ addNoteClick = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => {
+ const newBox = Docs.Create.TextDocument("Note", { _autoHeight: true });
+ FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+ Doc.AddDocToList(this.props.place, "data", newBox);
+ this._stack?.scrollToBottom();
+ e.stopPropagation();
+ e.preventDefault();
+ });
+ }
+
+
+ _stack: CollectionStackingView | null | undefined;
+
+ // Collection stacking view for documents in the infowindow of a map marker
+ @computed get renderChildDocs() {
+ return;
+ }
+ render() {
+ return <InfoWindow anchor={this.props.markerMap[this.props.place[Id]]} onCloseClick={this.handleInfoWindowClose} >
+ <div className="mapbox-infowindow">
+ <div style={{ width: this.props.PanelWidth(), height: this.props.PanelHeight() }}>
+ <CollectionStackingView
+ ref={r => this._stack = r}
+ {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
+ Document={this.props.place}
+ DataDoc={undefined}
+ fieldKey="data"
+ CollectionView={undefined}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ docFilters={returnEmptyFilter}
+ setHeight={emptyFunction}
+ isAnnotationOverlay={false}
+ select={emptyFunction}
+ scaling={returnOne}
+ isContentActive={returnTrue}
+ chromeHidden={true}
+ rootSelected={returnFalse}
+ childHideResizeHandles={returnTrue}
+ childHideDecorationTitle={returnTrue}
+ childFitWidth={doc => doc.type === DocumentType.RTF}
+ // childDocumentsActive={returnFalse}
+ removeDocument={(doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.RemoveDocFromList(this.props.place, "data", d), true as boolean)}
+ addDocument={(doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.AddDocToList(this.props.place, "data", d), true as boolean)}
+ renderDepth={this.props.renderDepth + 1}
+ viewType={CollectionViewType.Stacking}
+ pointerEvents="all"
+ />
+ </div>
+ <hr />
+ <div onPointerDown={this.addNoteClick} onClick={e => { e.stopPropagation(); e.preventDefault(); }} >
+ Add Note
+ </div>
+ </div>
+ </InfoWindow>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 30b4dc92a..d54b65d92 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -136,6 +136,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => this._searchString = e.currentTarget.value;
+ // adding external documents; to sidebar key
+ // if (doc.Geolocation) this.addDocument(doc, this.fieldkey+"-annotation")
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
if (!this.layoutDoc._showSidebar) this.toggleSidebar();
return this.addDocument(doc, sidebarKey);
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 7ad96bf05..0c631e5f9 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -313,7 +313,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
PanelHeight={this.formattedPanelHeight}
isAnnotationOverlay={true}
select={emptyFunction}
- isContentActive={returnFalse}
+ isContentActive={emptyFunction}
scaling={returnOne}
xPadding={25}
yPadding={10}
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index a75248fa0..8a3261c7d 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -52,12 +52,18 @@
width: 100%;
z-index: -1; // 0; // logically this should be 0 (or unset) which would give us transparent brush strokes over videos. However, this makes Chrome crawl to a halt
position: absolute;
+ video {
+ width: auto;
+ height: 100%;
+ display: flex;
+ margin: auto;
+ }
}
-.videoBox-content,
-.videoBox-content-interactive,
-.videoBox-content-fullScreen {
- height: Auto;
+.videoBox-content, .videoBox-content-interactive, .videoBox-content-fullScreen {
+ width: 100%;
+ height: 100%;
+ left: 0px;
}
.videoBox-content-YouTube,
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index d08006722..6dfb41671 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -301,8 +301,47 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
}
- @action
- youtubeIframeLoaded = (e: any) => {
+ // returns the path of the audio file
+ @computed get audiopath() {
+ const field = Cast(this.props.Document[this.props.fieldKey + '-audio'], AudioField, null);
+ const vfield = Cast(this.dataDoc[this.fieldKey], VideoField, null);
+ return field?.url.href ?? vfield?.url.href ?? "";
+ }
+ // ref for updating time
+ _audioPlayer: HTMLAudioElement | null = null;
+ setAudioRef = (e: HTMLAudioElement | null) => this._audioPlayer = e;
+ @computed get content() {
+ const field = Cast(this.dataDoc[this.fieldKey], VideoField);
+ const interactive = CurrentUserUtils.SelectedTool !== InkTool.None || !this.props.isSelected() ? "" : "-interactive";
+ const classname = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
+ return !field ? <div key="loading">Loading</div> :
+ <div className="videoBox-contentContainer" key="container" style={{ mixBlendMode: "multiply" }}>
+ <div className={classname}>
+ <video key="video" autoPlay={this._screenCapture} ref={this.setVideoRef}
+ onCanPlay={this.videoLoad}
+ controls={VideoBox._nativeControls}
+ onPlay={() => this.Play()}
+ onSeeked={this.updateTimecode}
+ onPause={() => this.Pause()}
+ onClick={e => e.preventDefault()}>
+ <source src={field.url.href} type="video/mp4" />
+ Not supported.
+ </video>
+ {!this.audiopath || this.audiopath === field.url.href ? (null) :
+ <audio ref={this.setAudioRef} className={`audiobox-control${this.props.isContentActive() ? "-interactive" : ""}`}>
+ <source src={this.audiopath} type="audio/mpeg" />
+ Not supported.
+ </audio>}
+ </div>
+ </div>;
+ }
+
+ @computed get youtubeVideoId() {
+ const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
+ return field && field.url.href.indexOf("youtube") !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split("/")) : "";
+ }
+
+ @action youtubeIframeLoaded = (e: any) => {
if (!this._youtubeContentCreated) {
this._forceCreateYouTubeIFrame = !this._forceCreateYouTubeIFrame;
return;
@@ -388,7 +427,18 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return false;
},
emptyFunction,
- e => this.layoutDoc._currentTimecode = start);
+ (e: PointerEvent) => this.layoutDoc._currentTimecode = 0);
+ }
+
+ @computed get youtubeContent() {
+ this._youtubeIframeId = VideoBox._youtubeIframeCounter++;
+ this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true;
+ const classname = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : "");
+ const start = untracked(() => Math.round((this.layoutDoc._currentTimecode || 0)));
+ return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
+ onPointerLeave={this.updateTimecode}
+ onLoad={this.youtubeIframeLoaded} className={classname} width={Doc.NativeWidth(this.layoutDoc) || 640} height={Doc.NativeHeight(this.layoutDoc) || 390}
+ src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=0&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._nativeControls ? 1 : 0}`} />;
}
@action.bound
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 33fa23805..bd103dcf7 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -636,30 +636,10 @@ Scripting.addGlobal(function getActiveTextInfo(info: "family" | "size" | "color"
Scripting.addGlobal(function setAlignment(align: "left" | "right" | "center", checkResult?: boolean) {
const editorView = RichTextMenu.Instance?.TextView?.EditorView;
if (checkResult) {
- let active: string;
- if (editorView) {
- active = editorView?.state && RichTextMenu.Instance.getActiveAlignment();
- } else {
- active = StrCast(Doc.UserDoc().textAlign);
- }
- if (active === align) return Colors.MEDIUM_BLUE;
- return "transparent";
+ return (editorView ? RichTextMenu.Instance.textAlign : Doc.UserDoc().textAlign) === align ? Colors.MEDIUM_BLUE : "transparent";
}
- SelectionManager.Docs().map(doc => doc.textAlign = align);
- switch (align) {
- case "left":
- editorView?.state && RichTextMenu.Instance.alignLeft(editorView, editorView.dispatch);
- break;
- case "center":
- editorView?.state && RichTextMenu.Instance.alignCenter(editorView, editorView.dispatch);
- break;
- case "right":
- editorView?.state && RichTextMenu.Instance.alignRight(editorView, editorView.dispatch);
- break;
- default:
- break;
- }
- Doc.UserDoc().textAlign = align;
+ if (editorView?.state) RichTextMenu.Instance.align(editorView, editorView.dispatch, align);
+ else Doc.UserDoc().textAlign = align;
});
Scripting.addGlobal(function setBulletList(mapStyle: "bullet" | "decimal", checkResult?: boolean) {
@@ -766,7 +746,7 @@ Scripting.addGlobal(function setActiveInkTool(tool: string, checkResult?: boolea
Doc.UserDoc().activeInkTool = InkTool.Pen;
GestureOverlay.Instance.InkShape = tool;
}
- } else if (tool) { // pen
+ } else if (tool) { // pen or eraser
if (Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance.InkShape) {
Doc.UserDoc().activeInkTool = InkTool.None;
} else {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index aa53f751d..4a6af86c1 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -120,10 +120,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
public ProseRef?: HTMLDivElement;
public get EditorView() { return this._editorView; }
public get SidebarKey() { return this.fieldKey + "-sidebar"; }
+ @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); };
@computed get sidebarWidthPercent() { return this._showSidebar ? "20%" : StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
@computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); }
- @computed get autoHeight() { return this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight; }
+ @computed get autoHeight() { return (this.props.forceAutoHeight || this.layoutDoc._autoHeight) && !this.props.ignoreAutoHeight; }
@computed get textHeight() { return NumCast(this.rootDoc[this.fieldKey + "-height"]); }
@computed get scrollHeight() { return NumCast(this.rootDoc[this.fieldKey + "-scrollHeight"]); }
@computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + "-height"]); }
@@ -1140,10 +1141,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
});
- const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
- if (startupText) {
- const { state: { tr }, dispatch } = this._editorView;
- dispatch(tr.insertText(startupText));
+ const { state, dispatch } = this._editorView;
+ if (!rtfField) {
+ const startupText = Field.toString(this.dataDoc[fieldKey] as Field);
+ if (startupText) {
+ dispatch(state.tr.insertText(startupText));
+ }
+ const textAlign = StrCast(this.dataDoc["text-align"], StrCast(Doc.UserDoc().textAlign, "left"));
+ if (textAlign !== "left") {
+ selectAll(this._editorView.state, (tr) => {
+ this._editorView!.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: textAlign })));
+ });
+ }
}
(this._editorView as any).TextView = this;
}
@@ -1174,8 +1183,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
...(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: StrCast(Doc.UserDoc().fontFamily) })] : []),
- ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: StrCast(Doc.UserDoc().fontSize, "") })] : []),
+ ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []),
+ ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []),
...(Doc.UserDoc().fontWeight === "bold" ? [schema.mark(schema.marks.strong)] : [])];
}
}
@@ -1496,6 +1505,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
if (!this.layoutDoc._showSidebar) this.toggleSidebar();
+ // console.log("printting allSideBarDocs");
+ // console.log(this.allSidebarDocs);
return this.addDocument(doc, sidebarKey);
}
sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
@@ -1514,6 +1525,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
const color = !annotated ? Colors.WHITE : Colors.BLACK;
const backgroundColor = !annotated ? this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : ""));
+
return (!annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging())) ? (null) :
<div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
style={{
@@ -1535,6 +1547,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
rootDoc={this.rootDoc}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
+ // usePanelWidth={true}
nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
showSidebar={this.SidebarShown}
PanelWidth={this.sidebarWidth}
@@ -1565,7 +1578,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
setHeight={this.setSidebarHeight}
fitContentsToDoc={this.fitToBox}
noSidebar={true}
- fieldKey={this.layoutDoc.sidebarViewType === "translation" ? `${this.fieldKey}-translation` : this.SidebarKey} />;
+ fieldKey={this.layoutDoc.sidebarViewType === "translation" ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`} />;
};
return <div className={"formattedTextBox-sidebar" + (CurrentUserUtils.SelectedTool !== InkTool.None ? "-inking" : "")}
style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
@@ -1581,10 +1594,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || this.props.layerProvider?.(this.layoutDoc) !== false);
if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide);
const minimal = this.props.ignoreAutoHeight;
- const margins = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
- const selPad = Math.min(margins, 10);
- const padding = Math.max(margins + ((selected && !this.layoutDoc._singleLine) || minimal ? -selPad : 0), 0);
- const selPaddingClass = selected && !this.layoutDoc._singleLine && margins >= 10 ? "-selected" : "";
+ const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0);
+ const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
+ const selPad = ((selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0);
+ const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? "-selected" : "";
const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
return (styleFromString?.height === "0px" ? (null) :
<div className="formattedTextBox-cont"
@@ -1628,7 +1641,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
onScroll={this.onScroll} onDrop={this.ondrop} >
<div className={minimal ? "formattedTextBox-minimal" : `formattedTextBox-inner${rounded}${selPaddingClass}`} ref={this.createDropTarget}
style={{
- padding: StrCast(this.layoutDoc._textBoxPadding, `${padding}px`),
+ padding: StrCast(this.layoutDoc._textBoxPadding),
+ paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
+ paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
+ paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
+ paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? "none" : undefined) : undefined
}}
/>
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index bd05af977..4814d6e9a 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -45,7 +45,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable private _activeFontSize: string = "13px";
@observable private _activeFontFamily: string = "";
@observable private activeListType: string = "";
- @observable private activeAlignment: string = "left";
+ @observable private _activeAlignment: string = "left";
@observable private brushMarks: Set<Mark> = new Set();
@observable private showBrushDropdown: boolean = false;
@@ -86,6 +86,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@computed get fontColor() { return this._activeFontColor; }
@computed get fontFamily() { return this._activeFontFamily; }
@computed get fontSize() { return this._activeFontSize; }
+ @computed get textAlign() { return this._activeAlignment; }
public delayHide = () => this._delayHide = true;
@@ -115,7 +116,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const activeHighlights = active.activeHighlights;
this.activeListType = this.getActiveListStyle();
- this.activeAlignment = this.getActiveAlignment();
+ this._activeAlignment = this.getActiveAlignment();
this._activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
this._activeFontSize = !activeSizes.length ? "13px" : activeSizes[0];
this._activeFontColor = !activeColors.length ? "black" : activeColors.length > 0 ? String(activeColors[0]) : "...";
@@ -388,29 +389,21 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
dispatch?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
return true;
}
- alignCenter = (view: EditorView, dispatch: any) => {
- return this.TextView.props.isSelected(true) && this.alignParagraphs(view, "center", dispatch);
- }
- alignLeft = (view: EditorView, dispatch: any) => {
- return this.TextView.props.isSelected(true) && this.alignParagraphs(view, "left", dispatch);
- }
- alignRight = (view: EditorView, dispatch: any) => {
- return this.TextView.props.isSelected(true) && this.alignParagraphs(view, "right", dispatch);
- }
- alignParagraphs(view: EditorView, align: "left" | "right" | "center", dispatch: any) {
- var tr = view.state.tr;
- view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
- tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, align }, node.marks);
- return false;
- }
+ align = (view: EditorView, dispatch: any, alignment: "left" | "right" | "center") => {
+ if (this.TextView.props.isSelected(true)) {
+ var tr = view.state.tr;
+ view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => {
+ if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) {
+ tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, align: alignment }, node.marks);
+ return false;
+ }
+ view.focus();
+ return true;
+ });
view.focus();
- return true;
- });
- view.focus();
- dispatch?.(tr);
- return true;
+ dispatch?.(tr);
+ }
}
insetParagraph(state: EditorState<any>, dispatch: any) {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index ea3bcf719..2370b7e16 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -46,6 +46,11 @@ const PresBoxDocument = makeInterface(documentSchema);
export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>(PresBoxDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); }
+ /**
+ * transitions & effects for documents
+ * @param renderDoc
+ * @param layoutDoc
+ */
static renderEffectsDoc(renderDoc: any, layoutDoc: Doc) {
const effectProps = {
left: layoutDoc.presEffectDirection === PresEffect.Left,
@@ -210,6 +215,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
}
}
+ //TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time
// No more frames in current doc and next slide is defined, therefore move to next slide
nextSlide = (activeNext: Doc) => {
const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null);
@@ -403,7 +409,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
/**
* Uses the viewfinder to progressivize through the different views of a single collection.
- * @param presTargetDoc: document for which internal zoom is used
+ * @param activeItem: document for which internal zoom is used
*/
zoomProgressivizeNext = (activeItem: Doc) => {
const targetDoc: Doc = this.targetDoc;
@@ -519,6 +525,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
load();
}
+ // The function pauses the auto presentation
@action
pauseAutoPres = () => {
if (this.layoutDoc.presStatus === PresStatus.Autoplay) {
@@ -542,6 +549,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
});
}
+ // The function allows for viewing the pres path on toggle
@action togglePath = (srcContext: Doc, off?: boolean) => {
if (off) {
this._pathBoolean = false;
@@ -552,6 +560,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
}
}
+ // The function allows for expanding the view of pres on toggle
@action toggleExpandMode = () => {
runInAction(() => this._expandBoolean = !this._expandBoolean);
this.rootDoc.expandBoolean = this._expandBoolean;
@@ -1833,6 +1842,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
case DocumentType.VID: type = "Video"; break;
case DocumentType.IMG: type = "Image"; break;
case DocumentType.WEB: type = "Web page"; break;
+ case DocumentType.MAP: type = "Map"; break;
default: type = "Other node"; break;
}
}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 5e713c3cf..238d025dc 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -166,6 +166,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
e.preventDefault();
}
+ /**
+ * Function to drag and drop the pres element to a diferent location
+ */
startDrag = (e: PointerEvent) => {
const miniView: boolean = this.toolbarWidth <= 100;
const activeItem = this.rootDoc;
@@ -244,6 +247,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
e.stopPropagation();
});
+ // set the value/title of the individual pres element
@undoBatch
@action
onSetValue = (value: string) => {