feat: support calendar (#56)

* chore: modified title sync

* chore: replace console.log to console.debug

* feat: add FullCalendar dependencies and improve code organization

* chore: update view name bugs

* chore: support no date row

* chore: layout by created time and modified time

* chore: adjust time

* chore: pnpm install

* chore: fix something
This commit is contained in:
Kilu.He
2025-09-09 12:07:59 +08:00
committed by GitHub
parent ef001756cd
commit f6a8c83015
133 changed files with 7542 additions and 1595 deletions

View File

@@ -35,6 +35,7 @@ import {
YjsDatabaseKey,
YjsEditorKey,
} from '@/application/types';
import { MetadataKey } from '@/application/user-metadata';
import { useCurrentUser } from '@/components/main/app.hooks';
import { getDateFormat, getTimeFormat, renderDate } from '@/utils/time';
@@ -841,12 +842,17 @@ export interface CalendarEvent {
start?: Date;
end?: Date;
id: string;
title: string;
allDay: boolean;
rowId: string;
isRange?: boolean;
}
export function useCalendarEventsSelector() {
const setting = useCalendarLayoutSetting();
const filedId = setting.fieldId;
const { field } = useFieldSelector(filedId);
const primaryFieldId = usePrimaryFieldId();
const rowOrders = useRowOrdersSelector();
const rows = useRowDocMap();
const [events, setEvents] = useState<CalendarEvent[]>([]);
@@ -856,68 +862,134 @@ export function useCalendarEventsSelector() {
if (!field || !rowOrders || !rows) return;
const fieldType = Number(field?.get(YjsDatabaseKey.type)) as FieldType;
if (fieldType !== FieldType.DateTime) return;
const newEvents: CalendarEvent[] = [];
const emptyEvents: CalendarEvent[] = [];
if (![FieldType.DateTime, FieldType.LastEditedTime, FieldType.CreatedTime].includes(fieldType) || !primaryFieldId) return;
rowOrders?.forEach((row) => {
const cell = getCell(row.id, filedId, rows);
const observerEvent = () => {
const newEvents: CalendarEvent[] = [];
const emptyEvents: CalendarEvent[] = [];
if (!cell) {
emptyEvents.push({
id: `${row.id}:${filedId}`,
});
return;
}
rowOrders?.forEach((row) => {
const cell = getCell(row.id, filedId, rows);
const primaryCell = getCell(row.id, primaryFieldId, rows);
const allDay = !cell?.get(YjsDatabaseKey.include_time);
const value = parseYDatabaseCellToCell(cell) as DateTimeCell;
const title = (primaryCell?.get(YjsDatabaseKey.data) as string) || '';
if (!value || !value.data) {
emptyEvents.push({
id: `${row.id}:${filedId}`,
});
return;
}
const doc = rows?.[row.id];
const getDate = (timestamp: string) => {
const dayjsResult = timestamp.length === 10 ? dayjs.unix(Number(timestamp)) : dayjs(timestamp);
if (!doc) return;
const rowSharedRoot = doc.getMap(YjsEditorKey.data_section);
const databbaseRow = rowSharedRoot.get(YjsEditorKey.database_row);
const rowCreatedTime = databbaseRow.get(YjsDatabaseKey.created_at);
const rowLastEditedTime = databbaseRow.get(YjsDatabaseKey.last_modified);
const value = cell ? parseYDatabaseCellToCell(cell) as DateTimeCell : undefined;
if ((!value?.data && fieldType !== FieldType.CreatedTime && fieldType !== FieldType.LastEditedTime) ||
(fieldType === FieldType.CreatedTime && !rowCreatedTime) ||
(fieldType === FieldType.LastEditedTime && !rowLastEditedTime)
) {
emptyEvents.push({
id: `${row.id}`,
title,
allDay,
rowId: row.id,
});
return;
}
const getDate = (timestamp: string) => {
const dayjsResult = timestamp.length === 10 ? dayjs.unix(Number(timestamp)) : dayjs(timestamp);
return dayjsResult.toDate();
};
if ([FieldType.CreatedTime, FieldType.LastEditedTime].includes(fieldType)) {
newEvents.push({
id: `${row.id}`,
start: fieldType === FieldType.CreatedTime ? getDate(rowCreatedTime) : getDate(rowLastEditedTime),
title,
allDay,
rowId: row.id,
});
} else if (value) {
newEvents.push({
id: `${row.id}`,
start: getDate(value.data),
isRange: value.isRange || false,
end: value.endTimestamp && value.isRange ? getDate(value.endTimestamp) : dayjs(getDate(value.data)).add(30, 'minute').toDate(),
title,
allDay,
rowId: row.id,
});
}
return dayjsResult.toDate();
};
newEvents.push({
id: `${row.id}:${filedId}`,
start: getDate(value.data),
end: value.endTimestamp && value.isRange ? getDate(value.endTimestamp) : getDate(value.data),
});
});
setEvents(newEvents);
setEmptyEvents(emptyEvents);
}, [field, rowOrders, rows, filedId]);
setEvents(newEvents);
setEmptyEvents(emptyEvents);
}
observerEvent();
field?.observeDeep(observerEvent);
const debouncedObserverEvent = debounce(observerEvent, 150);
// for every row
rowOrders?.forEach((row) => {
const rowDoc = rows?.[row.id];
if (!rowDoc) return;
rowDoc.getMap(YjsEditorKey.data_section).observeDeep(debouncedObserverEvent);
});
return () => {
debouncedObserverEvent.cancel();
field?.unobserveDeep(observerEvent);
rowOrders?.forEach((row) => {
const rowDoc = rows?.[row.id];
if (!rowDoc) return;
rowDoc.getMap(YjsEditorKey.data_section).unobserveDeep(debouncedObserverEvent);
});
};
}, [field, rowOrders, rows, filedId, primaryFieldId]);
return { events, emptyEvents };
}
export function useCalendarLayoutSetting() {
const currentUser = useCurrentUser();
const startWeekOn = Number(currentUser?.metadata?.[MetadataKey.StartWeekOn] || 0);
const view = useDatabaseView();
const layoutSetting = view?.get(YjsDatabaseKey.layout_settings)?.get('2');
const [setting, setSetting] = useState<CalendarLayoutSetting>({
fieldId: '',
firstDayOfWeek: 0,
firstDayOfWeek: startWeekOn,
showWeekNumbers: true,
showWeekends: true,
layout: 0,
numberOfDays: 7
});
useEffect(() => {
const observerHandler = () => {
const firstDayOfWeek = layoutSetting?.get(YjsDatabaseKey.first_day_of_week) === undefined ? startWeekOn : Number(layoutSetting?.get(YjsDatabaseKey.first_day_of_week) || 0);
setSetting({
fieldId: layoutSetting?.get(YjsDatabaseKey.field_id) as string,
firstDayOfWeek: Number(layoutSetting?.get(YjsDatabaseKey.first_day_of_week)),
firstDayOfWeek,
showWeekNumbers: Boolean(layoutSetting?.get(YjsDatabaseKey.show_week_numbers)),
showWeekends: Boolean(layoutSetting?.get(YjsDatabaseKey.show_weekends)),
layout: Number(layoutSetting?.get(YjsDatabaseKey.layout_ty)),
numberOfDays: layoutSetting?.get(YjsDatabaseKey.number_of_days) || 7,
});
};
@@ -926,7 +998,7 @@ export function useCalendarLayoutSetting() {
return () => {
layoutSetting?.unobserve(observerHandler);
};
}, [layoutSetting]);
}, [layoutSetting, startWeekOn]);
return setting;
}