init
This commit is contained in:
582
calendar/calendar.js
Normal file
582
calendar/calendar.js
Normal file
@@ -0,0 +1,582 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user