aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts21
-rw-r--r--src/client/documents/DocumentTypes.ts2
-rw-r--r--src/client/documents/Documents.ts12
-rw-r--r--src/client/util/CalendarManager.scss18
-rw-r--r--src/client/util/CalendarManager.tsx82
-rw-r--r--src/client/util/CurrentUserUtils.ts2
-rw-r--r--src/client/views/Main.tsx5
-rw-r--r--src/client/views/collections/CollectionCalendarView.tsx107
-rw-r--r--src/client/views/collections/CollectionSubView.tsx2
-rw-r--r--src/client/views/collections/CollectionView.tsx3
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx2
-rw-r--r--src/client/views/nodes/MapBox/MapAnchorMenu.tsx236
-rw-r--r--src/client/views/nodes/MapBox/MapBox.scss7
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx199
-rw-r--r--src/client/views/nodes/calendarBox/CalendarBox.tsx134
15 files changed, 566 insertions, 266 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index 5f9475f23..a060e4a2c 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -895,3 +895,24 @@ export function setupMoveUpEvents(
document.addEventListener('pointerup', _upEvent, true);
document.addEventListener('click', _clickEvent, true);
}
+
+export function dateRangeStrToDates (dateStr: string) {
+ // dateStr in yyyy-mm-dd format
+ const dateRangeParts = dateStr.split("|"); // splits into from and to date
+ const fromParts = dateRangeParts[0].split("-");
+ const toParts = dateRangeParts[1].split("-");
+
+ const fromYear = parseInt(fromParts[0]);
+ const fromMonth = parseInt(fromParts[1])-1;
+ const fromDay = parseInt(fromParts[2]);
+
+ const toYear = parseInt(toParts[0]);
+ const toMonth = parseInt(toParts[1])-1;
+ const toDay = parseInt(toParts[2]);
+
+
+ return [
+ new Date(fromYear, fromMonth, fromDay),
+ new Date(toYear, toMonth, toDay)
+ ];
+} \ No newline at end of file
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 5c2792a53..1123bcac9 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -62,5 +62,5 @@ export enum CollectionViewType {
Pile = 'pileup',
StackedTimeline = 'stacked timeline',
NoteTaking = 'notetaking',
- Calendar = 'calendar_view'
+ Calendar = 'calendar'
}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d6fd8aea3..ff14eb101 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -58,6 +58,7 @@ import { VideoBox } from '../views/nodes/VideoBox';
import { WebBox } from '../views/nodes/WebBox';
import { SearchBox } from '../views/search/SearchBox';
import { CollectionViewType, DocumentType } from './DocumentTypes';
+import { CalendarBox } from '../views/nodes/calendarBox/CalendarBox';
const {
default: { DFLT_IMAGE_NATIVE_DIM },
} = require('../views/global/globalCssVariables.module.scss');
@@ -784,6 +785,13 @@ export namespace Docs {
options: {},
},
],
+ [
+ DocumentType.CALENDAR,
+ {
+ layout: { view: CalendarBox, dataField: defaultDataKey },
+ options: {},
+ }
+ ]
]);
const suffix = 'Proto';
@@ -1146,8 +1154,8 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.MAPROUTE), new List(documents), { infoWindowOpen, ...options }, id);
}
- export function CalendarDocument(options: DocumentOptions={}, documents: Array<Doc>){
- return InstanceFromProto(Prototypes.get(DocumentType.CALENDAR), new List(documents), options)
+ export function CalendarDocument(options: DocumentOptions, documents: Array<Doc>){
+ return InstanceFromProto(Prototypes.get(DocumentType.CALENDAR), new List(documents), {...options})
}
// shouldn't ever need to create a KVP document-- instead set the LayoutTemplateString to be a KeyValueBox for the DocumentView (see addDocTab in TabDocView)
diff --git a/src/client/util/CalendarManager.scss b/src/client/util/CalendarManager.scss
index 60610f298..114e19a0e 100644
--- a/src/client/util/CalendarManager.scss
+++ b/src/client/util/CalendarManager.scss
@@ -31,19 +31,20 @@
margin-top: 10px;
align-self: center;
width: 60%;
+ }
- .MuiFilledInput-input{
- padding: 10px;
- }
+ .description-container{
+ margin-top: 10px;
+ align-self: center;
+ width: 60%;
}
.date-range-picker-container{
margin-top: 5px;
- align-self:center;
-
- .react-date-range{
-
- }
+ align-self: center;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
}
.create-button-container{
@@ -57,6 +58,5 @@
border-radius: 5px;
background-color: #EFF2F7;
}
-
}
}
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index 3872294db..6ef2d3429 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -10,16 +10,16 @@ import { MainViewModal } from '../views/MainViewModal';
import { TextField } from '@mui/material';
import Select from 'react-select';
import { SettingsManager } from './SettingsManager';
-import { DocCast, StrCast } from '../../fields/Types';
+import { DateCast, DocCast, StrCast } from '../../fields/Types';
import { SelectionManager } from './SelectionManager';
import { DocumentManager } from './DocumentManager';
import { DocData } from '../../fields/DocSymbols';
// import { DateRange, Range, RangeKeyDict } from 'react-date-range';
import { Button } from 'browndash-components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Provider, useProvider, defaultTheme } from '@adobe/react-spectrum';
-
-import { DateRangePicker } from '@adobe/react-spectrum';
+import { Provider, defaultTheme } from '@adobe/react-spectrum';
+import { DateValue } from '@internationalized/date';
+import { DateRangePicker, SpectrumDateRangePickerProps } from '@adobe/react-spectrum';
import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons';
import { Docs } from '../documents/Documents';
import { ObservableReactComponent } from '../views/ObservableReactComponent';
@@ -33,7 +33,10 @@ interface CalendarSelectOptions {
value: string;
}
-const formatDateToString = (date: Date) => {
+const formatCalendarDateToString = (calendarDate: any) => {
+ console.log('Formatting the following date: ', calendarDate);
+ const date = new Date(calendarDate.year, calendarDate.month - 1, calendarDate.day);
+ console.log(typeof date);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
@@ -115,25 +118,35 @@ export class CalendarManager extends ObservableReactComponent<{}> {
@action
handleSelectChange = (option: any) => {
- let selectOpt = option as CalendarSelectOptions;
- this.selectedExistingCalendarOption = selectOpt;
- this.calendarName = selectOpt.value; // or label
+ if (option) {
+ let selectOpt = option as CalendarSelectOptions;
+ this.selectedExistingCalendarOption = selectOpt;
+ this.calendarName = selectOpt.value; // or label
+ }
};
@action
- handleTextFieldChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
+ handleCalendarTitleChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
+ console.log('Existing calendars: ', this.existingCalendars);
this.calendarName = event.target.value;
};
+ @action
+ handleCalendarDescriptionChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
+ this.calendarDescription = event.target.value;
+ };
+
// TODO: Make undoable
private addToCalendar = () => {
let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; // doc to add to calendar
+ console.log(targetDoc);
if (targetDoc) {
let calendar: Doc;
if (this.creationType === 'new-calendar') {
if (!this.existingCalendars.find(doc => StrCast(doc.title) === this.calendarName)) {
+ console.log('creating...');
calendar = Docs.Create.CalendarDocument(
{
title: this.calendarName,
@@ -141,6 +154,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
},
[]
);
+ console.log('successful calendar creation');
} else {
this.errorMessage = 'Calendar with this name already exists';
return;
@@ -155,15 +169,20 @@ export class CalendarManager extends ObservableReactComponent<{}> {
}
}
// Get start and end date strings
- const startDateStr = formatDateToString(this.selectedDateRange.start);
- const endDateStr = formatDateToString(this.selectedDateRange.end);
+ const startDateStr = formatCalendarDateToString(this.selectedDateRange.start);
+ const endDateStr = formatCalendarDateToString(this.selectedDateRange.end);
+
+ console.log('start date: ', startDateStr);
+ console.log('end date: ', endDateStr);
const subDocEmbedding = Doc.MakeEmbedding(targetDoc); // embedding
+ console.log('subdoc embedding', subDocEmbedding);
subDocEmbedding.embedContainer = calendar; // set embed container
- subDocEmbedding.date_range = `${startDateStr}-${endDateStr}`; // set subDoc date range
+ subDocEmbedding.date_range = `${startDateStr}|${endDateStr}`; // set subDoc date range
Doc.AddDocToList(calendar, 'data', subDocEmbedding); // add embedded subDoc to calendar
+ console.log('my calendars: ', Doc.MyCalendars);
if (this.creationType === 'new-calendar') {
Doc.AddDocToList(Doc.MyCalendars, 'data', calendar); // add to new calendar to dashboard calendars
}
@@ -211,6 +230,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
@action
setSelectedDateRange = (range: any) => {
+ console.log('Range: ', range);
this.selectedDateRange = range;
};
@@ -220,9 +240,12 @@ export class CalendarManager extends ObservableReactComponent<{}> {
let startDate: Date | undefined;
let endDate: Date | undefined;
try {
- startDate = this.selectedDateRange[0].startDate;
- endDate = this.selectedDateRange[0].endDate;
+ startDate = this.selectedDateRange.start;
+ endDate = this.selectedDateRange.end;
+ console.log(startDate);
+ console.log(endDate);
} catch (e: any) {
+ console.log(e);
return false; // disabled
}
if (!startDate || !endDate) return false; // disabled if any is undefined
@@ -258,8 +281,9 @@ export class CalendarManager extends ObservableReactComponent<{}> {
{this.creationType === 'new-calendar' ? (
<TextField
fullWidth
- onChange={this.handleTextFieldChange}
- placeholder="Enter calendar name..."
+ onChange={this.handleCalendarTitleChange}
+ label="Calendar name"
+ placeholder="Enter a name..."
variant="filled"
style={{
backgroundColor: 'white',
@@ -298,14 +322,32 @@ export class CalendarManager extends ObservableReactComponent<{}> {
}}></Select>
)}
</div>
+ <div className="description-container">
+ <TextField
+ fullWidth
+ multiline
+ label="Calendar description"
+ placeholder="Enter a description (optional)..."
+ onChange={this.handleCalendarDescriptionChange}
+ variant="filled"
+ style={{
+ backgroundColor: 'white',
+ color: 'black',
+ borderRadius: '5px',
+ }}
+ />
+ </div>
<div className="date-range-picker-container">
+ <div>Select a date range: </div>
<Provider theme={defaultTheme}>
- <DateRangePicker value={this.selectedDateRange} onChange={v => this.setSelectedDateRange(v)} label="Date range" />
+ <DateRangePicker aria-label="Select a date range" value={this.selectedDateRange} onChange={v => this.setSelectedDateRange(v)} />
</Provider>
</div>
- <div className="create-button-container">
- <Button active={this.createButtonActive} onClick={() => {}} text="Add to Calendar" iconPlacement="right" icon={<FontAwesomeIcon icon={faPlus as IconLookup} />} />
- </div>
+ {this.createButtonActive && (
+ <div className="create-button-container">
+ <Button onClick={() => this.addToCalendar()} text="Add to Calendar" iconPlacement="right" icon={<FontAwesomeIcon icon={faPlus as IconLookup} />} />
+ </div>
+ )}
</div>
);
}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 61a9fa7c2..f7070a862 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1022,4 +1022,4 @@ ScriptingGlobals.add(function IsExploreMode() { return DocumentView.ExploreMode;
ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode");
ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering");
ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
-ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; });
+ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; }); \ No newline at end of file
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 96bd52d39..17c21326d 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -22,7 +22,10 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' };
(async () => {
MainView.Live = window.location.search.includes('live');
- const root = ReactDOM.createRoot(document.getElementById('root')!);
+ const rootEle = document.getElementById('root');
+ if (!rootEle) return;
+ rootEle.style.zIndex = '0';
+ const root = ReactDOM.createRoot(rootEle);
root.render(<FieldLoader />);
window.location.search.includes('safe') && CollectionView.SetSafeMode(true);
const info = await CurrentUserUtils.loadCurrentUser();
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx
new file mode 100644
index 000000000..2cec29669
--- /dev/null
+++ b/src/client/views/collections/CollectionCalendarView.tsx
@@ -0,0 +1,107 @@
+import * as React from 'react';
+import { CollectionSubView } from './CollectionSubView';
+import { observer } from 'mobx-react';
+import { computed, makeObservable, observable } from 'mobx';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { CollectionStackingView } from './CollectionStackingView';
+import { CollectionViewType } from '../../documents/DocumentTypes';
+import { dateRangeStrToDates, emptyFunction, returnAll, returnEmptyDoclist, returnNone, returnOne, returnTrue } from '../../../Utils';
+import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
+import { TbRuler } from 'react-icons/tb';
+import { Transform } from '../../util/Transform';
+import { DocData } from '../../../fields/DocSymbols';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { StyleProp } from '../StyleProvider';
+import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
+
+@observer
+export class CollectionCalendarView extends CollectionSubView() {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
+ componentDidMount(): void {}
+
+ componentWillUnmount(): void {}
+
+ @computed get allCalendars() {
+ return this.childDocs; // returns a list of docs (i.e. calendars)
+ }
+
+ removeCalendar = () => {};
+
+ addCalendar = (doc: Doc | Doc[], annotationKey?: string | undefined): boolean => {
+ // bring up calendar modal with option to create a calendar
+ return true;
+ };
+
+ _stackRef = React.createRef<CollectionStackingView>();
+
+ panelHeight = () => {
+ return this._props.PanelHeight() - 40; // this should be the height of the stacking view. For now, it's the hieight of the calendar view minus 40 to allow for a title
+ };
+
+ // most recent calendar should come first
+ sortByMostRecentDate = (calendarA: Doc, calendarB: Doc) => {
+ const aDateRangeStr = StrCast(DocListCast(calendarA.data).lastElement()?.date_range);
+ const bDateRangeStr = StrCast(DocListCast(calendarB.data).lastElement()?.date_range);
+
+ const [aFromDate, aToDate] = dateRangeStrToDates(aDateRangeStr);
+ const [bFromDate, bToDate] = dateRangeStrToDates(bDateRangeStr);
+
+ if (aFromDate > bFromDate) {
+ return -1; // a comes first
+ } else if (aFromDate < bFromDate) {
+ return 1; // b comes first
+ } else {
+ // start dates are the same
+ if (aToDate > bToDate) {
+ return -1; // a comes first
+ } else if (aToDate < bToDate) {
+ return 1; // b comes first
+ } else {
+ return 0; // same start and end dates
+ }
+ }
+ };
+
+ screenToLocalTransform = () =>
+ this._props
+ .ScreenToLocalTransform()
+ .translate(Doc.NativeWidth(this._props.Document), 0)
+ .scale(this._props.NativeDimScaling?.() || 1);
+
+ get calendarsKey() {
+ return this._props.fieldKey;
+ }
+
+ render() {
+ return (
+ <div className="collectionCalendarView">
+ <CollectionStackingView
+ {...this._props}
+ setContentView={emptyFunction}
+ ref={this._stackRef}
+ PanelHeight={this.panelHeight}
+ PanelWidth={this._props.PanelWidth}
+ // childFilters={this.childFilters} DO I NEED THIS?
+ sortFunc={this.sortByMostRecentDate}
+ setHeight={undefined}
+ isAnnotationOverlay={false}
+ // select={emptyFunction} What does this mean?
+ isAnyChildContentActive={returnTrue} // ??
+ // childDocumentsActive={}
+ // whenChildContentsActiveChanged={}
+ childHideDecorationTitle={false}
+ removeDocument={this.removeDocument} // will calendar automatically be removed from myCalendars
+ moveDocument={this.moveDocument}
+ addDocument={this.addCalendar}
+ ScreenToLocalTransform={this.screenToLocalTransform}
+ renderDepth={this._props.renderDepth + 1}
+ fieldKey={this.calendarsKey}
+ />
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 0131af6f2..b56973dc6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -212,7 +212,7 @@ export function CollectionSubView<X>(moreProps?: X) {
addDocument = (doc: Doc | Doc[], annotationKey?: string) => this._props.addDocument?.(doc, annotationKey) || false;
removeDocument = (doc: Doc | Doc[], annotationKey?: string) => this._props.removeDocument?.(doc, annotationKey) || false;
- moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this._props.moveDocument?.(doc, targetCollection, addDocument);
+ moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this._props.moveDocument?.(doc, targetCollection, addDocument) || false;
protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
const docDragData = de.complete.docDragData;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 0673b264b..0237ec95e 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -28,6 +28,7 @@ import { CollectionTreeView } from './CollectionTreeView';
import './CollectionView.scss';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import { CollectionGridView } from './collectionGrid/CollectionGridView';
+import { CollectionCalendarView} from './CollectionCalendarView';
import { CollectionLinearView } from './collectionLinear';
import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
@@ -122,6 +123,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
default:
case CollectionViewType.Freeform: return <CollectionFreeFormView key="collview" {...props} />;
case CollectionViewType.Schema: return <CollectionSchemaView key="collview" {...props} />;
+ case CollectionViewType.Calendar: return <CollectionCalendarView key="collview" {...props} />;
case CollectionViewType.Docking: return <CollectionDockingView key="collview" {...props} />;
case CollectionViewType.Tree: return <CollectionTreeView key="collview" {...props} />;
case CollectionViewType.Multicolumn: return <CollectionMulticolumnView key="collview" {...props} />;
@@ -146,6 +148,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
{ description: 'Schema', event: () => func(CollectionViewType.Schema), icon: 'th-list' },
{ description: 'Tree', event: () => func(CollectionViewType.Tree), icon: 'tree' },
{ description: 'Stacking', event: () => (func(CollectionViewType.Stacking)._layout_autoHeight = true), icon: 'ellipsis-v' },
+ { description: 'Calendar', event: () => func(CollectionViewType.Calendar), icon: 'columns'},
{ description: 'Notetaking', event: () => (func(CollectionViewType.NoteTaking)._layout_autoHeight = true), icon: 'ellipsis-v' },
{ description: 'Multicolumn', event: () => func(CollectionViewType.Multicolumn), icon: 'columns' },
{ description: 'Multirow', event: () => func(CollectionViewType.Multirow), icon: 'columns' },
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index e161b4c4c..5b2bf4774 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -14,6 +14,7 @@ import { CollectionDockingView } from '../collections/CollectionDockingView';
import { CollectionView } from '../collections/CollectionView';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { CollectionSchemaView } from '../collections/collectionSchema/CollectionSchemaView';
+import { CollectionCalendarView } from '../collections/CollectionCalendarView';
import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox';
import { PresElementBox } from '../nodes/trails/PresElementBox';
import { SearchBox } from '../search/SearchBox';
@@ -243,6 +244,7 @@ export class DocumentContentsView extends ObservableReactComponent<
CollectionFreeFormView,
CollectionDockingView,
CollectionSchemaView,
+ CollectionCalendarView,
CollectionView,
WebBox,
KeyValueBox,
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index b1fb3368c..1b1b74e7c 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -20,6 +20,7 @@ import { List } from '../../../../fields/List';
import { MarkerIcons } from './MarkerIcons';
import { CirclePicker, ColorResult } from 'react-color';
import { Position } from 'geojson';
+import { CalendarManager } from '../../../util/CalendarManager';
type MapAnchorMenuType = 'standard' | 'routeCreation' | 'calendar' | 'customize' | 'route';
@@ -45,13 +46,15 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public IsTargetToggler: () => boolean = returnFalse;
public DisplayRoute: (routeInfoMap: Record<TransportationType, any> | undefined, type: TransportationType) => void = unimplementedFunction;
- public HideRoute: () => void = unimplementedFunction;
public AddNewRouteToMap: (coordinates: Position[], origin: string, destination: any, createPinForDestination: boolean) => void = unimplementedFunction;
public CreatePin: (feature: any) => void = unimplementedFunction;
public UpdateMarkerColor: (color: string) => void = unimplementedFunction;
public UpdateMarkerIcon: (iconKey: string) => void = unimplementedFunction;
+
+ public Hide: () => void = unimplementedFunction;
+
public OpenAnimationPanel: (routeDoc: Doc | undefined) => void = unimplementedFunction;
@observable
@@ -70,14 +73,31 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
private title: string | undefined = undefined;
- public setPinDoc(pinDoc: Doc) {
- this.pinDoc = pinDoc;
- this.title = StrCast(pinDoc.title ? pinDoc.title : `${NumCast(pinDoc.longitude)}, ${NumCast(pinDoc.latitude)}`);
+ public setPinDoc(pinDoc: Doc | undefined) {
+ if (pinDoc){
+ this.pinDoc = pinDoc;
+ this.title = StrCast(pinDoc.title ? pinDoc.title : `${NumCast(pinDoc.longitude)}, ${NumCast(pinDoc.latitude)}`);
+ }
+
}
- public setRouteDoc(routeDoc: Doc) {
- this.routeDoc = routeDoc;
- this.title = StrCast(routeDoc.title ?? 'Map route');
+ public setRouteDoc(routeDoc: Doc | undefined) {
+ if (routeDoc){
+ this.routeDoc = routeDoc;
+ this.title = StrCast(routeDoc.title ?? 'Map route');
+ }
+ }
+
+ @action
+ public Reset(){
+ this.destinationSelected = false;
+ this.currentRouteInfoMap = undefined;
+ this.destinationFeatures = [];
+ this.selectedDestinationFeature = undefined;
+ this.allMapPinDocs = [];
+ this.title = undefined;
+ this.routeDoc = undefined;
+ this.pinDoc = undefined;
}
public setAllMapboxPins(pinDocs: Doc[]) {
@@ -263,7 +283,6 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
console.log(coordinates);
console.log(this.selectedDestinationFeature);
this.AddNewRouteToMap(coordinates, this.title ?? '', this.selectedDestinationFeature, this.createPinForDestination);
- this.HideRoute();
}
};
@@ -277,86 +296,155 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
return undefined;
};
+ getDirectionsButton: JSX.Element = (
+ <IconButton
+ tooltip="Get directions"
+ onPointerDown={this.DirectionsClick}
+ icon={<FontAwesomeIcon icon={faDiamondTurnRight as IconLookup} />}
+ color={SettingsManager.userColor} />
+ )
+
+ getAddToCalendarButton = (docType: string): JSX.Element => {
+ return (
+ <IconButton
+ tooltip="Add to calendar"
+ onPointerDown={() => {
+ CalendarManager.Instance.open(undefined, docType === 'pin' ? this.pinDoc : this.routeDoc)
+ }}
+ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />}
+ color={SettingsManager.userColor}
+ />
+ )
+
+ }
+ addToCalendarButton: JSX.Element = (
+ <IconButton
+ tooltip="Add to calendar"
+ onPointerDown={() => CalendarManager.Instance.open(undefined, this.pinDoc)}
+ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />}
+ color={SettingsManager.userColor} />
+ )
+
+ getLinkNoteToDocButton = (docType: string): JSX.Element => {
+ return (
+ <div ref={this._commentRef}>
+ <IconButton
+ tooltip={`Link Note to ${docType === 'pin' ? 'Pin' : 'Route'}`} //
+ onPointerDown={this.notePointerDown}
+ icon={<FontAwesomeIcon icon="sticky-note" />}
+ color={SettingsManager.userColor}
+ />
+ </div>
+ )
+ }
+
+ linkNoteToPinOrRoutenButton: JSX.Element = (
+ <div ref={this._commentRef}>
+ <IconButton
+ tooltip="Link Note to Pin" //
+ onPointerDown={this.notePointerDown}
+ icon={<FontAwesomeIcon icon="sticky-note" />}
+ color={SettingsManager.userColor}
+ />
+ </div>
+ )
+
+ customizePinButton: JSX.Element = (
+ <IconButton
+ tooltip="Customize pin"
+ onPointerDown={this.CustomizeClick}
+ icon={<FontAwesomeIcon icon={faEdit as IconLookup} />}
+ color={SettingsManager.userColor}
+ />
+ )
+
+ centerOnPinButton: JSX.Element = (
+ <IconButton
+ tooltip="Center on pin" //
+ onPointerDown={this.Center}
+ icon={<FontAwesomeIcon icon="compress-arrows-alt" />}
+ color={SettingsManager.userColor}
+ />
+ )
+
+ backButton: JSX.Element = (
+ <IconButton
+ tooltip="Go back" //
+ onPointerDown={this.BackClick}
+ icon={<FontAwesomeIcon icon={faArrowLeft as IconLookup} />}
+ color={SettingsManager.userColor}
+ />
+ )
+
+ addRouteButton: JSX.Element = (
+ <IconButton
+ tooltip="Add route" //
+ onPointerDown={this.HandleAddRouteClick}
+ icon={<FontAwesomeIcon icon={faAdd as IconLookup} />}
+ color={SettingsManager.userColor}
+ />
+ )
+
+ getDeleteButton = (type: string) => {
+ return (
+ <IconButton
+ tooltip={`Delete ${type === 'pin' ? 'Pin' : 'Route'}`} //
+ onPointerDown={this.Delete}
+ icon={<FontAwesomeIcon icon="trash-alt" />}
+ color={SettingsManager.userColor}
+ />
+ )
+ }
+
+ animateRouteButton: JSX.Element = (
+ <IconButton
+ tooltip="Animate route"
+ onPointerDown={() => this.OpenAnimationPanel(this.routeDoc)}
+ icon={<FontAwesomeIcon icon={faRoute as IconLookup} />}
+ color={SettingsManager.userColor}
+ />
+ )
+
+ revertToOriginalMarkerButton = (
+ <IconButton
+ tooltip="Revert to original" //
+ onPointerDown={() => this.revertToOriginalMarker()}
+ icon={<FontAwesomeIcon icon={faArrowsRotate as IconLookup} />}
+ color={SettingsManager.userColor}
+ />
+ )
+
render() {
const buttons = (
<div className="menu-buttons" style={{ display: 'flex' }}>
{this.menuType === 'standard' && (
<>
- <IconButton
- tooltip="Delete Pin" //
- onPointerDown={this.Delete}
- icon={<FontAwesomeIcon icon="trash-alt" />}
- color={SettingsManager.userColor}
- />
- <IconButton tooltip="Get directions" onPointerDown={this.DirectionsClick} /**TODO: fix */ icon={<FontAwesomeIcon icon={faDiamondTurnRight as IconLookup} />} color={SettingsManager.userColor} />
- <IconButton tooltip="Add to calendar" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
- <div ref={this._commentRef}>
- <IconButton
- tooltip="Link Note to Pin" //
- onPointerDown={this.notePointerDown}
- icon={<FontAwesomeIcon icon="sticky-note" />}
- color={SettingsManager.userColor}
- />
- </div>
- <IconButton tooltip="Customize pin" onPointerDown={this.CustomizeClick} icon={<FontAwesomeIcon icon={faEdit as IconLookup} />} color={SettingsManager.userColor} />
- <IconButton
- tooltip="Center on pin" //
- onPointerDown={this.Center}
- icon={<FontAwesomeIcon icon="compress-arrows-alt" />}
- color={SettingsManager.userColor}
- />
+ {this.getDeleteButton('pin')}
+ {this.getDirectionsButton}
+ {this.getAddToCalendarButton('pin')}
+ {this.getLinkNoteToDocButton('pin')}
+ {this.customizePinButton}
+ {this.centerOnPinButton}
</>
)}
{this.menuType === 'routeCreation' && (
<>
- <IconButton
- tooltip="Go back" //
- onPointerDown={this.BackClick}
- icon={<FontAwesomeIcon icon={faArrowLeft as IconLookup} />}
- color={SettingsManager.userColor}
- />
- <IconButton
- tooltip="Add route" //
- onPointerDown={this.HandleAddRouteClick}
- icon={<FontAwesomeIcon icon={faAdd as IconLookup} />}
- color={SettingsManager.userColor}
- />
+ {this.backButton}
+ {this.addRouteButton}
</>
)}
{this.menuType === 'route' && (
<>
- <IconButton
- tooltip="Delete Route" //
- onPointerDown={this.Delete}
- icon={<FontAwesomeIcon icon="trash-alt" />}
- color={SettingsManager.userColor}
- />
- <IconButton tooltip="Animate route" onPointerDown={() => this.OpenAnimationPanel(this.routeDoc)} /**TODO: fix */ icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />
- <div ref={this._commentRef}>
- <IconButton
- tooltip="Link Note to Pin" //
- onPointerDown={this.notePointerDown}
- icon={<FontAwesomeIcon icon="sticky-note" />}
- color={SettingsManager.userColor}
- />
- </div>
- <IconButton tooltip="Add to calendar" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
+ {this.getDeleteButton('route')}
+ {this.animateRouteButton}
+ {this.getAddToCalendarButton('route')}
+ {this.getLinkNoteToDocButton('route')}
</>
)}
{this.menuType === 'customize' && (
<>
- <IconButton
- tooltip="Go back" //
- onPointerDown={this.BackClick}
- icon={<FontAwesomeIcon icon={faArrowLeft as IconLookup} />}
- color={SettingsManager.userColor}
- />
- <IconButton
- tooltip="Revert to original" //
- onPointerDown={() => this.revertToOriginalMarker()}
- icon={<FontAwesomeIcon icon={faArrowsRotate as IconLookup} />}
- color={SettingsManager.userColor}
- />
+ {this.backButton}
+ {this.revertToOriginalMarkerButton}
</>
)}
@@ -373,12 +461,6 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
)} */}
</div>
);
- // return (
- // <div ref={MapAnchorMenu.top} style={{zIndex: 30000, width: '100%', height: '100px'}}>
- // HELLO THIS IS ANCHOR MENU
- // {this.getElement(buttons)}
- // </div>
- // )
return this.getElement(
<div ref={MapAnchorMenu.top} className="map-anchor-menu-container">
diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss
index b3ce55786..25b4587a5 100644
--- a/src/client/views/nodes/MapBox/MapBox.scss
+++ b/src/client/views/nodes/MapBox/MapBox.scss
@@ -109,20 +109,15 @@
display: flex;
justify-content: flex-start;
align-items: center;
- gap: 7px;
+ width: 100%;
.animation-suboptions {
display: flex;
justify-content: flex-start;
- flex-wrap: wrap;
align-items: center;
gap: 7px;
width: 100%;
- .first-person-label {
- width: '130px' !important;
- }
-
label {
margin-bottom: 0;
}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index b627ba459..562cb63d1 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -23,9 +23,7 @@ import { FieldView, FieldViewProps } from '../FieldView';
import { FormattedTextBox } from '../formattedText/FormattedTextBox';
import { PinProps, PresBox } from '../trails';
import { MapAnchorMenu } from './MapAnchorMenu';
-
import { ControlPosition, Layer, MapProvider, MapRef, Map as MapboxMap, Marker, MarkerProps, Source, ViewState, ViewStateChangeEvent } from 'react-map-gl';
-import MapboxGeocoder, { GeocoderOptions } from '@mapbox/mapbox-gl-geocoder!';
import './MapBox.scss';
// import { GeocoderControl } from './GeocoderControl';
import { IconLookup, faCircleXmark, faGear, faPause, faPlay, faRotate } from '@fortawesome/free-solid-svg-icons';
@@ -33,7 +31,7 @@ import { Checkbox, FormControlLabel, TextField } from '@mui/material';
import * as turf from '@turf/turf';
import * as d3 from 'd3';
import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, Position } from 'geojson';
-import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent } from 'mapbox-gl!';
+import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent } from '!mapbox-gl';
import { CirclePicker, ColorResult } from 'react-color';
import { MarkerEvent } from 'react-map-gl/dist/esm/types';
import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
@@ -68,13 +66,13 @@ type PopupInfo = {
description: string;
};
-export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & {
- mapboxAccessToken: string;
- marker?: Omit<MarkerProps, 'longitude' | 'latitude'>;
- position: ControlPosition;
+// export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & {
+// mapboxAccessToken: string;
+// marker?: Omit<MarkerProps, 'longitude' | 'latitude'>;
+// position: ControlPosition;
- onResult: (...args: any[]) => void;
-};
+// onResult: (...args: any[]) => void;
+// };
type MapMarker = {
longitude: number;
@@ -652,6 +650,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
deleteSelectedPinOrRoute = undoable(() => {
+ console.log('deleting')
if (this.selectedPinOrRoute) {
// Removes filter
Doc.setDocFilter(this.Document, 'latitude', this.selectedPinOrRoute.latitude, 'remove');
@@ -674,9 +673,19 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
e.stopPropagation();
e.preventDefault();
MapAnchorMenu.Instance.fadeOut(true);
+ runInAction(() => {
+ this.temporaryRouteSource = {
+ type: 'FeatureCollection',
+ features: [],
+ }
+ })
+
+
document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
};
+
+
@action
centerOnSelectedPin = () => {
if (this.selectedPinOrRoute) {
@@ -871,6 +880,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (createPinForDestination) {
this.createPushpin(destination.center[1], destination.center[0], destination.place_name);
}
+ this.temporaryRouteSource = {
+ type: 'FeatureCollection',
+ features: []
+ }
+ MapAnchorMenu.Instance.fadeOut(true);
return mapRoute;
}
// TODO: Display error that can't create route to same location
@@ -933,6 +947,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
handleMapClick = (e: MapLayerMouseEvent) => {
this.featuresFromGeocodeResults = [];
+ this.settingsOpen = false;
if (this._mapRef.current) {
const features = this._mapRef.current.queryRenderedFeatures(e.point, {
layers: ['map-routes-layer'],
@@ -955,13 +970,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
+ MapAnchorMenu.Instance.Reset();
+
MapAnchorMenu.Instance.setRouteDoc(routeDoc);
// TODO: Subject to change
MapAnchorMenu.Instance.setAllMapboxPins(this.allAnnotations.filter(anno => !anno.layout_unrendered));
MapAnchorMenu.Instance.DisplayRoute = this.displayRoute;
- MapAnchorMenu.Instance.HideRoute = this.hideRoute;
MapAnchorMenu.Instance.AddNewRouteToMap = this.createMapRoute;
MapAnchorMenu.Instance.CreatePin = this.addMarkerForFeature;
MapAnchorMenu.Instance.OpenAnimationPanel = this.openAnimationPanel;
@@ -978,6 +994,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
};
+
+
/**
* Makes a reverse geocoding API call to retrieve features corresponding to a map click (based on longitude
* and latitude). Sets the search results accordingly.
@@ -1033,12 +1051,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
+ MapAnchorMenu.Instance.Reset();
+
// pass in the pinDoc
MapAnchorMenu.Instance.setPinDoc(pinDoc);
MapAnchorMenu.Instance.setAllMapboxPins(this.allAnnotations.filter(anno => !anno.layout_unrendered));
MapAnchorMenu.Instance.DisplayRoute = this.displayRoute;
- MapAnchorMenu.Instance.HideRoute = this.hideRoute;
MapAnchorMenu.Instance.AddNewRouteToMap = this.createMapRoute;
MapAnchorMenu.Instance.CreatePin = this.addMarkerForFeature;
@@ -1369,8 +1388,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
};
- @action
- exportAnimationToVideo = () => {};
getRouteAnimationOptions = (): JSX.Element => {
return (
@@ -1408,7 +1425,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
<div>|</div>
<FormControlLabel
className="first-person-label"
- style={{ width: '130px' }}
label="1st person animation:"
labelPlacement="start"
control={<Checkbox color="success" checked={this.isStreetViewAnimation} onChange={this.toggleIsStreetViewAnimation} />}
@@ -1416,8 +1432,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
<div id="divider">|</div>
<IconButton tooltip={this.animationSpeedTooltipText} onPointerDown={this.updateAnimationSpeed} icon={this.animationSpeedIcon} size={Size.MEDIUM} />
<div id="divider">|</div>
- <div style={{ width: '230px' }}>Select Line Color: </div>
- <CirclePicker circleSize={12} circleSpacing={5} width="100%" colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']} onChange={color => this.setAnimationLineColor(color)} />
+ <div style={{display: 'flex', alignItems: 'center'}}>
+ <div>Select Line Color: </div>
+ <CirclePicker circleSize={12} circleSpacing={5} width="100%" colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']} onChange={color => this.setAnimationLineColor(color)} />
+ </div>
+
</div>
</>
</>
@@ -1451,7 +1470,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
changeMapStyle = (e: React.ChangeEvent<HTMLSelectElement>) => {
- this.dataDoc.map_style = `mapbox://styles/mapbox/${e.target.value}`;
+ this.dataDoc.map_style = e.target.value;
// this.mapStyle = `mapbox://styles/mapbox/${e.target.value}`
};
@@ -1459,6 +1478,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
onBearingChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const bearing = parseInt(e.target.value);
if (!isNaN(bearing) && this._mapRef.current) {
+ console.log('bearing change')
const fixedBearing = Math.max(0, Math.min(360, bearing));
this._mapRef.current.setBearing(fixedBearing);
this.dataDoc.map_bearing = fixedBearing;
@@ -1469,6 +1489,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
onPitchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const pitch = parseInt(e.target.value);
if (!isNaN(pitch) && this._mapRef.current) {
+ console.log('pitch change')
const fixedPitch = Math.max(0, Math.min(85, pitch));
this._mapRef.current.setPitch(fixedPitch);
this.dataDoc.map_pitch = fixedPitch;
@@ -1566,29 +1587,29 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
<div className="mapbox-style-select">
<div>Map Style:</div>
<div>
- <select onChange={this.changeMapStyle}>
- <option value="streets-v11">Streets</option>
- <option value="outdoors-v12">Outdoors</option>
- <option value="light-v11">Light</option>
- <option value="dark-v11">Dark</option>
- <option value="satellite-v9">Satellite</option>
- <option value="satellite-streets-v12">Satellite Streets</option>
- <option value="navigation-day-v1">Navigation Day</option>
- <option value="navigation-night-v1">Navigation Night</option>
+ <select onChange={this.changeMapStyle} value={StrCast(this.dataDoc.map_style)}>
+ <option value="mapbox://styles/mapbox/streets-v11">Streets</option>
+ <option value="mapbox://styles/mapbox/outdoors-v12">Outdoors</option>
+ <option value="mapbox://styles/mapbox/light-v11">Light</option>
+ <option value="mapbox://styles/mapbox/dark-v11">Dark</option>
+ <option value="mapbox://styles/mapbox/satellite-v9">Satellite</option>
+ <option value="mapbox://styles/mapbox/satellite-streets-v12">Satellite Streets</option>
+ <option value="mapbox://styles/mapbox/navigation-day-v1">Navigation Day</option>
+ <option value="mapbox://styles/mapbox/navigation-night-v1">Navigation Night</option>
</select>
</div>
</div>
<div className="mapbox-bearing-selection">
<div>Bearing: </div>
- <input value={NumCast(this.mapboxMapViewState.bearing).toFixed(2)} type="number" onChange={this.onBearingChange} />
+ <input value={NumCast(this.mapboxMapViewState.bearing).toFixed(0)} type="number" onChange={this.onBearingChange} />
</div>
<div className="mapbox-pitch-selection">
<div>Pitch: </div>
- <input value={NumCast(this.mapboxMapViewState.pitch).toFixed(2)} type="number" onChange={this.onPitchChange} />
+ <input value={NumCast(this.mapboxMapViewState.pitch).toFixed(0)} type="number" onChange={this.onPitchChange} />
</div>
<div className="mapbox-pitch-selection">
<div>Zoom: </div>
- <input value={NumCast(this.mapboxMapViewState.zoom).toFixed(2)} type="number" onChange={this.onZoomChange} />
+ <input value={NumCast(this.mapboxMapViewState.zoom).toFixed(0)} type="number" onChange={this.onZoomChange} />
</div>
<div className="mapbox-terrain-selection">
<div>Show terrain: </div>
@@ -1622,26 +1643,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
</React.Fragment>
</div>
)}
- {/* <div className='zoom-box'>
- <IconButton // increment
- style={{borderBottom: '1px', borderBottomColor: 'white'}}
- onPointerDown={() => this.onStepZoomChange(true)}
- icon={<FontAwesomeIcon icon={faPlus as IconLookup} color='black'/>}
- size={Size.SMALL}
- color={SettingsManager.userColor}
- />
- <IconButton // decrement
- onPointerDown={() => this.onStepZoomChange(false)}
- icon={<FontAwesomeIcon icon={faMinus as IconLookup} color='black'/>}
- size={Size.SMALL}
- color={SettingsManager.userColor}
- />
- </div> */}
<MapProvider>
<MapboxMap
ref={this._mapRef}
mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
- viewState={this.isAnimating ? undefined : { ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width), height: NumCast(this.layoutDoc._height) }}
+ viewState={(this.isAnimating || this.routeToAnimate) ? undefined : { ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width), height: NumCast(this.layoutDoc._height) }}
mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'}
style={{
position: 'absolute',
@@ -1740,59 +1746,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
))} */}
</MapboxMap>
</MapProvider>
-
- {/* <BingMapsReact
- onMapReady={this.bingMapReady} //
- bingMapsKey={bingApiKey}
- height="100%"
- mapOptions={this.bingMapOptions}
- width="100%"
- viewOptions={this.bingViewOptions}
- /> */}
- {/* <div>
- {!this._mapReady
- ? null
- : this.allAnnotations
- .filter(anno => !anno.layout_unrendered)
- .map((pushpin, i) => (
- <DocumentView
- key={i}
- {...this._props}
- renderDepth={this._props.renderDepth + 1}
- Document={pushpin}
- 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}
- /> */}
</div>
- {/* </LoadScript > */}
<div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
<SidebarAnnos
ref={this._sidebarRef}
@@ -1816,54 +1770,3 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
);
}
}
-
-{
- /* <Autocomplete
- fullWidth
- id="map-location-searcher"
- freeSolo
- onInputChange={(e, searchText) => this.handleSearchChange(searchText)}
- onChange={(e, selectedOption) => {
- this.handleSearchChange(""); // clear input
- this.addMarkerForFeature(selectedOption);
- }}
- options={this.featuresFromGeocodeResults
- .filter(feature => feature.place_name)
- .map(feature => feature)}
- getOptionLabel={(feature) => feature.place_name}
- renderInput={(params) => (
- <TextField
- {...params}
- placeholder='Enter a location'
- />
- )}
- /> */
-}
-{
- /* <EditableText
- // editing
- setVal={(newText: string | number) => typeof newText === 'string' && this.handleSearchChange(newText)}
- // onEnter={e => this.bingSearch()}
- onEnter={e => {}}
- height={32}
- // placeholder={this.bingSearchBarContents || 'Enter a location'}
- placeholder='Enter a location'
- 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> */
-}
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
new file mode 100644
index 000000000..989feb774
--- /dev/null
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -0,0 +1,134 @@
+import * as React from 'react';
+import { observer } from "mobx-react";
+import { Doc } from "../../../../fields/Doc";
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps, ViewBoxBaseComponent } from '../../DocComponent';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { StrCast } from '../../../../fields/Types';
+import { makeObservable } from 'mobx';
+import { dateRangeStrToDates } from '../../../../Utils';
+import { Calendar, EventClickArg, EventSourceInput } from '@fullcalendar/core'
+import dayGridPlugin from '@fullcalendar/daygrid'
+import multiMonthPlugin from '@fullcalendar/multimonth'
+import { faListNumeric } from '@fortawesome/free-solid-svg-icons';
+
+type CalendarView = 'month' | 'multi-month' | 'week';
+
+@observer
+export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>(){
+ public static LayoutString(fieldKey: string = 'calendar') {
+ return FieldView.LayoutString(CalendarBox, fieldKey);
+ }
+
+ componentDidMount(): void {
+
+ }
+
+ componentWillUnmount(): void {
+
+ }
+
+ _calendarRef = React.createRef<HTMLElement>()
+
+ get dateRangeStr (){
+ return StrCast(this.Document.date_range);
+ }
+
+ // Choose a calendar view based on the date range
+ get calendarViewType (): CalendarView {
+ const [fromDate, toDate] = dateRangeStrToDates(this.dateRangeStr);
+
+ if (fromDate.getFullYear() !== toDate.getFullYear() || fromDate.getMonth() !== toDate.getMonth()) return 'multi-month';
+
+ if (Math.abs(fromDate.getDay() - toDate.getDay()) > 7) return 'month';
+ return 'week';
+ }
+
+ get calendarStartDate () {
+ return this.dateRangeStr.split("|")[0];
+ }
+
+ get calendarToDate () {
+ return this.dateRangeStr.split("|")[1];
+ }
+
+ get childDocs (): Doc[] {
+ return this.childDocs; // get all sub docs for a calendar
+ }
+
+ docBackgroundColor (type: string): string {
+ // TODO: Return a different color based on the event type
+ return 'blue';
+ }
+
+ get calendarEvents (): EventSourceInput | undefined {
+ if (this.childDocs.length === 0) return undefined;
+ return this.childDocs.map((doc, idx) => {
+ const docTitle = StrCast(doc.title);
+ const docDateRange = StrCast(doc.date_range);
+ const [startDate, endDate] = dateRangeStrToDates(docDateRange);
+ const docType = doc.type;
+ const docDescription = doc.description ? StrCast(doc.description): "";
+
+ return {
+ title: docTitle,
+ start: startDate,
+ end: endDate,
+ allDay: false,
+ classNames:[StrCast(docType)], // will determine the style
+ editable: false, // subject to change in the future
+ backgroundColor: this.docBackgroundColor(StrCast(doc.type)),
+ color: 'white',
+ extendedProps: {
+ description: docDescription
+ },
+
+ }
+
+ })
+ }
+
+ handleEventClick = (arg: EventClickArg) => {
+ // TODO: open popover with event description, option to open CalendarManager and change event date, delete event, etc.
+ }
+
+ calendarEl: HTMLElement = document.getElementById('calendar-box-v1')!;
+
+ // https://fullcalendar.io
+ get calendar() {
+ return new Calendar(this.calendarEl,
+ {
+ plugins: [this.calendarViewType === 'multi-month' ? multiMonthPlugin : dayGridPlugin],
+ headerToolbar: {
+ left: 'prev,next today',
+ center: 'title',
+ right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
+ },
+ initialDate: this.calendarStartDate,
+ navLinks: true,
+ editable: false,
+ displayEventTime: false,
+ displayEventEnd: false,
+ events: this.calendarEvents,
+ eventClick: this.handleEventClick
+ }
+ )
+
+ }
+
+
+ constructor(props: any){
+ super(props);
+ makeObservable(this);
+ }
+
+ render(){
+ return (
+ <div className='calendar-box-conatiner'>
+ <div id='calendar-box-v1'>
+
+ </div>
+ </div>
+ );
+
+ }
+} \ No newline at end of file