aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package-lock.json97
-rw-r--r--package.json2
-rw-r--r--src/client/documents/DocumentTypes.ts1
-rw-r--r--src/client/documents/Documents.ts32
-rw-r--r--src/client/util/CurrentUserUtils.ts2
-rw-r--r--src/client/util/DocumentManager.ts2
-rw-r--r--src/client/views/DocumentDecorations.tsx8
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/SidebarAnnos.tsx7
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx2
-rw-r--r--src/client/views/nodes/DocumentView.tsx2
-rw-r--r--src/client/views/nodes/MapBox/MapAnchorMenu.scss54
-rw-r--r--src/client/views/nodes/MapBox/MapAnchorMenu.tsx146
-rw-r--r--src/client/views/nodes/MapBox/MapBox.scss161
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx914
-rw-r--r--src/client/views/nodes/MapBox/MapBox2.tsx8
-rw-r--r--src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx4
-rw-r--r--src/client/views/nodes/MapBox/MapPushpinBox.tsx35
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx41
-rw-r--r--src/fields/Doc.ts11
20 files changed, 1034 insertions, 497 deletions
diff --git a/package-lock.json b/package-lock.json
index af5bddbf9..00410e3be 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6537,9 +6537,9 @@
}
},
"browndash-components": {
- "version": "0.0.97",
- "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.97.tgz",
- "integrity": "sha512-UO8NQrOJoAq+UQGR8TXCRzr6jX9qSnEot9FHP7zdwlKH1sGntbQpyM5BXdwfkyXo+oh1qstTGkyR4s20CY7Yrw==",
+ "version": "0.0.98",
+ "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.98.tgz",
+ "integrity": "sha512-g69eW7TJGYo7w3foy68ebZ3A9+2vk2MExIEIV1pH/rWupm6nriZaP64tOOnmOd96oD8Efrb0KT5D3xQeVlfnjw==",
"requires": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
@@ -7094,12 +7094,97 @@
"strip-ansi": "^7.0.1"
}
},
+ "string-width-cjs": {
+ "version": "npm:string-width@4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ }
+ }
+ },
"strip-ansi": {
"version": "7.1.0",
"bundled": true,
"requires": {
"ansi-regex": "^6.0.1"
}
+ },
+ "strip-ansi-cjs": {
+ "version": "npm:strip-ansi@6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+ }
+ }
+ },
+ "wrap-ansi-cjs": {
+ "version": "npm:wrap-ansi@7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ }
+ }
}
}
},
@@ -8604,7 +8689,7 @@
}
},
"string-width-cjs": {
- "version": "npm:string-width@4.2.3",
+ "version": "npm:string-width-cjs@4.2.3",
"bundled": true,
"requires": {
"emoji-regex": "^8.0.0",
@@ -8627,7 +8712,7 @@
}
},
"strip-ansi-cjs": {
- "version": "npm:strip-ansi@6.0.1",
+ "version": "npm:strip-ansi-cjs@6.0.1",
"bundled": true,
"requires": {
"ansi-regex": "^5.0.1"
@@ -8786,7 +8871,7 @@
}
},
"wrap-ansi-cjs": {
- "version": "npm:wrap-ansi@7.0.0",
+ "version": "npm:wrap-ansi-cjs@7.0.0",
"bundled": true,
"requires": {
"ansi-styles": "^4.0.0",
diff --git a/package.json b/package.json
index a1e7f858a..576c95192 100644
--- a/package.json
+++ b/package.json
@@ -178,7 +178,7 @@
"body-parser": "^1.19.2",
"bootstrap": "^4.6.1",
"brotli": "^1.3.3",
- "browndash-components": "^0.0.97",
+ "browndash-components": "^0.0.98",
"browser-assert": "^1.2.1",
"bson": "^4.6.1",
"canvas": "^2.9.3",
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 1d0ddce40..f8d129e79 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -37,6 +37,7 @@ export enum DocumentType {
YOUTUBE = 'youtube',
COMPARISON = 'comparison',
GROUP = 'group',
+ PUSHPIN = "pushpin",
SCRIPTDB = 'scriptdb', // database of scripts
GROUPDB = 'groupdb', // database of groups
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 4933f0a7c..add12896e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -49,6 +49,7 @@ 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';
@@ -155,8 +156,11 @@ export class DocumentOptions {
z?: NUMt = new NumInfo('whether document is in overlay (1) or not (0)', false, [1, 0]);
_dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height");
_dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units");
- lat?: NUMt = new NumInfo('latitude coordinate for map views');
- lng?: NUMt = new NumInfo('longitude coordinate for map views');
+ latitude?: NUMt = new NumInfo('latitude coordinate for map views');
+ longitude?: NUMt = new NumInfo('longitude coordinate for map views');
+ map?: STRt = new StrInfo('text location of map');
+ map_type?: STRt = new StrInfo('type of map view');
+ map_zoom?: NUMt = new NumInfo('zoom of a map view');
_timecodeToShow?: NUMt = new NumInfo('the time that a document should be displayed (e.g., when an annotation shows up as a video plays)');
_timecodeToHide?: NUMt = new NumInfo('the time that a document should be hidden');
_width?: NUMt = new NumInfo('displayed width of a document');
@@ -251,6 +255,8 @@ export class DocumentOptions {
recording?: BOOLt = new BoolInfo('whether WebCam is recording or not');
autoPlayAnchors?: BOOLt = new BoolInfo('whether to play audio/video when an anchor is clicked in a stackedTimeline.');
dontPlayLinkOnSelect?: BOOLt = new BoolInfo('whether an audio/video should start playing when a link is followed to it.');
+ openFactoryLocation?: string; // an OpenWhere value to place the factory created document
+ openFactoryAsDelegate?: boolean; //
updateContentsScript?: ScriptField; // reactive script invoked when viewing a document that can update contents of a collection (or do anything)
toolTip?: string; // tooltip to display on hover
toolType?: string; // type of pen tool
@@ -289,6 +295,11 @@ export class DocumentOptions {
_isTimelineLabel?: BOOLt = new BoolInfo('is document a timeline label');
_isLightbox?: BOOLt = new BoolInfo('whether a collection acts as a lightbox by opening lightbox links by hiding all other documents in collection besides link target');
+ config_latitude?: NUMt = new NumInfo('latitude of a map'); // latitude of a map
+ config_longitude?: NUMt = new NumInfo('longitude of map'); // longitude of map
+ config_map_zoom?: NUMt = new NumInfo('zoom of map'); // zoom of map
+ config_map_type?: string;
+ config_map?: string;
config_panX?: NUMt = new NumInfo('panX saved as a view spec');
config_panY?: NUMt = new NumInfo('panY saved as a view spec');
config_viewScale?: NUMt = new NumInfo('viewScale saved as a view Spec');
@@ -360,8 +371,6 @@ export class DocumentOptions {
waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire. 'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait
onPointerDown?: ScriptField;
onPointerUp?: ScriptField;
- openFactoryLocation?: string; // an OpenWhere value to place the factory created document
- openFactoryAsDelegate?: BOOLt = new BoolInfo('create a delegate of the factory');
_forceActive?: BOOLt = new BoolInfo('flag to handle pointer events when not selected (or otherwise active)');
_dragOnlyWithinContainer?: BOOLt = new BoolInfo('whether the document should remain in its collection when someone tries to drag and drop it elsewhere');
_raiseWhenDragged?: BOOLt = new BoolInfo('whether a document is brought to front when dragged.');
@@ -521,7 +530,7 @@ export namespace Docs {
DocumentType.MAP,
{
layout: { view: MapBox, dataField: defaultDataKey },
- options: { _height: 600, _width: 800, nativeDimModifiable: true, systemIcon: 'BsFillPinMapFill' },
+ options: { map: '', _height: 600, _width: 800, nativeDimModifiable: true, systemIcon: 'BsFillPinMapFill' },
},
],
[
@@ -713,6 +722,13 @@ export namespace Docs {
},
},
],
+ [
+ DocumentType.PUSHPIN,
+ {
+ layout: { view: MapPushpinBox, dataField: defaultDataKey },
+ options: {},
+ },
+ ],
]);
const suffix = 'Proto';
@@ -1059,6 +1075,10 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.MAP), new List(documents), options);
}
+ export function PushpinDocument(lat: number, lng: number, infoWindowOpen: boolean, documents: Array<Doc>, options: DocumentOptions, id?: string) {
+ return InstanceFromProto(Prototypes.get(DocumentType.PUSHPIN), new List(documents), { latitude: lat, longitude: lng, infoWindowOpen, ...options }, id);
+ }
+
// shouldn't ever need to create a KVP document-- instead set the LayoutTemplateString to be a KeyValueBox for the DocumentView (see addDocTab in TabDocView)
// export function KVPDocument(document: Doc, options: DocumentOptions = {}) {
// return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + '.kvp', ...options });
@@ -1078,7 +1098,7 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Freeform }, id);
}
export function MapMarkerDocument(lat: number, lng: number, infoWindowOpen: boolean, documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { lat, lng, infoWindowOpen, ...options, _type_collection: CollectionViewType.Freeform }, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { latitude: lat, longitude: lng, infoWindowOpen, ...options, _type_collection: CollectionViewType.Freeform }, id);
}
export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 62b99d752..672f7d99f 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -272,7 +272,7 @@ export class CurrentUserUtils {
{key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, data_useCors: true, }},
{key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }},
{key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }},
- {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _layout_fitWidth: true, _layout_showSidebar: true, }},
+ {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _layout_fitWidth: true, }},
{key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }},
{key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, isSystem: true, cloneFieldFilter: new List<string>(["isSystem"]) }},
{key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}},
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 7c3b5be05..5b627c2f3 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -310,7 +310,7 @@ export class DocumentManager {
if (viewSpec && docView) {
if (docView.ComponentView instanceof FormattedTextBox) docView.ComponentView?.focus(viewSpec, options);
PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500);
- Doc.linkFollowHighlight(docView.rootDoc, undefined, options.effect);
+ Doc.linkFollowHighlight(viewSpec ? [docView.rootDoc, viewSpec]: docView.rootDoc, undefined, options.effect);
if (options.playAudio) DocumentManager.playAudioAnno(docView.rootDoc);
if (options.toggleTarget && (!options.didMove || docView.rootDoc.hidden)) docView.rootDoc.hidden = !docView.rootDoc.hidden;
if (options.effect) docView.rootDoc[Animation] = options.effect;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index f3daf3ffa..90425f264 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -80,8 +80,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
// show decorations whenever pointer moves outside of selection bounds.
'pointermove',
action(e => {
- if (this.Bounds.x !== Number.MAX_VALUE && (this.Bounds.x > e.clientX || this.Bounds.r < e.clientX || this.Bounds.y > e.clientY || this.Bounds.b < e.clientY)) {
- this._showNothing = false;
+ if (this.Bounds.x || this.Bounds.y || this.Bounds.r || this.Bounds.b) {
+ if (this.Bounds.x !== Number.MAX_VALUE && (this.Bounds.x > e.clientX || this.Bounds.r < e.clientX || this.Bounds.y > e.clientY || this.Bounds.b < e.clientY)) {
+ this._showNothing = false;
+ }
}
})
);
@@ -885,7 +887,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
);
return (
- <div className={`documentDecorations${colorScheme}`} style={{ opacity: this._showNothing ? 0.1 : undefined }}>
+ <div className={`documentDecorations${colorScheme}`} style={{ display: this._showNothing ? 'none' : undefined }}>
<div
className="documentDecorations-background"
style={{
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 8565941fd..344a401f3 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -59,6 +59,7 @@ import GenerativeFill from './nodes/generativeFill/GenerativeFill';
import { ImageBox } from './nodes/ImageBox';
import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
import { LinkDocPreview } from './nodes/LinkDocPreview';
+import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu';
import { RadialMenu } from './nodes/RadialMenu';
import { TaskCompletionBox } from './nodes/TaskCompletedBox';
import { OverlayView } from './OverlayView';
@@ -1010,6 +1011,7 @@ export class MainView extends React.Component {
<ContextMenu />
<RadialMenu />
<AnchorMenu />
+ <MapAnchorMenu/>
<DashFieldViewMenu />
<MarqueeOptionsMenu />
<TimelineMenu />
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index db273cc88..520485a71 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -87,6 +87,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
const taggedContent = this.childFilters()
.filter(data => data.split(':')[0])
+ .filter(data => data.split(':')[0] !== 'latitude' && data.split(':')[0] !== 'longitude')
.map(data => {
const key = data.split(':')[0];
const val = Field.Copy(this.allMetadata.get(key));
@@ -225,10 +226,12 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
<div className="sidebarAnnos-tagList" style={{ height: this.filtersHeight() }} onWheel={e => e.stopPropagation()}>
{this.allUsers.map(renderUsers)}
{this.allHashtags.map(renderTag)}
- {Array.from(this.allMetadata.keys())
+ {/* {Array.from(this.allMetadata.keys())
.sort()
- .map(key => renderMeta(key, this.allMetadata.get(key)))}
+ .map(key => renderMeta(key, this.allMetadata.get(key)))} */}
</div>
+
+
<div style={{ width: '100%', height: `calc(100% - 38px)`, position: 'relative' }}>
<CollectionStackingView
{...this.props}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 2d8663c9c..8ac9d6804 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -47,6 +47,7 @@ import { VideoBox } from './VideoBox';
import { WebBox } from './WebBox';
import React = require('react');
import XRegExp = require('xregexp');
+import { MapPushpinBox } from './MapBox/MapPushpinBox';
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -271,6 +272,7 @@ export class DocumentContentsView extends React.Component<
PhysicsSimulationBox,
SchemaRowBox,
ImportElementBox,
+ MapPushpinBox,
}}
bindings={bindings}
jsx={layoutFrame}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b72864567..be1eb3901 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -704,7 +704,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (e && !(e.nativeEvent as any).dash) {
const onDisplay = () => {
- DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
+ if (this.rootDoc.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
setTimeout(() => simulateMouseClick(document.elementFromPoint(e.clientX, e.clientY), e.clientX, e.clientY, e.screenX, e.screenY));
};
if (navigator.userAgent.includes('Macintosh')) {
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.scss b/src/client/views/nodes/MapBox/MapAnchorMenu.scss
new file mode 100644
index 000000000..6990bdcf1
--- /dev/null
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.scss
@@ -0,0 +1,54 @@
+.anchorMenu-addTag {
+ display: grid;
+ width: 200px;
+ padding: 5px;
+ grid-template-columns: 90px 20px 90px;
+}
+.anchorMenu-highlighter {
+ padding-right: 5px;
+ .antimodeMenu-button {
+ padding: 0;
+ padding: 0;
+ padding-right: 0px;
+ padding-left: 0px;
+ width: 5px;
+ }
+}
+.anchor-color-preview-button {
+ width: 25px !important;
+ .anchor-color-preview {
+ display: flex;
+ flex-direction: column;
+ padding-right: 3px;
+ width: unset !important;
+ .color-preview {
+ width: 60%;
+ top: 80%;
+ height: 4px;
+ position: relative;
+ top: unset;
+ width: 15px;
+ margin-top: 5px;
+ display: block;
+ }
+ }
+}
+
+.color-wrapper {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+
+ button.color-button {
+ width: 20px;
+ height: 20px;
+ border-radius: 15px !important;
+ margin: 3px;
+ border: 2px solid transparent !important;
+ padding: 3px;
+
+ &.active {
+ border: 2px solid white;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
new file mode 100644
index 000000000..f731763af
--- /dev/null
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -0,0 +1,146 @@
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import { ColorState } from 'react-color';
+import { Doc, Opt } from '../../../../fields/Doc';
+import { returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from '../../../../Utils';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu"
+import { LinkPopup } from '../../linking/LinkPopup';
+import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
+// import { GPTPopup, GPTPopupMode } from './../../GPTPopup/GPTPopup';
+import { EditorView } from 'prosemirror-view';
+import './MapAnchorMenu.scss';
+import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
+import { StrCast } from '../../../../fields/Types';
+import { DocumentType } from '../../../documents/DocumentTypes';
+
+@observer
+export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
+ static Instance: MapAnchorMenu;
+
+ private _disposer: IReactionDisposer | undefined;
+ private _disposer2: IReactionDisposer | undefined;
+ private _commentCont = React.createRef<HTMLButtonElement>();
+
+
+
+
+ public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
+
+ public Center: () => void = unimplementedFunction;
+ // public OnClick: (e: PointerEvent) => void = unimplementedFunction;
+ // public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
+ // public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
+ // public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
+ public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined;
+ public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
+ public Delete: () => void = unimplementedFunction;
+ public LinkNote: () => void = unimplementedFunction;
+ // public MakeTargetToggle: () => void = unimplementedFunction;
+ // public ShowTargetTrail: () => void = unimplementedFunction;
+ public IsTargetToggler: () => boolean = returnFalse;
+ public get Active() {
+ return this._left > 0;
+ }
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+
+ MapAnchorMenu.Instance = this;
+ MapAnchorMenu.Instance._canFade = false;
+ }
+
+ componentWillUnmount() {
+ this._disposer?.();
+ this._disposer2?.();
+ }
+
+ componentDidMount() {
+ this._disposer2 = reaction(
+ () => this._opacity,
+ opacity => {
+ if (!opacity) {
+ }
+ },
+ { fireImmediately: true }
+ );
+ this._disposer = reaction(
+ () => SelectionManager.Views().slice(),
+ selected => {
+ MapAnchorMenu.Instance.fadeOut(true);
+ }
+ );
+ }
+ // audioDown = (e: React.PointerEvent) => {
+ // setupMoveUpEvents(this, e, returnFalse, returnFalse, e => this.OnAudio?.(e));
+ // };
+
+ // cropDown = (e: React.PointerEvent) => {
+ // setupMoveUpEvents(
+ // this,
+ // e,
+ // (e: PointerEvent) => {
+ // this.StartCropDrag(e, this._commentCont.current!);
+ // return true;
+ // },
+ // returnFalse,
+ // e => this.OnCrop?.(e)
+ // );
+ // };
+
+
+ static top = React.createRef<HTMLDivElement>();
+ // public get Top(){
+ // return this.top
+ // }
+
+
+ render() {
+ const buttons =(
+ <>
+ {(
+ <IconButton
+ tooltip="Delete Pin" //
+ onPointerDown={this.Delete}
+ icon={<FontAwesomeIcon icon="trash-alt" />}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
+ )}
+ {(
+ <IconButton
+ tooltip="Link Note to Pin" //
+ onPointerDown={this.LinkNote}
+ icon={<FontAwesomeIcon icon="sticky-note" />}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
+ )}
+ {(
+ <IconButton
+ tooltip="Center on pin" //
+ onPointerDown={this.Center}
+ icon={<FontAwesomeIcon icon="compress-arrows-alt" />}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
+ )}
+ {/* {this.IsTargetToggler !== returnFalse && (
+ <Toggle
+ tooltip={'Make target visibility toggle on click'}
+ type={Type.PRIM}
+ toggleType={ToggleType.BUTTON}
+ toggleStatus={this.IsTargetToggler()}
+ onClick={this.MakeTargetToggle}
+ icon={<FontAwesomeIcon icon="thumbtack" />}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
+ )} */}
+ </>
+ );
+
+ return this.getElement(<div ref={MapAnchorMenu.top} style={{width:"100%", display:"flex"}}>
+ {buttons}
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss
index fb15520f6..242677231 100644
--- a/src/client/views/nodes/MapBox/MapBox.scss
+++ b/src/client/views/nodes/MapBox/MapBox.scss
@@ -1,87 +1,108 @@
-@import "../../global/globalCssVariables.scss";
+@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;
- }
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ display: flex;
- .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-infoWindow {
+ background-color: white;
+ opacity: 0.75;
+ padding: 12;
+ font-size: 17;
+ }
+ .mapBox-searchbar {
+ display: flex;
+ flex-direction: row;
+ width: calc(100% - 40px);
+ .editableText-container {
+ width: 100%;
+ font-size: 16px !important;
}
-
- .mapBox-wrapper {
+ input {
width: 100%;
- .mapBox-input {
- 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-topbar {
+ display: flex;
+ flex-direction: row;
+ }
- .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-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-addMarker {
+ }
+
+ .mapBox-wrapper {
+ width: 100%;
+ .mapBox-input {
+ 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;
- right: unset !important;
- margin-top: -10;
- height: max-content;
- }
- .searchbox {
- display:none;
- }
- .mapBox-addMarker {
- display:none;
+ margin-left: -120px;
}
+ }
+ .mapBox-sidebar {
+ position: absolute;
+ right: 0;
+ height: 100%;
+ }
+
+ .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;
+ display: block;
}
.searchbox {
- display :block;
+ display: block;
}
}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index cf017d746..284e598c5 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1,31 +1,31 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Autocomplete, GoogleMap, GoogleMapProps, Marker } from '@react-google-maps/api';
import BingMapsReact from 'bingmaps-react';
-import { action, computed, IReactionDisposer, observable, ObservableMap, runInAction } from 'mobx';
+import { Button, EditableText, IconButton, Type } from 'browndash-components';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
-import { Width } from '../../../../fields/DocSymbols';
-import { Id } from '../../../../fields/FieldSymbols';
+import { DocCss, Highlight, Width } from '../../../../fields/DocSymbols';
import { InkTool } from '../../../../fields/InkField';
-import { NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnOne, returnTrue, setupMoveUpEvents, Utils } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
+import { DocumentManager } from '../../../util/DocumentManager';
import { DragManager } from '../../../util/DragManager';
import { SnappingManager } from '../../../util/SnappingManager';
-import { UndoManager } from '../../../util/UndoManager';
+import { Transform } from '../../../util/Transform';
+import { undoable, UndoManager } from '../../../util/UndoManager';
import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
import { Colors } from '../../global/globalEnums';
import { MarqueeAnnotator } from '../../MarqueeAnnotator';
-import { AnchorMenu } from '../../pdf/AnchorMenu';
-import { Annotation } from '../../pdf/Annotation';
import { SidebarAnnos } from '../../SidebarAnnos';
+import { DocumentView } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
-import { PinProps } from '../trails';
+import { PinProps, PresBox } from '../trails';
+import { MapAnchorMenu } from './MapAnchorMenu';
import './MapBox.scss';
-import { MapBoxInfoWindow } from './MapBoxInfoWindow';
-
+// amongus
/**
* MapBox architecture:
* Main component: MapBox.tsx
@@ -39,29 +39,7 @@ import { MapBoxInfoWindow } from './MapBoxInfoWindow';
* A map marker is considered a document that contains a collection with stacking view of documents, it has a lat, lng location, which is passed to Maps API's custom marker (red pin) to be rendered on the google maps
*/
-// const _global = (window /* browser */ || global /* node */) as any;
-
-const mapContainerStyle = {
- height: '100%',
-};
-
-const defaultCenter = {
- lat: 42.360081,
- lng: -71.058884,
-};
-
-const mapOptions = {
- fullscreenControl: false,
-};
-
const bingApiKey = process.env.BING_MAPS; // if you're running local, get a Bing Maps api key here: https://www.bingmapsportal.com/ and then add it to the .env file in the Dash-Web root directory as: _CLIENT_BING_MAPS=<your apikey>
-const 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
@@ -79,245 +57,51 @@ document.head.appendChild(script);
// },
// });
-// 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>>() {
- static UseBing = true;
- 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));
+export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(MapBox, fieldKey);
}
- public get SidebarKey() {
- return this.fieldKey + '_sidebar';
- }
+ private _dragRef = React.createRef<HTMLDivElement>();
+ private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+ private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
+ private _sidebarRef = React.createRef<SidebarAnnos>();
+ private _ref: React.RefObject<HTMLDivElement> = React.createRef();
+ private _disposers: { [key: string]: IReactionDisposer } = {};
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void);
- @computed get inlineTextAnnotations() {
- return this.allMapMarkers.filter(a => a.text_inlineAnnotations);
- }
- @observable private _map: google.maps.Map = null as unknown as google.maps.Map;
- @observable private selectedPlace: Doc | undefined;
- @observable private markerMap: { [id: string]: google.maps.Marker } = {};
- @observable private center = navigator.geolocation ? navigator.geolocation.getCurrentPosition : defaultCenter;
@observable private _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 inlineTextAnnotations() {
+ return this.allMapMarkers.filter(a => a.text_inlineAnnotations);
+ }
@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._layout_showSidebar ? true : false;
+ return this.layoutDoc._layout_showSidebar ? true : false;
+ }
+ @computed get sidebarWidthPercent() {
+ return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
+ }
+ @computed get sidebarColor() {
+ return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
+ }
+ @computed get SidebarKey() {
+ return this.fieldKey + '_sidebar';
}
-
- static _canAnnotate = true;
- static _hadSelection: boolean = false;
- private _sidebarRef = React.createRef<SidebarAnnos>();
- private _ref: React.RefObject<HTMLDivElement> = React.createRef();
componentDidMount() {
this.props.setContentView?.(this);
}
- @action
- private setSearchBox = (searchBox: any) => {
- this.searchBox = searchBox;
- };
-
- // iterate allMarkers to size, center, and zoom map to contain all markers
- private fitBounds = (map: google.maps.Map) => {
- const curBounds = map.getBounds() ?? new window.google.maps.LatLngBounds();
- const isFitting = this.allMapMarkers.reduce((fits, place) => fits && curBounds?.contains({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), true as boolean);
- !isFitting && map.fitBounds(this.allMapMarkers.reduce((bounds, place) => bounds.extend({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), new window.google.maps.LatLngBounds()));
- };
-
- /**
- * Custom control for add marker button
- * @param controlDiv
- * @param map
- */
- private CenterControl = () => {
- const controlDiv = document.createElement('div');
- controlDiv.className = '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.freeform_fitContentsToBox && this.fitBounds(this._map);
- }
- }, 250);
- // listener to addmarker event
- this._map.addListener('click', (e: MouseEvent) => {
- if (this.toggleAddMarker === true) {
- this.placeMarker((e as any).latLng, map);
- }
- });
- };
-
- @action
- centered = () => {
- if (this._loadPending && this._map.getBounds()) {
- this._loadPending = false;
- this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
- }
- this.dataDoc.mapLat = this._map.getCenter()?.lat();
- this.dataDoc.mapLng = this._map.getCenter()?.lng();
- };
-
- @action
- zoomChanged = () => {
- if (this._loadPending && this._map.getBounds()) {
- this._loadPending = false;
- this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
- }
- this.dataDoc.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;
- };
+ componentWillUnmount(): void {
+ this.deselectPin();
+ Object.keys(this._disposers).forEach(key => this._disposers[key]?.());
+ }
/**
* Called when dragging documents into map sidebar or directly into infowindow; to create a map marker, ref to MapMarkerDocument in Documents.ts
@@ -326,7 +110,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
* @returns
*/
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
- console.log('print all sidebar Docs');
if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
const docs = doc instanceof Doc ? [doc] : doc;
docs.forEach(doc => {
@@ -335,7 +118,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (existingMarker) {
Doc.AddDocToList(existingMarker, 'data', doc);
} else {
- const marker = Docs.Create.MapMarkerDocument(NumCast(doc.lat), NumCast(doc.lng), false, [doc], {});
+ const marker = Docs.Create.PushpinDocument(NumCast(doc.lat), NumCast(doc.lng), false, [doc], {});
this.addDocument(marker, this.annotationKey);
}
}
@@ -350,11 +133,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
* @param sidebarKey
* @returns
*/
- sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
- if (this.layoutDoc._layout_showSidebar) this.toggleSidebar();
- const docs = doc instanceof Doc ? [doc] : doc;
- return this.removeDocument(doc, sidebarKey);
- };
+ sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => this.removeDocument(doc, sidebarKey);
/**
* Toggle sidebar onclick the tiny comment button on the top right corner
@@ -373,11 +152,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const fullWidth = this.layoutDoc[Width]();
const mapWidth = fullWidth - this.sidebarWidth();
if (this.sidebarWidth() + localDelta[0] > 0) {
- this._showSidebar = true;
+ this.layoutDoc._layout_showSidebar = true;
this.layoutDoc._width = fullWidth + localDelta[0];
this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%';
} else {
- this._showSidebar = false;
+ this.layoutDoc._layout_showSidebar = false;
this.layoutDoc._width = mapWidth;
this.layoutDoc._layout_sidebarWidthPercent = '0%';
}
@@ -387,60 +166,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
() => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map')
);
};
-
- sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
- @computed get layout_sidebarWidthPercent() {
- return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
- }
- @computed get sidebarColor() {
- return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
- }
-
- /**
- * function that reads the place inputed from searchbox, then zoom in on the location that's been autocompleted;
- * add a customized temporary marker on the map
- */
- @action
- private handlePlaceChanged = () => {
- const place = this.searchBox.getPlace();
-
- if (!place.geometry || !place.geometry.location) {
- // user entered the name of a place that wasn't suggested & pressed the enter key, or place details request failed
- window.alert("No details available for input: '" + place.name + "'");
- return;
- }
-
- // zoom in on the location of the search result
- if (place.geometry.viewport) {
- this._map.fitBounds(place.geometry.viewport);
- } else {
- this._map.setCenter(place.geometry.location);
- this._map.setZoom(17);
- }
-
- // customize icon => customized icon for the nature of the location selected
- const icon = {
- url: place.icon as string,
- size: new google.maps.Size(71, 71),
- origin: new google.maps.Point(0, 0),
- anchor: new google.maps.Point(17, 34),
- scaledSize: new google.maps.Size(25, 25),
- };
-
- // put temporary cutomized marker on searched location
- this.searchMarkers.forEach(marker => {
- marker.setMap(null);
- });
- this.searchMarkers = [];
- this.searchMarkers.push(
- new window.google.maps.Marker({
- map: this._map,
- icon,
- title: place.name,
- position: place.geometry.location,
- })
- );
- };
+ sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
/**
* Handles toggle of sidebar on click the little comment button
@@ -465,12 +191,28 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
// TODO: Adding highlight box layer to Maps
@action
toggleSidebar = () => {
- //1.2 * w * ? = .2 * w .2/1.2
const prevWidth = this.sidebarWidth();
this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(100 * 0.2) / 1.2}%` : '0%') !== '0%';
this.layoutDoc._width = this.layoutDoc._layout_showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
};
+ createNoteAnnotation = () => {
+ const createFunc = undoable(
+ action(() => {
+ const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false));
+ if (note && this.selectedPin) {
+ note.latitude = this.selectedPin.latitude;
+ note.longitude = this.selectedPin.longitude;
+ note.map = this.selectedPin.map;
+ }
+ }),
+ 'create note annotation'
+ );
+ if (!this.layoutDoc.layout_showSidebar) {
+ this.toggleSidebar();
+ setTimeout(createFunc);
+ } else createFunc();
+ };
sidebarDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
};
@@ -503,48 +245,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
};
@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);
- };
+ addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey);
pointerEvents = () => (this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
- @computed get annotationLayer() {
- return (
- <div className="mapBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
- {this.inlineTextAnnotations
- .sort((a, b) => NumCast(a.y) - NumCast(b.y))
- .map(anno => (
- <Annotation key={`${anno[Id]}-annotation`} {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} />
- ))}
- </div>
- );
- }
-
- getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => AnchorMenu.Instance?.GetAnchor(this._savedAnnotations, addAsAnnotation) ?? this.rootDoc;
-
- /**
- * render contents in allMapMarkers (e.g. images with exifData) into google maps as map marker
- * @returns
- */
- private renderMarkers = () => {
- return this.allMapMarkers.map(place => (
- <Marker key={place[Id]} position={{ lat: NumCast(place.lat), lng: NumCast(place.lng) }} onLoad={marker => this.markerLoadHandler(marker, place)} onClick={(e: google.maps.MapMouseEvent) => this.markerClickHandler(e, place)} />
- ));
- };
-
- // TODO: auto center on select a document in the sidebar
- private handleMapCenter = (map: google.maps.Map) => {
- // console.log("print the selected views in selectionManager:")
- // if (SelectionManager.Views().lastElement()) {
- // console.log(SelectionManager.Views().lastElement());
- // }
- };
-
panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth();
panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
@@ -581,24 +288,406 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
} else {
this._bingSearchManager.geocode({
where: query,
- callback: action((r: any) => {
- res(r.results[0].location);
- }),
+ callback: action((r: any) => res(r.results[0].location)),
errorCallback: (e: any) => reject(),
});
}
});
};
+ @observable
+ bingSearchBarContents: any = this.rootDoc.map; // For Bing Maps: The contents of the Bing search bar (string)
+
+ geoDataRequestOptions = {
+ entityType: 'PopulatedPlace',
+ };
+
+ // incrementer: number = 0;
+ /*
+ * Creates Pushpin doc and adds it to the list of annotations
+ */
+ @action
+ createPushpin = undoable((latitude: number, longitude: number, map?: string) => {
+ // Stores the pushpin as a MapMarkerDocument
+ const mapMarker = Docs.Create.PushpinDocument(
+ NumCast(latitude),
+ NumCast(longitude),
+ false,
+ [],
+ { map: map }
+ // ,'pushpinIDamongus'+ this.incrementer++
+ );
+ this.addDocument(mapMarker, this.annotationKey);
+
+ // mapMarker.infoWindowOpen = true;
+ }, 'createpin');
+
+ // The pin that is selected
+ @observable selectedPin: Doc | undefined;
+
+ @action
+ deselectPin = () => {
+ if (this.selectedPin) {
+ // Removes filter
+ Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove');
+ Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove');
+
+ const temp = this.selectedPin;
+ 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._bingMap.current.entities.push(newpin);
+ this.map_docToPinMap.set(temp, newpin);
+ this.selectedPin = undefined;
+ this.bingSearchBarContents = this.rootDoc.map;
+ }
+ };
+
+ getView = async (doc: Doc) => {
+ if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar();
+ return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ };
+ /*
+ * Pushpin onclick
+ */
+ @action
+ pushpinClicked = (pinDoc: Doc) => {
+ this.deselectPin();
+ this.selectedPin = pinDoc;
+ this.bingSearchBarContents = pinDoc.map;
+
+ Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'match');
+ Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'match');
+
+ this.recolorPin(this.selectedPin, 'green');
+
+ MapAnchorMenu.Instance.Delete = this.deleteSelectedPin;
+ MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
+ MapAnchorMenu.Instance.LinkNote = this.createNoteAnnotation;
+
+ const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPin.latitude, this.selectedPin.longitude));
+ const x = point.x + (this.props.PanelWidth() - this.sidebarWidth()) / 2;
+ const y = point.y + this.props.PanelHeight() / 2 + 32;
+ const cpt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+ MapAnchorMenu.Instance.jumpTo(cpt[0], cpt[1], true);
+
+ document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+ };
+
+ /**
+ * Returns a list of Pushpin docs
+ */
+ @computed get allMapPushpins() {
+ return DocListCast(this.dataDoc[this.annotationKey]);
+ }
+
+ /**
+ * Map OnClick
+ */
+ @action
+ mapOnClick = (e: { location: { latitude: any; longitude: any } }) => {
+ this.props.select(false);
+ this.deselectPin();
+ };
+ /*
+ * Updates values of layout doc to match the current map
+ */
+ @action
+ mapRecentered = () => {
+ if (
+ Math.abs(NumCast(this.dataDoc.latitude) - this._bingMap.current.getCenter().latitude) > 1e-7 || //
+ Math.abs(NumCast(this.dataDoc.longitude) - this._bingMap.current.getCenter().longitude) > 1e-7
+ ) {
+ this.dataDoc.latitude = this._bingMap.current.getCenter().latitude;
+ this.dataDoc.longitude = this._bingMap.current.getCenter().longitude;
+ this.dataDoc.map = '';
+ this.bingSearchBarContents = '';
+ }
+ this.dataDoc.map_zoom = this._bingMap.current.getZoom();
+ };
+ /*
+ * Updates maptype
+ */
+ @action
+ updateMapType = () => (this.dataDoc.map_type = this._bingMap.current.getMapTypeId());
+
+ /*
+ * For Bing Maps
+ * Called by search button's onClick
+ * Finds the geocode of the searched contents and sets location to that location
+ **/
+ @action
+ bingSearch = () => {
+ return this.bingGeocode(this._bingMap, this.bingSearchBarContents).then(location => {
+ this.dataDoc.latitude = location.latitude;
+ this.dataDoc.longitude = location.longitude;
+ this.dataDoc.map_zoom = this._bingMap.current.getZoom();
+ this.dataDoc.map = this.bingSearchBarContents;
+ });
+ };
+
+ /*
+ * Returns doc w/ relevant info
+ */
+ getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
+ /// this should use SELECTED pushpin for lat/long if there is a selection, otherwise CENTER
+ const anchor = Docs.Create.ConfigDocument({
+ title: 'MapAnchor:' + this.rootDoc.title,
+ text: StrCast(this.selectedPin?.map) || StrCast(this.rootDoc.map) || 'map location',
+ config_latitude: NumCast(this.selectedPin?.latitude ?? this.dataDoc.latitude),
+ config_longitude: NumCast(this.selectedPin?.longitude ?? this.dataDoc.longitude),
+ config_map_zoom: NumCast(this.dataDoc.map_zoom),
+ config_map_type: StrCast(this.dataDoc.map_type),
+ config_map: StrCast(this.selectedPin?.map) || StrCast(this.dataDoc.map),
+ layout_unrendered: true,
+ });
+ if (anchor) {
+ anchor.mapPin = this.selectedPin;
+ if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
+ /* addAsAnnotation &&*/ this.addDocument(anchor);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.rootDoc);
+ return anchor;
+ }
+ return this.rootDoc;
+ };
+
+ map_docToPinMap = new Map<Doc, any>();
+ map_pinHighlighted = new Map<Doc, boolean>();
+ /*
+ * Input: pin doc
+ * Adds MicrosoftMaps Pushpin to the map (render)
+ */
+ @action
+ addPushpin = (pin: Doc) => {
+ const pushPin = pin.infoWindowOpen
+ ? new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(pin.latitude, pin.longitude), {})
+ : new this.MicrosoftMaps.Pushpin(
+ new this.MicrosoftMaps.Location(pin.latitude, pin.longitude)
+ // {icon: 'http://icons.iconarchive.com/icons/icons-land/vista-map-markers/24/Map-Marker-Marker-Outside-Chartreuse-icon.png'}
+ );
+
+ this._bingMap.current.entities.push(pushPin);
+
+ this.MicrosoftMaps.Events.addHandler(pushPin, 'click', (e: any) => this.pushpinClicked(pin));
+ // this.MicrosoftMaps.Events.addHandler(pushPin, 'dblclick', (e: any) => this.pushpinDblClicked(pushPin, pin));
+ this.map_docToPinMap.set(pin, pushPin);
+ };
+
+ /*
+ * Input: pin doc
+ * Removes pin from annotations
+ */
+ @action
+ removePushpin = (pinDoc: Doc) => this.removeDocument(pinDoc, this.annotationKey);
+
+ /*
+ * Removes pushpin from map render
+ */
+ deletePushpin = (pinDoc: Doc) => {
+ this._bingMap.current.entities.remove(this.map_docToPinMap.get(pinDoc));
+ this.map_docToPinMap.delete(pinDoc);
+ this.selectedPin = undefined;
+ };
+
+ @action
+ deleteSelectedPin = undoable(() => {
+ if (this.selectedPin) {
+ // Removes filter
+ Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove');
+ Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove');
+
+ this.removePushpin(this.selectedPin);
+ }
+ MapAnchorMenu.Instance.fadeOut(true);
+ document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+ }, 'delete pin');
+
+ tryHideMapAnchorMenu = (e: PointerEvent) => {
+ let target = document.elementFromPoint(e.x, e.y);
+ while (target) {
+ if (target === MapAnchorMenu.top.current) return;
+ target = target.parentElement;
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ MapAnchorMenu.Instance.fadeOut(true);
+ document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+ };
+
+ @action
+ centerOnSelectedPin = () => {
+ if (this.selectedPin) {
+ this.dataDoc.latitude = this.selectedPin.latitude;
+ this.dataDoc.longitude = this.selectedPin.longitude;
+ this.dataDoc.map = this.selectedPin.map ?? '';
+ this.bingSearchBarContents = this.selectedPin.map;
+ }
+ MapAnchorMenu.Instance.fadeOut(true);
+ document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu);
+ };
+
+ /**
+ * View options for bing maps
+ */
bingViewOptions = {
- center: { latitude: defaultCenter.lat, longitude: defaultCenter.lng },
+ // center: { latitude: this.dataDoc.latitude ?? defaultCenter.lat, longitude: this.dataDoc.longitude ?? defaultCenter.lng },
+ zoom: this.dataDoc.latitude ?? 10,
mapTypeId: 'grayscale',
};
+
+ /**
+ * Map options
+ */
bingMapOptions = {
navigationBarMode: 'square',
+ backgroundColor: '#f1f3f4',
+ enableInertia: true,
+ supportedMapTypes: ['grayscale', 'canvasLight'],
+ disableMapTypeSelectorMouseOver: true,
+ // showScalebar:true
+ // disableRoadView:true,
+ // disableBirdseye:true
+ streetsideOptions: {
+ showProblemReporting: false,
+ showCurrentAddress: false,
+ },
};
- bingMapReady = (map: any) => (this._bingMap = map.map);
+
+ @action
+ searchbarOnEdit = (newText: string) => (this.bingSearchBarContents = newText);
+
+ recolorPin = (pin: Doc, color?: string) => {
+ this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin));
+ this.map_docToPinMap.delete(pin);
+ const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(pin.latitude, pin.longitude), color ? { color } : {});
+ this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(pin));
+ this._bingMap.current.entities.push(newpin);
+ this.map_docToPinMap.set(pin, newpin);
+ };
+
+ /*
+ * Called when BingMap is first rendered
+ * Initializes starting values
+ */
+ @observable _mapReady = false;
+ @action
+ bingMapReady = (map: any) => {
+ this._mapReady = true;
+ this._bingMap = map.map;
+ if (!this._bingMap.current) {
+ alert('NO Map!?');
+ }
+ this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'click', this.mapOnClick);
+ this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'viewchangeend', undoable(this.mapRecentered, 'Map Layout Change'));
+ this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'maptypechanged', undoable(this.updateMapType, 'Map ViewType Change'));
+
+ this._disposers.mapLocation = reaction(
+ () => this.rootDoc.map,
+ mapLoc => (this.bingSearchBarContents = mapLoc),
+ { fireImmediately: true }
+ );
+ this._disposers.highlight = reaction(
+ () => this.allMapPushpins.map(doc => doc[Highlight]),
+ () => {
+ const allPins = this.allMapPushpins.map(doc => ({ doc, pushpin: DocCast(doc.mapPin) })).filter(pair => pair.pushpin);
+ allPins.forEach(({ doc, pushpin }) => {
+ if (!pushpin[Highlight] && this.map_pinHighlighted.get(pushpin)) {
+ this.recolorPin(pushpin);
+ this.map_pinHighlighted.delete(pushpin);
+ }
+ });
+ allPins.forEach(({ doc, pushpin }) => {
+ if (doc[Highlight] && !this.map_pinHighlighted.get(pushpin)) {
+ this.recolorPin(pushpin, 'orange');
+ this.map_pinHighlighted.set(pushpin, true);
+ }
+ });
+ },
+ { fireImmediately: true }
+ );
+
+ this._disposers.location = reaction(
+ () => ({ lat: this.rootDoc.latitude, lng: this.rootDoc.longitude, zoom: this.rootDoc.map_zoom, mapType: this.rootDoc.map_type }),
+ locationObject => {
+ // if (this._bingMap.current)
+ try {
+ locationObject?.zoom &&
+ this._bingMap.current?.setView({
+ mapTypeId: locationObject.mapType,
+ zoom: locationObject.zoom,
+ center: new this.MicrosoftMaps.Location(locationObject.lat, locationObject.lng),
+ });
+ } catch (e) {
+ console.log(e);
+ }
+ },
+ { fireImmediately: true }
+ );
+ };
+
+ dragToggle = (e: React.PointerEvent) => {
+ let dragClone: HTMLDivElement | undefined;
+
+ setupMoveUpEvents(
+ e,
+ e,
+ e => {
+ if (!dragClone) {
+ dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement;
+ dragClone.style.position = 'absolute';
+ dragClone.style.zIndex = '10000';
+ DragManager.Root().appendChild(dragClone);
+ }
+ dragClone.style.transform = `translate(${e.clientX - 15}px, ${e.clientY - 15}px)`;
+ return false;
+ },
+ e => {
+ if (!dragClone) return;
+ DragManager.Root().removeChild(dragClone);
+ let target = document.elementFromPoint(e.x, e.y);
+ while (target) {
+ if (target === this._ref.current) {
+ const cpt = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+ const x = cpt[0] - (this.props.PanelWidth() - this.sidebarWidth()) / 2;
+ const y = cpt[1] - 32 /* height of search bar */ - this.props.PanelHeight() / 2;
+ const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
+ this.createPushpin(location.latitude, location.longitude);
+ break;
+ }
+ target = target.parentElement;
+ }
+ },
+ e => {
+ const createPin = () => this.createPushpin(this.rootDoc.latitude, this.rootDoc.longitude, this.rootDoc.map);
+ if (this.bingSearchBarContents) {
+ this.bingSearch().then(createPin);
+ } else createPin();
+ }
+ );
+ };
+
+ searchbarKeyDown = (e: any) => e.key === 'Enter' && this.bingSearch();
+
+ static _firstRender = true;
+ static _rerenderDelay = 0;
+ _rerenderTimeout: any;
render() {
+ // bcz: no idea what's going on here, but bings maps have some kind of bug
+ // such that we need to delay rendering a second map on startup until the first map is rendered.
+ this.rootDoc[DocCss];
+ if (MapBox._firstRender) {
+ MapBox._firstRender = false;
+ MapBox._rerenderDelay = 500;
+ } else if (MapBox._rerenderDelay) {
+ // prettier-ignore
+ this._rerenderTimeout = this._rerenderTimeout ??
+ setTimeout(action(() => {
+ MapBox._rerenderDelay = 0;
+ this.rootDoc[DocCss] = this.rootDoc[DocCss] + 1;
+ }), MapBox._rerenderDelay);
+ return null;
+ }
+
const renderAnnotations = (childFilters?: () => string[]) => null;
return (
<div className="mapBox" ref={this._ref}>
@@ -607,47 +696,84 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
onWheel={e => e.stopPropagation()}
onPointerDown={async e => {
e.button === 0 && !e.ctrlKey && e.stopPropagation();
- // just a simple test of bing maps geocode api
- // const loc = await this.bingGeocode(this._bingMap, 'Philadelphia, PA');
- // this._bingMap.current.setView({
- // mapTypeId: this.MicrosoftMaps.MapTypeId.aerial,
- // center: new this.MicrosoftMaps.Location(loc.latitude, loc.longitude),
- // zoom: 15,
- // });
}}
- style={{ width: `calc(100% - ${this.layout_sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
+ style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
<div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
{renderAnnotations(this.opaqueFilter)}
{SnappingManager.GetIsDragging() ? null : renderAnnotations()}
- {this.annotationLayer}
-
- {!MapBox.UseBing ? null : <BingMapsReact onMapReady={this.bingMapReady} bingMapsKey={bingApiKey} height="100%" mapOptions={this.bingMapOptions} width="100%" viewOptions={this.bingViewOptions} />}
- <div style={{ display: MapBox.UseBing ? 'none' : undefined }}>
- <GoogleMap mapContainerStyle={mapContainerStyle} onZoomChanged={this.zoomChanged} onCenterChanged={this.centered} onLoad={this.loadHandler} options={mapOptions}>
- <Autocomplete onLoad={this.setSearchBox} onPlaceChanged={this.handlePlaceChanged}>
- <input className="mapBox-input" ref={this.inputRef} type="text" onKeyDown={e => e.stopPropagation()} placeholder="Enter location" />
- </Autocomplete>
-
- {this.renderMarkers()}
- {this.allMapMarkers
- .filter(marker => marker.infoWindowOpen)
- .map(marker => (
- <MapBoxInfoWindow
- key={marker[Id]}
- {...this.props}
- setContentView={emptyFunction}
- place={marker}
- markerMap={this.markerMap}
- PanelWidth={this.infoWidth}
- PanelHeight={this.infoHeight}
- moveDocument={this.moveDocument}
- isAnyChildContentActive={this.isAnyChildContentActive}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- />
- ))}
- {/* {this.handleMapCenter(this._map)} */}
- </GoogleMap>
+
+ <div className="mapBox-searchbar">
+ <EditableText
+ // editing
+ setVal={(newText: string | number) => typeof newText === 'string' && this.searchbarOnEdit(newText)}
+ onEnter={e => this.bingSearch()}
+ placeholder={this.bingSearchBarContents || 'enter city/zip/...'}
+ textAlign="center"
+ />
+ <IconButton
+ icon={
+ <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="magnifying-glass" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" color="#DFDFDF">
+ <path
+ fill="currentColor"
+ d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"></path>
+ </svg>
+ }
+ onClick={this.bingSearch}
+ type={Type.TERT}
+ />
+ <div style={{ width: 30, height: 30 }} ref={this._dragRef} onPointerDown={this.dragToggle}>
+ <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} />
+ </div>
</div>
+
+ <BingMapsReact
+ onMapReady={this.bingMapReady} //
+ bingMapsKey={bingApiKey}
+ height="100%"
+ mapOptions={this.bingMapOptions}
+ width="100%"
+ viewOptions={this.bingViewOptions}
+ />
+ <div>
+ {!this._mapReady
+ ? null
+ : this.allMapPushpins.map(pushpin => (
+ <DocumentView
+ {...this.props}
+ renderDepth={this.props.renderDepth + 1}
+ Document={pushpin}
+ DataDoc={undefined}
+ PanelWidth={returnOne}
+ PanelHeight={returnOne}
+ NativeWidth={returnOne}
+ NativeHeight={returnOne}
+ onKey={undefined}
+ onDoubleClick={undefined}
+ onBrowseClick={undefined}
+ childFilters={returnEmptyFilter}
+ childFiltersByRanges={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ isDocumentActive={returnFalse}
+ isContentActive={returnFalse}
+ addDocTab={returnFalse}
+ ScreenToLocalTransform={Transform.Identity}
+ fitContentsToBox={undefined}
+ focus={returnOne}
+ />
+ ))}
+ </div>
+ {/* <MapBoxInfoWindow
+ key={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})[Id]}
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+ place={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})}
+ markerMap={this.markerMap}
+ PanelWidth={this.infoWidth}
+ PanelHeight={this.infoHeight}
+ moveDocument={this.moveDocument}
+ isAnyChildContentActive={this.isAnyChildContentActive}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ /> */}
+
{!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
<MarqueeAnnotator
rootDoc={this.rootDoc}
@@ -666,7 +792,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
)}
</div>
{/* </LoadScript > */}
- <div className="mapBox-sidebar" style={{ position: 'absolute', right: 0, height: '100%', width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+ <div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
<SidebarAnnos
ref={this._sidebarRef}
{...this.props}
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index b3ae8242d..a54bdcd5e 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -229,7 +229,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
map: map,
});
map.panTo(position);
- const mapMarker = Docs.Create.MapMarkerDocument(NumCast(position.lat()), NumCast(position.lng()), false, [], {});
+ const mapMarker = Docs.Create.PushpinDocument(NumCast(position.lat()), NumCast(position.lng()), false, [], {});
this.addDocument(mapMarker, this.annotationKey);
};
@@ -260,7 +260,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
// } else {
// alert("Your geolocation is not supported by browser.")
// };
- map.setZoom(NumCast(this.dataDoc.mapZoom, 2.5));
+ map.setZoom(NumCast(this.dataDoc.map_zoom, 2.5));
map.setCenter(new google.maps.LatLng(NumCast(this.dataDoc.mapLat), NumCast(this.dataDoc.mapLng)));
setTimeout(() => {
if (this._loadPending && this._map.getBounds()) {
@@ -292,7 +292,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this._loadPending = false;
this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
}
- this.dataDoc.mapZoom = this._map.getZoom();
+ this.dataDoc.map_zoom = this._map.getZoom();
};
/**
@@ -333,7 +333,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (existingMarker) {
Doc.AddDocToList(existingMarker, 'data', doc);
} else {
- const marker = Docs.Create.MapMarkerDocument(NumCast(doc.lat), NumCast(doc.lng), false, [doc], {});
+ const marker = Docs.Create.PushpinDocument(NumCast(doc.lat), NumCast(doc.lng), false, [doc], {});
this.addDocument(marker, this.annotationKey);
}
}
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index 577101445..66c47d131 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -47,7 +47,9 @@ export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & Vi
removeDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.RemoveDocFromList(this.props.place, 'data', d), true as boolean);
render() {
return (
- <InfoWindow anchor={this.props.markerMap[this.props.place[Id]]} onCloseClick={this.handleInfoWindowClose}>
+ <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
diff --git a/src/client/views/nodes/MapBox/MapPushpinBox.tsx b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
new file mode 100644
index 000000000..552bceace
--- /dev/null
+++ b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
@@ -0,0 +1,35 @@
+import { observer } from 'mobx-react';
+// import { SettingsManager } from '../../../util/SettingsManager';
+import { ViewBoxBaseComponent } from '../../DocComponent';
+import { FieldView, FieldViewProps } from '../FieldView';
+import React = require('react');
+import { computed } from 'mobx';
+import { MapBox } from './MapBox';
+
+/**
+ * Map Pushpin doc class
+ */
+@observer
+export class MapPushpinBox extends ViewBoxBaseComponent<FieldViewProps>() {
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(MapPushpinBox, fieldKey);
+ }
+ componentDidMount() {
+ // if (this.mapBoxView)
+ this.mapBoxView.addPushpin(this.rootDoc);
+ }
+ componentWillUnmount() {
+ this.mapBoxView.deletePushpin(this.rootDoc);
+ }
+
+ @computed get mapBoxView() {
+ return this.props.DocumentView?.()?.props.docViewPath().lastElement()?.ComponentView as MapBox;
+ }
+ @computed get mapBox() {
+ return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc;
+ }
+
+ render() {
+ return <div />;
+ }
+}
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index f61563f4c..6ff22e929 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -52,6 +52,7 @@ export interface pinDataTypes {
dataview?: boolean;
poslayoutview?: boolean;
dataannos?: boolean;
+ map?: boolean;
}
export interface PinProps {
audioRange?: boolean;
@@ -383,6 +384,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const inkable = [DocumentType.INK].includes(targetType);
const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetType) || target?._type_collection === CollectionViewType.Stacking;
const pannable = [DocumentType.IMG, DocumentType.PDF].includes(targetType) || (targetType === DocumentType.COL && target?._type_collection === CollectionViewType.Freeform);
+ const map = [DocumentType.MAP].includes(targetType);
const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(targetType);
const clippable = [DocumentType.COMPARISON].includes(targetType);
const datarange = [DocumentType.FUNCPLOT].includes(targetType);
@@ -392,7 +394,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const filters = true;
const pivot = true;
const dataannos = false;
- return { scrollable, pannable, inkable, type_collection, pivot, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos };
+ return { scrollable, pannable, inkable, type_collection, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos };
}
@action
@@ -460,6 +462,28 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
changed = true;
}
}
+ if (pinDataTypes?.map || (!pinDataTypes && activeItem.config_latitude !== undefined)) {
+ if (bestTarget.latitude !== activeItem.config_latitude) {
+ Doc.SetInPlace(bestTarget, 'latitude', NumCast(activeItem.config_latitude), true);
+ changed = true;
+ }
+ if (bestTarget.longitude !== activeItem.config_longitude) {
+ Doc.SetInPlace(bestTarget, 'longitude', NumCast(activeItem.config_longitude), true);
+ changed = true;
+ }
+ if (bestTarget.zoom !== activeItem.config_map_zoom) {
+ Doc.SetInPlace(bestTarget, 'map_zoom', NumCast(activeItem.config_map_zoom), true);
+ changed = true;
+ }
+ if (bestTarget.map_type !== activeItem.config_map_type) {
+ Doc.SetInPlace(bestTarget, 'map_type', StrCast(activeItem.config_map_type), true);
+ changed = true;
+ }
+ if (bestTarget.map !== activeItem.config_map) {
+ Doc.SetInPlace(bestTarget, 'map', StrCast(activeItem.config_map), true);
+ changed = true;
+ }
+ }
if (pinDataTypes?.temporal || (!pinDataTypes && activeItem.config_clipStart !== undefined)) {
if (bestTarget._layout_currentTimecode !== activeItem.config_clipStart) {
bestTarget._layout_currentTimecode = activeItem.config_clipStart;
@@ -571,7 +595,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const dv = DocumentManager.Instance.getDocumentView(bestTarget);
if (dv) {
changed = true;
- const computedScale = NumCast(activeItem.presZoom, 1) * Math.min(dv.props.PanelWidth() / viewport.width, dv.props.PanelHeight() / viewport.height);
+ const computedScale = NumCast(activeItem.config_zoom, 1) * Math.min(dv.props.PanelWidth() / viewport.width, dv.props.PanelHeight() / viewport.height);
activeItem.presentation_movement === PresMovement.Zoom && (bestTarget._freeform_scale = computedScale);
dv.ComponentView?.brushView?.(viewport, transTime);
}
@@ -640,6 +664,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
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 =>
@@ -723,7 +754,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const options: DocFocusOptions = {
willPan: activeItem.presentation_movement !== PresMovement.None,
willZoomCentered: activeItem.presentation_movement === PresMovement.Zoom || activeItem.presentation_movement === PresMovement.Jump || activeItem.presentation_movement === PresMovement.Center,
- zoomScale: activeItem.presentation_movement === PresMovement.Center ? 0 : NumCast(activeItem.presZoom, 1),
+ zoomScale: activeItem.presentation_movement === PresMovement.Center ? 0 : NumCast(activeItem.config_zoom, 1),
zoomTime: activeItem.presentation_movement === PresMovement.Jump ? 0 : Math.min(Math.max(effect ? 750 : 500, (effect ? 0.2 : 1) * presTime), presTime),
effect: activeItem,
noSelect: true,
@@ -1362,7 +1393,7 @@ 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.presZoom = scale));
+ this.selectedArray.forEach(doc => (doc.config_zoom = scale));
};
/*
@@ -1636,7 +1667,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
if (activeItem && this.targetDoc) {
const transitionSpeed = activeItem.presentation_transition ? NumCast(activeItem.presentation_transition) / 1000 : 0.5;
- const zoom = NumCast(activeItem.presZoom, 1) * 100;
+ const zoom = NumCast(activeItem.config_zoom, 1) * 100;
const effect = activeItem.presentation_effect ? activeItem.presentation_effect : PresMovement.None;
return (
<div
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index ceacb8a08..7ba4f0e6f 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -510,6 +510,13 @@ export namespace Doc {
export function IsDelegateField(doc: Doc, fieldKey: string) {
return doc && Get(doc, fieldKey, true) !== undefined;
}
+ //
+ // this will write the value to the key on either the data doc or the embedding doc. The choice
+ // of where to write it is based on:
+ // 1) if the embedding Doc already has this field defined on it, then it will be written to the embedding
+ // 2) if the data doc has the field, then it's written there.
+ // 3) if neither already has the field, then 'defaultProto' determines whether to write it to the data doc (or the embedding)
+ //
export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) {
if (key.startsWith('_')) key = key.substring(1);
const hasProto = Doc.GetProto(doc) !== doc ? Doc.GetProto(doc) : undefined;
@@ -1483,8 +1490,8 @@ export namespace Doc {
runInAction(() => {
for (let i = 0; i < childFilters.length; i++) {
const fields = childFilters[i].split(FilterSep); // split key:value:modifier
- if (fields[0] === key && (fields[1] === value || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) {
- if (fields[2] === modifiers && modifiers && fields[1] === value) {
+ if (fields[0] === key && (fields[1] === value.toString() || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) {
+ if (fields[2] === modifiers && modifiers && fields[1] === value.toString()) {
if (toggle) modifiers = 'remove';
else return;
}