import { DateSelectArg, EventChangeArg, EventSourceFuncArg, LocaleInput, PluginDef } from "@fullcalendar/core/index.js"; import { ViewModeProps } from "../interface"; import Calendar from "./calendar"; import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks"; import "./index.css"; import { useNoteLabel, useNoteLabelBoolean, useResizeObserver, useSpacedUpdate, useTriliumOption, useTriliumOptionInt } from "../../react/hooks"; import { CreateChildrenResponse, LOCALE_IDS } from "@triliumnext/commons"; import { Calendar as FullCalendar } from "@fullcalendar/core"; import { removeOwnedAttributesByNameOrType, setLabel } from "../../../services/attributes"; import { circle } from "leaflet"; import server from "../../../services/server"; import { parseStartEndDateFromEvent, parseStartEndTimeFromEvent } from "./utils"; import dialog from "../../../services/dialog"; import { t } from "../../../services/i18n"; import { buildEvents, buildEventsForCalendar } from "./event_builder"; import { changeEvent, newEvent } from "./api"; import froca from "../../../services/froca"; import date_notes from "../../../services/date_notes"; import appContext from "../../../components/app_context"; import { DateClickArg } from "@fullcalendar/interaction"; interface CalendarViewData { } const CALENDAR_VIEWS = [ "timeGridWeek", "dayGridMonth", "multiMonthYear", "listMonth" ] // Here we hard-code the imports in order to ensure that they are embedded by webpack without having to load all the languages. const LOCALE_MAPPINGS: Record Promise<{ default: LocaleInput }>) | null> = { de: () => import("@fullcalendar/core/locales/de"), es: () => import("@fullcalendar/core/locales/es"), fr: () => import("@fullcalendar/core/locales/fr"), cn: () => import("@fullcalendar/core/locales/zh-cn"), tw: () => import("@fullcalendar/core/locales/zh-tw"), ro: () => import("@fullcalendar/core/locales/ro"), ru: () => import("@fullcalendar/core/locales/ru"), ja: () => import("@fullcalendar/core/locales/ja"), "pt_br": () => import("@fullcalendar/core/locales/pt-br"), uk: () => import("@fullcalendar/core/locales/uk"), en: null }; export default function CalendarView({ note, noteIds }: ViewModeProps) { const containerRef = useRef(null); const calendarRef = useRef(null); const [ calendarRoot ] = useNoteLabelBoolean(note, "calendarRoot"); const [ workspaceCalendarRoot ] = useNoteLabelBoolean(note, "workspaceCalendarRoot"); const [ firstDayOfWeek ] = useTriliumOptionInt("firstDayOfWeek"); const [ hideWeekends ] = useNoteLabelBoolean(note, "calendar:hideWeekends"); const [ weekNumbers ] = useNoteLabelBoolean(note, "calendar:weekNumbers"); const [ calendarView, setCalendarView ] = useNoteLabel(note, "calendar:view"); const initialView = useRef(calendarView); const viewSpacedUpdate = useSpacedUpdate(() => setCalendarView(initialView.current)); useResizeObserver(containerRef, () => calendarRef.current?.updateSize()); const isCalendarRoot = (calendarRoot || workspaceCalendarRoot); const isEditable = !isCalendarRoot; const eventBuilder = useMemo(() => { if (!isCalendarRoot) { return async () => await buildEvents(noteIds); } else { return async (e: EventSourceFuncArg) => await buildEventsForCalendar(note, e); } }, [isCalendarRoot, noteIds]); const plugins = usePlugins(isEditable, isCalendarRoot); const locale = useLocale(); const onCalendarSelection = useCallback(async (e: DateSelectArg) => { const { startDate, endDate } = parseStartEndDateFromEvent(e); if (!startDate) return; const { startTime, endTime } = parseStartEndTimeFromEvent(e); // Ask for the title const title = await dialog.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") }); if (!title?.trim()) { return; } newEvent(note, { title, startDate, endDate, startTime, endTime }); }, [ note ]); const onEventChange = useCallback(async (e: EventChangeArg) => { const { startDate, endDate } = parseStartEndDateFromEvent(e.event); if (!startDate) return; const { startTime, endTime } = parseStartEndTimeFromEvent(e.event); const note = await froca.getNote(e.event.extendedProps.noteId); if (!note) return; changeEvent(note, { startDate, endDate, startTime, endTime }); }, []); // Called upon when clicking the day number in the calendar, opens or creates the day note but only if in a calendar root. const onDateClick = useCallback(async (e: DateClickArg) => { const eventNote = await date_notes.getDayNote(e.dateStr); if (eventNote) { appContext.triggerCommand("openInPopup", { noteIdOrPath: eventNote.noteId }); } }, []); return (plugins &&
{ if (initialView.current !== view.type) { initialView.current = view.type; viewSpacedUpdate.scheduleUpdate(); } }} />
); } function usePlugins(isEditable: boolean, isCalendarRoot: boolean) { const [ plugins, setPlugins ] = useState(); useEffect(() => { async function loadPlugins() { const plugins: PluginDef[] = []; plugins.push((await import("@fullcalendar/daygrid")).default); plugins.push((await import("@fullcalendar/timegrid")).default); plugins.push((await import("@fullcalendar/list")).default); plugins.push((await import("@fullcalendar/multimonth")).default); if (isEditable || isCalendarRoot) { plugins.push((await import("@fullcalendar/interaction")).default); } setPlugins(plugins); } loadPlugins(); }, [ isEditable, isCalendarRoot ]); return plugins; } function useLocale() { const [ locale ] = useTriliumOption("locale"); const [ calendarLocale, setCalendarLocale ] = useState(); useEffect(() => { const correspondingLocale = LOCALE_MAPPINGS[locale]; if (correspondingLocale) { correspondingLocale().then((locale) => setCalendarLocale(locale.default)); } else { setCalendarLocale(undefined); } }); return calendarLocale; }