diff options
-rw-r--r-- | src/client/apis/GoogleAuthenticationManager.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/TaskBox.tsx | 40 | ||||
-rw-r--r-- | src/client/views/nodes/calendarBox/CalendarBox.scss | 9 | ||||
-rw-r--r-- | src/client/views/nodes/calendarBox/CalendarBox.tsx | 99 | ||||
-rw-r--r-- | src/server/ApiManagers/GeneralGoogleManager.ts | 35 |
5 files changed, 172 insertions, 13 deletions
diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx index 94ce42d8d..46581397d 100644 --- a/src/client/apis/GoogleAuthenticationManager.tsx +++ b/src/client/apis/GoogleAuthenticationManager.tsx @@ -41,6 +41,8 @@ export class GoogleAuthenticationManager extends React.Component<object> { if (new RegExp(AuthenticationUrl).test(response)) { this.isOpen = true; this.authenticationLink = response; + + // GETS STUCK AT THIS PROMISE!! return new Promise<string>(resolve => { this.disposer?.(); this.disposer = reaction( diff --git a/src/client/views/nodes/TaskBox.tsx b/src/client/views/nodes/TaskBox.tsx index 9d59746f8..1c7aeeb82 100644 --- a/src/client/views/nodes/TaskBox.tsx +++ b/src/client/views/nodes/TaskBox.tsx @@ -8,6 +8,7 @@ import { DateField } from '../../../fields/DateField'; import { Doc } from '../../../fields/Doc'; import './TaskBox.scss'; +import { GoogleAuthenticationManager } from '../../apis/GoogleAuthenticationManager'; /** * Props (reference to document) for Task Box @@ -267,6 +268,45 @@ export class TaskBox extends React.Component<TaskBoxProps> { </label> </div> )} + + {/** test button */} + <button + className="task-manager-google" + onClick={async () => { + console.log('GT button clicked'); + const token = await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(); + console.log('Got token', token); + + try { + const response = await fetch('/googleTasks/create', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + title: taskTitle || 'Untitled Task', + notes: taskDesc, + due: allDay + ? String(doc.$task_dateRange)?.split('|')[0] + 'T23:59:00Z' + : (doc.$task_endTime instanceof DateField && doc.$task_endTime.date?.toISOString()) || undefined, + }), + }); + + const result = await response.json(); + console.log("📬 Google Task result:", result); + alert(result?.id ? 'Task sent to Google Tasks!' : `Failed: ${result?.error?.message || 'Unknown error'}`); + } catch (err) { + console.error("Fetch error:", err); + alert("Fetch failed: "); + } + }} + > + GT + </button> + + + </div> ); } diff --git a/src/client/views/nodes/calendarBox/CalendarBox.scss b/src/client/views/nodes/calendarBox/CalendarBox.scss index 891db9d90..89dc294a5 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.scss +++ b/src/client/views/nodes/calendarBox/CalendarBox.scss @@ -50,3 +50,12 @@ pointer-events: unset; } } + +.custom-drag-mirror { + transition: none !important; + transform: none !important; +} + +.fc-event-dragging { + opacity: 0 !important; +} diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx index 26aed72c3..a4183a11a 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -22,6 +22,7 @@ import './CalendarBox.scss'; import { DateField } from '../../../../fields/DateField'; import { undoable } from '../../../util/UndoManager'; import { DocumentType } from '../../../documents/DocumentTypes'; +import { truncate } from 'fs/promises'; type CalendarView = 'multiMonth' | 'dayGridMonth' | 'timeGridWeek' | 'timeGridDay'; @@ -74,8 +75,20 @@ export class CalendarBox extends CollectionSubView() { @computed get calendarEvents(): EventSourceInput | undefined { return this.childDocs.map(doc => { - const { start, end } = dateRangeStrToDates(StrCast(doc.$task_dateRange)); - const isCompleted = BoolCast(doc.$task_completed); // AARAV ADD + // const { start, end } = dateRangeStrToDates(StrCast(doc.$task_dateRange)); + const isCompleted = BoolCast(doc.$task_completed); + + const rangeStr = StrCast(doc.$task_dateRange); + const [startStr, endStr] = rangeStr.split('|'); + let start: string | Date, end: string | Date; + + if (BoolCast(doc.$task_allDay)) { + start = startStr; + end = endStr; + } else { + ({ start, end } = dateRangeStrToDates(rangeStr)); + } + return { title: StrCast(doc.title), start, @@ -157,7 +170,7 @@ export class CalendarBox extends CollectionSubView() { delete doc.$task_endTime; } else { doc.$task_startTime = new DateField(startDate); - doc.task_endTime = new DateField(endDate); + doc.$task_endTime = new DateField(endDate); } }, 'change event date'); @@ -220,7 +233,7 @@ export class CalendarBox extends CollectionSubView() { }} initialDate={untracked(() => this.dateSelect.start)} navLinks={true} - editable={false} + editable={true} // expandRows={true} // handleWindowResize={true} displayEventTime={false} @@ -231,6 +244,7 @@ export class CalendarBox extends CollectionSubView() { events={this.calendarEvents} eventClick={this.handleEventClick} eventDrop={this.handleEventDrop} + eventResize={this.handleEventDrop} unselectAuto={false} // unselect={() => {}} select={(info: DateSelectArg) => { @@ -284,15 +298,74 @@ export class CalendarBox extends CollectionSubView() { ev.preventDefault(); }); }} + + // for dragging and dropping (mirror) + + eventDragStart={(arg) => { + const mirror = arg.el.cloneNode(true) as HTMLElement; + const rect = arg.el.getBoundingClientRect(); + + mirror.style.position = 'fixed'; + mirror.style.pointerEvents = 'none'; + mirror.style.opacity = '0.8'; + mirror.style.zIndex = '10000'; + mirror.classList.add('custom-drag-mirror'); + mirror.style.width = `${rect.width}px`; + mirror.style.height = `${rect.height}px`; + + document.body.appendChild(mirror); + + const moveListener = (ev: MouseEvent) => { + mirror.style.left = `${ev.clientX}px`; + mirror.style.top = `${ev.clientY}px`; + }; + + window.addEventListener('mousemove', moveListener); + + // hide the actual box + arg.el.style.visibility = 'hidden'; + arg.el.style.opacity = '0'; + + (arg.el as any)._mirrorElement = mirror; + (arg.el as any)._moveListener = moveListener; + }} + + eventDragStop={(arg) => { + const el = arg.el as any; + const mirror = el._mirrorElement; + const moveListener = el._moveListener; + + // show the actual box + el.style.visibility = 'visible'; + el.style.opacity = '1'; + + if (mirror) document.body.removeChild(mirror); + if (moveListener) window.removeEventListener('mousemove', moveListener); + }} + /> ); } render() { + const scale = this._props.ScreenToLocalTransform().Scale; + const scaledWidth = this._props.PanelWidth(); + const scaledHeight = this._props.PanelHeight(); + return ( <div key={this.calendarViewType} className={`calendarBox${this._props.isContentActive() ? '-interactive' : ''}`} + style={{ + width: scaledWidth, + height: scaledHeight, + overflow: 'hidden', + position: 'relative', + }} + ref={r => { + this.createDashEventsTarget(r); + this.fixWheelEvents(r, this._props.isContentActive); + }} onPointerDown={e => { setTimeout( action(() => { @@ -303,17 +376,17 @@ export class CalendarBox extends CollectionSubView() { if (cname.includes('timeGridDay')) this.dataDoc[this.calTypeFieldKey] = 'timeGridDay'; }) ); - }} - style={{ - width: this._props.PanelWidth() / this._props.ScreenToLocalTransform().Scale, - height: this._props.PanelHeight() / this._props.ScreenToLocalTransform().Scale, - transform: `scale(${this._props.ScreenToLocalTransform().Scale})`, - }} - ref={r => { - this.createDashEventsTarget(r); - this.fixWheelEvents(r, this._props.isContentActive); }}> + <div + style={{ + transform: `scale(${scale})`, + transformOrigin: 'top left', + width: scaledWidth / scale, + height: scaledHeight / scale, + }} + > {this.renderCalendar} + </div> </div> ); } diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts index 12913b1ef..aa06ca1b3 100644 --- a/src/server/ApiManagers/GeneralGoogleManager.ts +++ b/src/server/ApiManagers/GeneralGoogleManager.ts @@ -3,6 +3,8 @@ import { Method } from '../RouteManager'; import { GoogleApiServerUtils } from '../apis/google/GoogleApiServerUtils'; import RouteSubscriber from '../RouteSubscriber'; import { Database } from '../database'; +import { google } from 'googleapis'; + const EndpointHandlerMap = new Map<GoogleApiServerUtils.Action, GoogleApiServerUtils.ApiRouter>([ ['create', (api, params) => api.create(params)], @@ -61,5 +63,38 @@ export default class GeneralGoogleManager extends ApiManager { res.send(undefined); }, }); + + // AARAV ADD (creating a task) + + register({ + method: Method.POST, + subscription: new RouteSubscriber('googleTasks').add('create'), + secureHandler: async ({ req, res, user }) => { + try { + const { credentials } = await GoogleApiServerUtils.retrieveCredentials(user.id); + if (!credentials?.access_token) { + return res.status(401).send('Google access token not found.'); + } + + const auth = new google.auth.OAuth2(); + auth.setCredentials({ access_token: credentials.access_token }); + + const tasks = google.tasks({ version: 'v1', auth }); + + const { title, notes, due } = req.body; + const result = await tasks.tasks.insert({ + tasklist: '@default', + requestBody: { title, notes, due }, + }); + + res.status(200).send(result.data); + } catch (err) { + console.error('Google Tasks error:', err); + res.status(500).send('Failed to create task.'); + } + }, + }); + + } } |