mirror of
https://github.com/zadam/trilium.git
synced 2025-11-18 22:54:23 +01:00
177 lines
7.4 KiB
TypeScript
177 lines
7.4 KiB
TypeScript
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<LOCALE_IDS, (() => 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<CalendarViewData>) {
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const calendarRef = useRef<FullCalendar>(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 &&
|
|
<div className="calendar-view" ref={containerRef}>
|
|
<Calendar
|
|
events={eventBuilder}
|
|
calendarRef={calendarRef}
|
|
plugins={plugins}
|
|
tabIndex={100}
|
|
initialView={initialView.current && CALENDAR_VIEWS.includes(initialView.current) ? initialView.current : "dayGridMonth"}
|
|
headerToolbar={{
|
|
start: "title",
|
|
end: `${CALENDAR_VIEWS.join(",")} today prev,next`
|
|
}}
|
|
firstDay={firstDayOfWeek ?? 0}
|
|
weekends={!hideWeekends}
|
|
weekNumbers={weekNumbers}
|
|
height="100%"
|
|
nowIndicator
|
|
handleWindowResize={false}
|
|
locale={locale}
|
|
editable={isEditable} selectable={isEditable}
|
|
select={onCalendarSelection}
|
|
eventChange={onEventChange}
|
|
dateClick={isCalendarRoot ? onDateClick : undefined}
|
|
viewDidMount={({ view }) => {
|
|
if (initialView.current !== view.type) {
|
|
initialView.current = view.type;
|
|
viewSpacedUpdate.scheduleUpdate();
|
|
}
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function usePlugins(isEditable: boolean, isCalendarRoot: boolean) {
|
|
const [ plugins, setPlugins ] = useState<PluginDef[]>();
|
|
|
|
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<LocaleInput>();
|
|
|
|
useEffect(() => {
|
|
const correspondingLocale = LOCALE_MAPPINGS[locale];
|
|
if (correspondingLocale) {
|
|
correspondingLocale().then((locale) => setCalendarLocale(locale.default));
|
|
} else {
|
|
setCalendarLocale(undefined);
|
|
}
|
|
});
|
|
|
|
return calendarLocale;
|
|
}
|