582 lines
27 KiB
JavaScript
582 lines
27 KiB
JavaScript
import calendarUtil from "./calendarUtil.js"
|
|
import "./Week/WeekView.js"
|
|
import "./Month/MonthView.js"
|
|
import "./Day/DayView.js"
|
|
import "./Events/EventForm.js"
|
|
import "./Events/EventDetails.js"
|
|
import "./Events/FilePreview.js"
|
|
import "./Toolbar/CalendarToolbar.js"
|
|
import "./Toolbar/CalendarOptions.js"
|
|
import "./Toolbar/BottomBar.js"
|
|
import "./CalendarForm.js"
|
|
import "../components/BottomSheet.js"
|
|
import "/_/code/components/LoadingCircle.js"
|
|
|
|
css(`
|
|
calendar- {
|
|
font-family: 'Arial';
|
|
scrollbar-width: none;
|
|
-ms-overflow-style: none;
|
|
}
|
|
|
|
calendar- h1 {
|
|
font-family: 'Bona';
|
|
}
|
|
`)
|
|
|
|
class Calendar extends Shadow {
|
|
swipeTranslate = 0; // current drag offset in px
|
|
isSwiping = false;
|
|
isCommitting = false;
|
|
swipeDragStartX = null;
|
|
swipeDragStartY = null;
|
|
swipeDragStartTime = null;
|
|
swipeAxisLocked = false;
|
|
swipeIsHorizontal = false;
|
|
|
|
get basePath() {
|
|
return window.location.pathname.replace(/\/day\/[^/]+$/, '').replace(/\/$/, '')
|
|
}
|
|
|
|
get urlDayDate() {
|
|
const match = window.location.pathname.match(/\/day\/(\d{4}-\d{2}-\d{2})$/)
|
|
return match ? new Date(match[1] + 'T00:00:00') : null
|
|
}
|
|
|
|
SWIPE_COMMIT_DISTANCE = window.outerWidth * 0.35; // 35% of screen
|
|
SWIPE_VELOCITY_THRESHOLD = 0.4; // px/ms
|
|
|
|
calendars = [];
|
|
events = [];
|
|
|
|
constructor() {
|
|
super()
|
|
this.currentDate = new Date();
|
|
this.viewMode = localStorage.getItem(`calendarViewMode_${global.profile.id}`) || "month";
|
|
this.weekStartsOn = 0;
|
|
this.showPopout = false;
|
|
this.calendars = [...global.currentNetwork.data.calendars];
|
|
// Restore previously-selected calendars from localStorage; fall back to all
|
|
const storedCalIds = JSON.parse(localStorage.getItem(`calendarSelection_${global.profile.id}_${global.currentNetwork.id}`) || 'null')
|
|
if (storedCalIds) {
|
|
console.log(storedCalIds)
|
|
const restored = this.calendars.filter(c => storedCalIds.includes(c.id))
|
|
this.selectedCalendars = restored.length > 0 ? restored : [...this.calendars]
|
|
} else {
|
|
console.log("nope")
|
|
this.selectedCalendars = [...this.calendars]
|
|
}
|
|
this.events = global.currentNetwork.data.events.map(event => ({
|
|
...event,
|
|
time_start: new Date(event.time_start),
|
|
time_end: new Date(event.time_end)
|
|
}));
|
|
}
|
|
|
|
render() {
|
|
const dayDate = this.urlDayDate
|
|
ZStack(() => {
|
|
VStack(() => {
|
|
if (dayDate) {
|
|
CalendarToolbar(
|
|
dayDate,
|
|
this.weekStartsOn,
|
|
{
|
|
goToPrevious: () => this.subpathNavigateToDate(calendarUtil.addDays(dayDate, -1)),
|
|
goToCurrent: () => this.subpathNavigateToDate(new Date()),
|
|
goToNext: () => this.subpathNavigateToDate(calendarUtil.addDays(dayDate, 1)),
|
|
goToDate: (date) => this.subpathNavigateToDate(date),
|
|
},
|
|
this.selectedCalendars,
|
|
this.events,
|
|
this.showPopout,
|
|
{ onBack: () => navigateTo(this.basePath), viewModeOverride: "day" }
|
|
)
|
|
} else {
|
|
CalendarToolbar(this.currentDate, this.weekStartsOn, {
|
|
goToPrevious: () => this.goToPrevious(),
|
|
goToCurrent: () => this.goToCurrent(),
|
|
goToNext: () => this.goToNext(),
|
|
goToDate: (date) => this.goToDate(date)
|
|
}, this.selectedCalendars, this.events, this.showPopout)
|
|
}
|
|
|
|
if (global.appRefreshing) {
|
|
LoadingCircle()
|
|
} else {
|
|
ZStack(() => {
|
|
// Three panels (previous/current/next) for swipe transitions
|
|
[-1, 0, 1].forEach(offset => {
|
|
let viewDate;
|
|
if (dayDate) {
|
|
viewDate = calendarUtil.addDays(dayDate, offset);
|
|
} else if (this.viewMode === "week") {
|
|
viewDate = calendarUtil.addDays(this.currentDate, offset * 7);
|
|
} else if (this.viewMode === "month") {
|
|
viewDate = calendarUtil.addMonths(this.currentDate, offset);
|
|
} else if (this.viewMode === "day") {
|
|
viewDate = calendarUtil.addDays(this.currentDate, offset);
|
|
}
|
|
|
|
ZStack(() => {
|
|
const isCenter = offset === 0;
|
|
if (dayDate) {
|
|
DayView(this.selectedCalendars, this.events, viewDate, (dateTime) => this.openNewEventForm(dateTime), isCenter)
|
|
} else if (this.viewMode === "week") {
|
|
WeekView(this.selectedCalendars, this.events, viewDate, this.weekStartsOn, (dateTime) => this.openNewEventForm(dateTime), (day) => window.navigateTo(`${this.basePath}/day/${calendarUtil.toDateInput(day)}`), isCenter)
|
|
} else if (this.viewMode === "month") {
|
|
MonthView(this.selectedCalendars, this.events, viewDate, this.weekStartsOn, (day) => {
|
|
if (!calendarUtil.isSameMonth(day, viewDate)) {
|
|
this.commitSwipe(day > viewDate ? "next" : "previous")
|
|
} else {
|
|
window.navigateTo(`${this.basePath}/day/${calendarUtil.toDateInput(day)}`)
|
|
}
|
|
})
|
|
} else if (this.viewMode === "day") {
|
|
DayView(this.selectedCalendars, this.events, viewDate, (dateTime) => this.openNewEventForm(dateTime), isCenter)
|
|
}
|
|
})
|
|
.position("absolute")
|
|
.width(100, pct)
|
|
.height(100, pct)
|
|
.transform(`translateX(${offset * 100}%)`)
|
|
.attr({ "data-swipe-panel": offset })
|
|
})
|
|
})
|
|
.position("relative")
|
|
.overflow("hidden")
|
|
.width(100, pct)
|
|
.flex(1)
|
|
.onTouch((start, e) => this.handleSwipeTouch(start, e))
|
|
}
|
|
})
|
|
.height(100, pct)
|
|
|
|
ActionSheetPopup()
|
|
|
|
FilePreview()
|
|
|
|
const sheet = BottomSheet();
|
|
// Exposed so child views (WeekView, DayView, etc.) can open event details
|
|
sheet.showEvent = (event) => {
|
|
let dirty = false;
|
|
sheet.show(
|
|
() => EventDetails(
|
|
this.calendars,
|
|
event,
|
|
(updateResult) => {
|
|
if (updateResult?.scope) {
|
|
this.handleEditResult(updateResult);
|
|
} else {
|
|
const updatedEvent = updateResult;
|
|
this.events = this.events.map(e => {
|
|
if (e.id !== updatedEvent.id) return e;
|
|
if (updatedEvent._isOccurrence) return { ...e, calendars: updatedEvent.calendars };
|
|
return { ...updatedEvent, time_start: new Date(updatedEvent.time_start), time_end: new Date(updatedEvent.time_end) };
|
|
});
|
|
global.currentNetwork.data.events = global.currentNetwork.data.events.map(e => {
|
|
if (e.id !== updatedEvent.id) return e;
|
|
if (updatedEvent._isOccurrence) return { ...e, calendars: updatedEvent.calendars };
|
|
return updatedEvent;
|
|
});
|
|
}
|
|
dirty = true;
|
|
},
|
|
(deleteResult) => {
|
|
this.handleDeleteResult(deleteResult);
|
|
dirty = false;
|
|
this.rerender();
|
|
}
|
|
),
|
|
() => { if (dirty) { dirty = false; this.rerender(); } }
|
|
);
|
|
};
|
|
|
|
BottomBar({
|
|
onAddEvent: () => this.openNewEventForm(dayDate ?? null),
|
|
hideViewSelect: !!dayDate,
|
|
onCalendarOptions: () => $("bottomsheet-").show(() => CalendarOptions(this.calendars, this.selectedCalendars, {
|
|
onSelection: (newSelectedCalendars) => {
|
|
this.selectedCalendars = newSelectedCalendars;
|
|
localStorage.setItem(`calendarSelection_${global.profile.id}_${global.currentNetwork.id}`, JSON.stringify(newSelectedCalendars.map(c => c.id)))
|
|
this.rerender();
|
|
},
|
|
onCalendarAdded: (newCalendar) => {
|
|
this.calendars = [...this.calendars, newCalendar];
|
|
global.currentNetwork.data.calendars = [...global.currentNetwork.data.calendars, newCalendar];
|
|
},
|
|
onCalendarUpdated: (updatedCalendar) => {
|
|
this.calendars = this.calendars.map(c => c.id === updatedCalendar.id ? updatedCalendar : c);
|
|
this.selectedCalendars = this.selectedCalendars.map(c => c.id === updatedCalendar.id ? updatedCalendar : c);
|
|
global.currentNetwork.data.calendars = global.currentNetwork.data.calendars.map(c => c.id === updatedCalendar.id ? updatedCalendar : c);
|
|
},
|
|
onCalendarDeleted: (deletedId) => {
|
|
this.calendars = this.calendars.filter(c => c.id !== deletedId);
|
|
this.selectedCalendars = this.selectedCalendars.filter(c => c.id !== deletedId);
|
|
global.currentNetwork.data.calendars = global.currentNetwork.data.calendars.filter(c => c.id !== deletedId);
|
|
}
|
|
})),
|
|
viewMode: this.viewMode,
|
|
onChangeView: (mode) => { this.viewMode = mode; localStorage.setItem(`calendarViewMode_${global.profile.id}`, mode); this.rerender(); }
|
|
})
|
|
})
|
|
.position("relative")
|
|
.overflowY("hidden")
|
|
.boxSizing("border-box")
|
|
.height(100, pct)
|
|
.width(100, pct)
|
|
.onNavigate(() => this.rerender())
|
|
}
|
|
|
|
subpathNavigateToDate(date) {
|
|
this.currentDate = date
|
|
window.history.replaceState({}, '', `${this.basePath}/day/${calendarUtil.toDateInput(date)}`)
|
|
this.rerender()
|
|
}
|
|
|
|
openNewEventForm(initialDate = null) {
|
|
let formEl
|
|
const sheet = $("bottomsheet-")
|
|
const onSaveError = () => {
|
|
sheet._closeOverride = () => sheet.forceClose()
|
|
}
|
|
sheet.show(() => {
|
|
formEl = EventForm(this.calendars, (event) => this.updateEvents(event), null, null, null, initialDate, onSaveError)
|
|
})
|
|
sheet._closeOverride = () => {
|
|
sheet.setSheet(true)
|
|
formEl?.handleBack()
|
|
}
|
|
}
|
|
|
|
handleEditResult({ scope, event: resultEvent, templateId, occurrenceDate }) {
|
|
const event = { ...resultEvent, time_start: new Date(resultEvent.time_start), time_end: new Date(resultEvent.time_end) };
|
|
|
|
if (scope === 'all') {
|
|
// Preserve end_date from old template — it may have been set by a 'this and future' split.
|
|
const oldTemplate = this.events.find(e => e.id === templateId);
|
|
const oldEndDate = oldTemplate?.recurrence?.end_date ?? null;
|
|
const recurrence = event.recurrence
|
|
? { ...event.recurrence, end_date: event.recurrence.end_date ?? oldEndDate }
|
|
: null;
|
|
const mergedEvent = { ...event, recurrence };
|
|
this.events = this.events.map(e => e.id === templateId ? mergedEvent : e);
|
|
global.currentNetwork.data.events = global.currentNetwork.data.events.map(e =>
|
|
e.id === templateId ? { ...resultEvent, recurrence } : e
|
|
);
|
|
|
|
} else if (scope === 'single') {
|
|
const alreadyExists = this.events.some(e => e.id === resultEvent.id);
|
|
if (alreadyExists) {
|
|
this.events = this.events.map(e => e.id === resultEvent.id ? event : e);
|
|
global.currentNetwork.data.events = global.currentNetwork.data.events.map(e => e.id === resultEvent.id ? resultEvent : e);
|
|
} else {
|
|
this.events = [...this.events, event];
|
|
global.currentNetwork.data.events = [...global.currentNetwork.data.events, resultEvent];
|
|
}
|
|
|
|
} else if (scope === 'future') {
|
|
const capDate = occurrenceDate ? new Date(occurrenceDate) : null;
|
|
if (capDate) {
|
|
const oldTemplate = this.events.find(e => e.id === templateId);
|
|
const oldEndDate = oldTemplate?.recurrence?.end_date ? new Date(oldTemplate.recurrence.end_date) : null;
|
|
|
|
// Inherit recurrence from form (or old template's rule). A's old end_date caps the new series to avoid overlap with independent later splits.
|
|
const baseRecurrence = event.recurrence ?? oldTemplate?.recurrence;
|
|
const inheritedRecurrence = baseRecurrence
|
|
? { ...baseRecurrence, end_date: oldEndDate ? oldEndDate.toISOString() : null }
|
|
: null;
|
|
const newTemplateEvent = { ...event, recurrence: inheritedRecurrence };
|
|
const newTemplateRaw = { ...resultEvent, recurrence: inheritedRecurrence };
|
|
|
|
// Collect descendants in [capDate, oldEndDate) only; independent splits at/beyond oldEndDate are preserved
|
|
const descendantIds = new Set(
|
|
this.events
|
|
.filter(e => {
|
|
if (!(e.parent_template_id === templateId && e.recurrence_id)) return false;
|
|
const t = new Date(e.time_start);
|
|
return t >= capDate && (!oldEndDate || t < oldEndDate);
|
|
})
|
|
.map(e => e.id)
|
|
);
|
|
|
|
const newId = resultEvent.id;
|
|
const updateAndFilter = (arr) => arr.map(e => {
|
|
if (e.id === templateId && e.recurrence) {
|
|
return { ...e, recurrence: { ...e.recurrence, end_date: capDate.toISOString() } };
|
|
}
|
|
// Migrate overrides in [capDate, oldEndDate) to the new template (mirrors server migration)
|
|
if (e.recurrence_parent_id === templateId && e.recurrence_exception_date) {
|
|
const exDate = new Date(e.recurrence_exception_date);
|
|
if (exDate >= capDate && (!oldEndDate || exDate < oldEndDate)) {
|
|
return { ...e, recurrence_parent_id: newId };
|
|
}
|
|
}
|
|
return e;
|
|
}).filter(e => {
|
|
// Overrides of the old template stay if they're before capDate.
|
|
// Migrated overrides now have recurrence_parent_id = newId so they pass through.
|
|
if (e.recurrence_parent_id === templateId && e.recurrence_exception_date) {
|
|
return new Date(e.recurrence_exception_date) < capDate;
|
|
}
|
|
if (descendantIds.has(e.id) || descendantIds.has(e.recurrence_parent_id)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
this.events = [...updateAndFilter(this.events), newTemplateEvent];
|
|
global.currentNetwork.data.events = [...updateAndFilter(global.currentNetwork.data.events), newTemplateRaw];
|
|
}
|
|
}
|
|
}
|
|
|
|
handleDeleteResult({ scope, templateId, occurrenceDate, overrideId }) {
|
|
if (scope === 'all') {
|
|
// Promote non-cancelled overrides (single-event edits) to standalone; remove cancelled placeholders and template
|
|
const promoteOverrides = (arr) => arr
|
|
.filter(e => e.id !== templateId && !(e.recurrence_parent_id === templateId && e.is_cancelled))
|
|
.map(e => e.recurrence_parent_id === templateId
|
|
? { ...e, recurrence_parent_id: null, recurrence_exception_date: null }
|
|
: e
|
|
);
|
|
this.events = promoteOverrides(this.events);
|
|
global.currentNetwork.data.events = promoteOverrides(global.currentNetwork.data.events);
|
|
} else if (scope === 'single') {
|
|
if (overrideId) {
|
|
this.events = this.events.map(e => e.id === overrideId ? { ...e, is_cancelled: true } : e);
|
|
global.currentNetwork.data.events = global.currentNetwork.data.events.map(e => e.id === overrideId ? { ...e, is_cancelled: true } : e);
|
|
} else if (occurrenceDate) {
|
|
const occDate = new Date(occurrenceDate);
|
|
const syntheticOverride = {
|
|
id: `cancelled_${templateId}_${occurrenceDate}`,
|
|
recurrence_parent_id: templateId,
|
|
recurrence_exception_date: occDate,
|
|
is_cancelled: true,
|
|
time_start: occDate,
|
|
time_end: occDate,
|
|
calendars: [],
|
|
all_day: false,
|
|
};
|
|
this.events = [...this.events, syntheticOverride];
|
|
global.currentNetwork.data.events = [...global.currentNetwork.data.events, syntheticOverride];
|
|
}
|
|
} else if (scope === 'future') {
|
|
const capDate = occurrenceDate ? new Date(occurrenceDate) : null;
|
|
if (capDate) {
|
|
const oldTemplate = this.events.find(e => e.id === templateId);
|
|
// Server does a full delete when capDate <= time_start (no occurrences would remain)
|
|
if (oldTemplate && capDate <= new Date(oldTemplate.time_start)) {
|
|
const promoteOverrides = (arr) => arr
|
|
.filter(e => e.id !== templateId && !(e.recurrence_parent_id === templateId && e.is_cancelled))
|
|
.map(e => e.recurrence_parent_id === templateId
|
|
? { ...e, recurrence_parent_id: null, recurrence_exception_date: null }
|
|
: e
|
|
);
|
|
this.events = promoteOverrides(this.events);
|
|
global.currentNetwork.data.events = promoteOverrides(global.currentNetwork.data.events);
|
|
return;
|
|
}
|
|
const oldEndDate = oldTemplate?.recurrence?.end_date ? new Date(oldTemplate.recurrence.end_date) : null;
|
|
const descendantIds = new Set(
|
|
this.events
|
|
.filter(e => {
|
|
if (!(e.parent_template_id === templateId && e.recurrence_id)) return false;
|
|
const t = new Date(e.time_start);
|
|
return t >= capDate && (!oldEndDate || t < oldEndDate);
|
|
})
|
|
.map(e => e.id)
|
|
);
|
|
const updateAndFilter = (arr) => arr.map(e => {
|
|
if (e.id === templateId && e.recurrence) {
|
|
return { ...e, recurrence: { ...e.recurrence, end_date: capDate.toISOString() } };
|
|
}
|
|
// Promote future non-cancelled overrides to standalone events
|
|
if (e.recurrence_parent_id === templateId && e.recurrence_exception_date
|
|
&& new Date(e.recurrence_exception_date) >= capDate && !e.is_cancelled) {
|
|
return { ...e, recurrence_parent_id: null, recurrence_exception_date: null };
|
|
}
|
|
return e;
|
|
}).filter(e => {
|
|
// Remove future cancelled placeholders and past-promoted overrides that are still linked
|
|
if (e.recurrence_parent_id === templateId && e.recurrence_exception_date) {
|
|
return new Date(e.recurrence_exception_date) < capDate;
|
|
}
|
|
if (descendantIds.has(e.id) || descendantIds.has(e.recurrence_parent_id)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
this.events = updateAndFilter(this.events);
|
|
global.currentNetwork.data.events = updateAndFilter(global.currentNetwork.data.events);
|
|
}
|
|
}
|
|
}
|
|
|
|
updateEvents(event) {
|
|
this.events = [...this.events, { ...event, time_start: new Date(event.time_start), time_end: new Date(event.time_end) }];
|
|
global.currentNetwork.data.events = [...global.currentNetwork.data.events, event];
|
|
this.rerender();
|
|
}
|
|
|
|
changeView(type) {
|
|
if (this.viewMode === type || this.viewMode === "day") return false;
|
|
this.viewMode = type;
|
|
localStorage.setItem('calendarViewMode', type)
|
|
this.rerender();
|
|
return true;
|
|
}
|
|
|
|
goToPrevious() { this.navigate("previous"); }
|
|
goToCurrent() { this.currentDate = new Date(); this.rerender(); }
|
|
goToNext() { this.navigate("next"); }
|
|
goToDate(date) {
|
|
const prev = this.currentDate;
|
|
this.currentDate = date;
|
|
|
|
if (this.viewMode === "week") {
|
|
if (calendarUtil.isSameWeek(prev, date)) return false;
|
|
} else if (this.viewMode === "month") {
|
|
if (calendarUtil.isSameMonth(prev, date)) return false;
|
|
}
|
|
this.rerender();
|
|
return true;
|
|
}
|
|
|
|
navigate(direction) {
|
|
const sign = direction === "next" ? 1 : -1;
|
|
if (this.viewMode === "week") { this.currentDate = calendarUtil.addDays(this.currentDate, sign * 7) }
|
|
else if (this.viewMode === "day") { this.currentDate = calendarUtil.addDays(this.currentDate, sign) }
|
|
else if (this.viewMode === "month") { this.currentDate = calendarUtil.addMonths(this.currentDate, sign) }
|
|
this.rerender();
|
|
}
|
|
|
|
handleSwipeTouch(start, e) {
|
|
if (start) {
|
|
// Block new swipes during active animations
|
|
if (this.isCommitting) return;
|
|
|
|
if ($("home-").sidebarOpen) return;
|
|
|
|
const startX = e.touches[0].clientX;
|
|
const sidebarOpenZone = window.outerWidth / 10;
|
|
const sidebarCloseZone = window.outerWidth * 5 / 6;
|
|
if (startX < sidebarOpenZone || startX > sidebarCloseZone) return;
|
|
|
|
this.swipeDragStartX = e.touches[0].clientX;
|
|
this.swipeDragStartY = e.touches[0].clientY;
|
|
this.swipeDragStartTime = Date.now();
|
|
this.isSwiping = true;
|
|
this.swipeAxisLocked = false;
|
|
this.swipeIsHorizontal = false;
|
|
document.addEventListener("touchmove", this.onSwipeMove, { passive: true });
|
|
} else {
|
|
if (!this.isSwiping) return;
|
|
document.removeEventListener("touchmove", this.onSwipeMove);
|
|
|
|
if (!this.swipeIsHorizontal) {
|
|
this.isSwiping = false;
|
|
this.swipeDragStartX = null;
|
|
this.swipeDragStartY = null;
|
|
return;
|
|
}
|
|
|
|
const endX = e.changedTouches[0].clientX;
|
|
const delta = endX - this.swipeDragStartX;
|
|
const elapsed = Date.now() - this.swipeDragStartTime;
|
|
const velocity = Math.abs(delta) / elapsed;
|
|
const shouldCommit = Math.abs(delta) > this.SWIPE_COMMIT_DISTANCE || velocity > this.SWIPE_VELOCITY_THRESHOLD;
|
|
|
|
if (shouldCommit && delta < 0) {
|
|
this.commitSwipe("next");
|
|
} else if (shouldCommit && delta > 0) {
|
|
this.commitSwipe("previous");
|
|
} else {
|
|
this.snapBack();
|
|
}
|
|
|
|
this.isSwiping = false;
|
|
this.swipeDragStartX = null;
|
|
this.swipeDragStartY = null;
|
|
}
|
|
}
|
|
|
|
onSwipeMove = (e) => {
|
|
if (!this.isSwiping) return;
|
|
|
|
const dx = e.touches[0].clientX - this.swipeDragStartX;
|
|
const dy = e.touches[0].clientY - this.swipeDragStartY;
|
|
|
|
if (!this.swipeAxisLocked) {
|
|
if (Math.abs(dx) < 5 && Math.abs(dy) < 5) return;
|
|
this.swipeAxisLocked = true;
|
|
this.swipeIsHorizontal = Math.abs(dx) > Math.abs(dy);
|
|
}
|
|
if (!this.swipeIsHorizontal) return;
|
|
|
|
const delta = e.touches[0].clientX - this.swipeDragStartX;
|
|
this.swipeTranslate = delta;
|
|
this.applySwipeTransform(delta, false);
|
|
}
|
|
|
|
applySwipeTransform(delta, animated) {
|
|
const panels = this.$$("[data-swipe-panel]", this);
|
|
panels.forEach(panel => {
|
|
const offset = parseInt(panel.getAttribute("data-swipe-panel"));
|
|
panel.style.transition = animated ? "transform 300ms ease" : "";
|
|
panel.style.transform = `translateX(calc(${offset * 100}% + ${delta}px))`;
|
|
});
|
|
}
|
|
|
|
commitSwipe(direction) {
|
|
const dayDate = this.urlDayDate
|
|
const sign = direction === "next" ? 1 : -1;
|
|
let nextDate;
|
|
if (dayDate) {
|
|
nextDate = calendarUtil.addDays(dayDate, sign);
|
|
} else if (this.viewMode === "week") {
|
|
nextDate = calendarUtil.addDays(this.currentDate, sign * 7);
|
|
} else if (this.viewMode === "day") {
|
|
nextDate = calendarUtil.addDays(this.currentDate, sign);
|
|
} else {
|
|
nextDate = calendarUtil.addMonths(this.currentDate, sign);
|
|
}
|
|
|
|
this.isCommitting = true;
|
|
|
|
const screenWidth = window.innerWidth;
|
|
const currentDelta = this.swipeTranslate;
|
|
|
|
const panels = this.$$("[data-swipe-panel]", this);
|
|
panels.forEach(panel => {
|
|
const offset = parseInt(panel.getAttribute("data-swipe-panel"));
|
|
panel.style.transition = "";
|
|
panel.style.transform = `translateX(calc(${offset * 100}% + ${currentDelta}px))`;
|
|
panel.getBoundingClientRect(); // force reflow so transition fires from current position
|
|
panel.style.transition = "transform 300ms ease";
|
|
panel.style.transform = `translateX(calc(${offset * 100}% + ${sign * -screenWidth}px))`;
|
|
});
|
|
|
|
setTimeout(() => {
|
|
this.swipeTranslate = 0;
|
|
this.isCommitting = false;
|
|
if (dayDate) {
|
|
this.subpathNavigateToDate(nextDate)
|
|
} else {
|
|
this.currentDate = nextDate;
|
|
this.rerender();
|
|
}
|
|
}, 300);
|
|
}
|
|
|
|
snapBack() {
|
|
const panels = this.$$("[data-swipe-panel]", this);
|
|
panels.forEach(panel => {
|
|
const offset = parseInt(panel.getAttribute("data-swipe-panel"));
|
|
panel.style.transition = "transform 300ms ease";
|
|
panel.style.transform = `translateX(${offset * 100}%)`;
|
|
});
|
|
this.swipeTranslate = 0;
|
|
}
|
|
}
|
|
|
|
register(Calendar) |