diff options
author | zaultavangar <zaul_tavangar@brown.edu> | 2023-12-16 17:26:32 -0500 |
---|---|---|
committer | zaultavangar <zaul_tavangar@brown.edu> | 2023-12-16 17:26:32 -0500 |
commit | 4a1d64e470f7f8fae90e5014f3e6e64c5ffea2c8 (patch) | |
tree | c9be41dc96a377e7e72d41e017c490bfba49a9f7 /src | |
parent | ddf35f6b406a2f2e8c27c2c65d15206eaa3ddbe6 (diff) |
more functionality for calendar feature
Diffstat (limited to 'src')
-rw-r--r-- | src/client/documents/DocumentTypes.ts | 2 | ||||
-rw-r--r-- | src/client/documents/Documents.ts | 11 | ||||
-rw-r--r-- | src/client/util/CalendarManager.tsx | 153 | ||||
-rw-r--r-- | src/client/views/DashboardView.tsx | 31 | ||||
-rw-r--r-- | src/client/views/collections/CollectionDockingView.tsx | 1 | ||||
-rw-r--r-- | src/fields/Doc.ts | 1 |
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 |