aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorzaultavangar <zaul_tavangar@brown.edu>2023-12-16 17:26:32 -0500
committerzaultavangar <zaul_tavangar@brown.edu>2023-12-16 17:26:32 -0500
commit4a1d64e470f7f8fae90e5014f3e6e64c5ffea2c8 (patch)
treec9be41dc96a377e7e72d41e017c490bfba49a9f7 /src
parentddf35f6b406a2f2e8c27c2c65d15206eaa3ddbe6 (diff)
more functionality for calendar feature
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/DocumentTypes.ts2
-rw-r--r--src/client/documents/Documents.ts11
-rw-r--r--src/client/util/CalendarManager.tsx153
-rw-r--r--src/client/views/DashboardView.tsx31
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx1
-rw-r--r--src/fields/Doc.ts1
6 files changed, 175 insertions, 24 deletions
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 306e9e14b..5b9f71641 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -38,6 +38,7 @@ export enum DocumentType {
GROUP = 'group',
PUSHPIN = 'pushpin',
MAPROUTE = 'maproute',
+ CALENDAR = 'calendar',
SCRIPTDB = 'scriptdb', // database of scripts
GROUPDB = 'groupdb', // database of groups
@@ -62,4 +63,5 @@ export enum CollectionViewType {
Pile = 'pileup',
StackedTimeline = 'stacked timeline',
NoteTaking = 'notetaking',
+ Calendar = 'calendar_view'
}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f5f140ae9..778dbe5b8 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -186,6 +186,8 @@ export class DocumentOptions {
map_bearing?: NUMt = new NumInfo('bearing of a map view', false);
map_style?: STRt = new StrInfo('mapbox style for a map view', false);
+ date_range?: STRt = new StrInfo('date range for calendar', false);
+
wikiData?: STRt = new StrInfo('WikiData ID related to map location');
description?: STRt = new StrInfo('A description of the document');
_timecodeToShow?: NUMt = new NumInfo('the time that a document should be displayed (e.g., when an annotation shows up as a video plays)', false);
@@ -1039,6 +1041,7 @@ export namespace Docs {
export function LoadingDocument(file: File | string, options: DocumentOptions) {
return InstanceFromProto(Prototypes.get(DocumentType.LOADING), undefined, { _height: 150, _width: 200, title: typeof file == 'string' ? file : file.name, ...options }, undefined, '');
}
+
export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') {
return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey);
@@ -1160,6 +1163,10 @@ 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)
+ }
+
// 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 });
@@ -1214,6 +1221,10 @@ export namespace Docs {
return doc;
}
+ export function CalendarCollectionDocument(documents: Array<Doc>, options: DocumentOptions){
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), {...options, _type_collection: CollectionViewType.Calendar});
+ }
+
export function StackingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string, protoId?: string) {
return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Stacking }, id, undefined, protoId);
}
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index 39ba41652..e471db7c6 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import './CalendarManager.scss';
import { observer } from "mobx-react";
import { action, computed, observable, runInAction } from 'mobx';
-import { Doc } from '../../fields/Doc';
+import { Doc, DocListCast } from '../../fields/Doc';
import { DocumentView } from '../views/nodes/DocumentView';
import { DictationOverlay } from '../views/DictationOverlay';
import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
@@ -10,19 +10,38 @@ import { MainViewModal } from '../views/MainViewModal';
import { TextField } from '@material-ui/core';
import Select from 'react-select';
import { SettingsManager } from './SettingsManager';
-import { StrCast } from '../../fields/Types';
+import { 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 { DateRange, Range, RangeKeyDict } from 'react-date-range';
import { Button } from 'browndash-components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import {DateRangePicker} from '@adobe/react-spectrum';
import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons';
-import 'react-date-range/dist/styles.css';
-import 'react-date-range/dist/theme/default.css';
+import { Docs } from '../documents/Documents';
+// import 'react-date-range/dist/styles.css';
+// import 'react-date-range/dist/theme/default.css';
type CreationType = 'new-calendar' | 'existing-calendar' | 'manage-calendars';
+interface CalendarSelectOptions {
+ label: string,
+ value: string
+}
+
+const formatDateToString = (date: Date) => {
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+
+ return `${year}-${month}-${day}`;
+}
+
+// TODO: If doc is already part of a calendar, display that
+// TODO: For a doc already in a calendar: give option to edit date range, delete from calendar
+
+
@observer
export class CalendarManager extends React.Component<{}> {
public static Instance: CalendarManager;
@@ -36,11 +55,28 @@ export class CalendarManager extends React.Component<{}> {
@observable private creationType: CreationType = 'new-calendar';
+ @observable private existingCalendars: Doc[] = DocListCast(Doc.MyCalendars.data);
+
+ @computed get selectOptions() {
+ return this.existingCalendars.map(calendar => ({ label: StrCast(calendar.title), value: StrCast(calendar.title) }));
+ }
+
+ @observable
+ selectedExistingCalendarOption: CalendarSelectOptions | null = null;
+
@observable
calendarName: string = "";
+ @observable
+ calendarDescription: string = "";
+
+ @observable
+ errorMessage: string = "";
+
@action
setInterationType = (type: CreationType) => {
+ this.errorMessage = "";
+ this.calendarName = "";
this.creationType = type;
}
@@ -76,6 +112,60 @@ export class CalendarManager extends React.Component<{}> {
}
+ @action
+ handleSelectChange = (option: any) => {
+ let selectOpt = option as CalendarSelectOptions;
+ this.selectedExistingCalendarOption = selectOpt;
+ this.calendarName = selectOpt.value; // or label
+ }
+
+ @action
+ handleTextFieldChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
+ this.calendarName = event.target.value;
+ }
+
+ // TODO: Make undoable
+ private addToCalendar = () => {
+ let docs = SelectionManager.Views().length < 2 ? [this.targetDoc] : SelectionManager.Views().map(docView => docView.rootDoc);
+ const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; // doc to add to calendar
+
+ if (targetDoc) {
+ let calendar: Doc;
+ if (this.creationType === 'new-calendar'){
+ if (!this.existingCalendars.find(doc => StrCast(doc.title) === this.calendarName)){
+ calendar = Docs.Create.CalendarDocument({
+ title: this.calendarName,
+ description: this.calendarDescription
+ }, []);
+ } else {
+ this.errorMessage = "Calendar with this name already exists"
+ return;
+ }
+ } else {
+ // find existing calendar based on selected name (should technically always find one)
+ const existingCalendar = this.existingCalendars.find(calendar => StrCast(calendar.title) === this.calendarName);
+ if (existingCalendar) calendar = existingCalendar;
+ else {
+ this.errorMessage = "Must select an existing calendar";
+ return;
+ }
+ }
+ // Get start and end date strings
+ const startDateStr = formatDateToString(this.selectedDateRange.start);
+ const endDateStr = formatDateToString(this.selectedDateRange.end);
+
+ const subDocEmbedding = Doc.MakeEmbedding(targetDoc); // embedding
+ subDocEmbedding.embedContainer = calendar; // set embed container
+ subDocEmbedding.date_range = `${startDateStr}-${endDateStr}`; // set subDoc date range
+
+ Doc.AddDocToList(calendar, 'data', subDocEmbedding); // add embedded subDoc to calendar
+
+ Doc.AddDocToList(Doc.MyCalendars, 'data', calendar); // add to dashboard calendars
+
+ }
+
+ }
+
private focusOn = (contents: string) => {
const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
const docs = SelectionManager.Views().length > 1 ? SelectionManager.Views().map(docView => docView.props.Document) : [this.targetDoc];
@@ -108,20 +198,19 @@ export class CalendarManager extends React.Component<{}> {
};
@observable
- selectedDateRange: Range[] = [{
- startDate: new Date(),
- endDate: undefined,
- key: 'selection'
+ selectedDateRange: any = [{
+ start: new Date(),
+ end: new Date(),
}]
@action
- setSelectedDateRange = (range: Range[]) => {
+ setSelectedDateRange = (range: any) => {
this.selectedDateRange = range;
}
@computed
get createButtonActive() {
- if (this.calendarName.length === 0) return false // disabled if no calendar name
+ if (this.calendarName.length === 0 || this.errorMessage.length > 0) return false // disabled if no calendar name
let startDate: Date | undefined;
let endDate: Date | undefined;
try {
@@ -170,6 +259,7 @@ export class CalendarManager extends React.Component<{}> {
{this.creationType === 'new-calendar' ?
<TextField
fullWidth
+ onChange={this.handleTextFieldChange}
placeholder='Enter calendar name...'
variant='filled'
style={{
@@ -178,38 +268,55 @@ export class CalendarManager extends React.Component<{}> {
borderRadius: '5px'
}}
-
/>
:
<Select
className='existing-calendar-search'
placeholder='Search for existing calendar...'
- isMulti
+ isClearable
isSearchable
+ options={this.selectOptions}
+ value={this.selectedExistingCalendarOption}
+ onChange={this.handleSelectChange}
+ styles={{
+ control: () => ({
+ display: 'inline-flex',
+ width: '100%',
+ }),
+ indicatorSeparator: () => ({
+ display: 'inline-flex',
+ visibility: 'hidden',
+ }),
+ indicatorsContainer: () => ({
+ display: 'inline-flex',
+ textDecorationColor: 'black',
+ }),
+ valueContainer: () => ({
+ display: 'inline-flex',
+ fontStyle: StrCast(Doc.UserDoc().userColor),
+ color: StrCast(Doc.UserDoc().userColor),
+ width: '100%',
+ }),
+ }}
>
-
</Select>
}
</div>
<div className='date-range-picker-container'>
- <DateRange
- className='react-date-range'
- editableDateInputs={true}
- // ranges={[selectionRange]}
- onChange={item => this.setSelectedDateRange([item.selection])}
- ranges={this.selectedDateRange}
- // onChange={this.handleSelect}
- />
+ <DateRangePicker
+ value={this.selectedDateRange}
+ onChange={(v => this.setSelectedDateRange(v))}
+ label="Date range" />
</div>
<div className='create-button-container'>
<Button
active={this.createButtonActive}
+ onClick={() => {}}
text="Add to Calendar"
iconPlacement='right'
icon={<FontAwesomeIcon icon={faPlus as IconLookup}/>}
/>
</div>
-
</div>
)
}
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 2765e95e6..3c8221f6a 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -379,13 +379,42 @@ export class DashboardView extends React.Component {
Doc.AddDocToList(Doc.MyDashboards, 'data', dashboardDoc);
DashboardView.SetupDashboardTrails(dashboardDoc);
-
+ DashboardView.SetupDashboardCalendars(dashboardDoc);
// open this new dashboard
Doc.ActiveDashboard = dashboardDoc;
Doc.ActivePage = 'dashboard';
Doc.ActivePresentation = undefined;
};
+ public static SetupDashboardCalendars(dashboardDoc: Doc){
+ // this section is creating the button document itself === myTrails = new Button
+
+ // create a a list of calendars (as a CalendarCollectionDocument) and store it on the new dashboard
+ const reqdOpts: DocumentOptions = {
+ title: 'My Calendars',
+ _layout_showTitle: 'title',
+ _height: 100,
+ treeView_HideTitle: true,
+ _layout_fitWidth: true,
+ _gridGap: 5,
+ _forceActive: true,
+ childDragAction: 'embed',
+ treeView_TruncateTitleWidth: 150,
+ ignoreClick: true,
+ contextMenuIcons: new List<string>(['plus']),
+ contextMenuLabels: new List<string>(['Create New Calendar']),
+ _lockedPosition: true,
+ layout_boxShadow: '0 0',
+ childDontRegisterViews: true,
+ dropAction: 'same',
+ isSystem: true,
+ layout_explainer: 'All of the calendars that you have created will appear here.',
+ };
+ const myCalendars = DocUtils.AssignScripts(Docs.Create.CalendarCollectionDocument([], reqdOpts));
+ // { treeView_ChildDoubleClick: 'openPresentation(documentView.rootDoc)' }
+ dashboardDoc.myCalendars = new PrefetchProxy(myCalendars);
+ }
+
public static SetupDashboardTrails(dashboardDoc: Doc) {
// this section is creating the button document itself === myTrails = new Button
const reqdBtnOpts: DocumentOptions = {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 2e8047309..f479b9b11 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -465,6 +465,7 @@ export class CollectionDockingView extends CollectionSubView() {
});
const copy = Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) });
DashboardView.SetupDashboardTrails(copy);
+ DashboardView.SetupDashboardCalendars(copy); // Zaul TODO: needed?
return DashboardView.openDashboard(copy);
}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 1678d9012..cfbef2d21 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -147,6 +147,7 @@ export class Doc extends RefField {
public static get MyTopBarBtns() { return DocCast(Doc.UserDoc().myTopBarBtns); } // prettier-ignore
public static get MyRecentlyClosed() { return DocCast(Doc.UserDoc().myRecentlyClosed); } // prettier-ignore
public static get MyTrails() { return DocCast(Doc.ActiveDashboard?.myTrails); } // prettier-ignore
+ public static get MyCalendars() { return DocCast(Doc.ActiveDashboard?.myCalendars); } // prettier-ignore
public static get MyOverlayDocs() { return DocListCast(Doc.ActiveDashboard?.myOverlayDocs ?? DocCast(Doc.UserDoc().myOverlayDocs)?.data); } // prettier-ignore
public static get MyPublishedDocs() { return DocListCast(Doc.ActiveDashboard?.myPublishedDocs ?? DocCast(Doc.UserDoc().myPublishedDocs)?.data); } // prettier-ignore
public static get MyDashboards() { return DocCast(Doc.UserDoc().myDashboards); } // prettier-ignore