Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support preview the calendar view on web #5394

Merged
merged 1 commit into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions frontend/appflowy_web_app/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 15 additions & 4 deletions frontend/appflowy_web_app/src/application/collab.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@ export enum YjsDatabaseKey {
visible = 'visible',
hide_ungrouped_column = 'hide_ungrouped_column',
collapse_hidden_groups = 'collapse_hidden_groups',
first_day_of_week = 'first_day_of_week',
show_week_numbers = 'show_week_numbers',
show_weekends = 'show_weekends',
layout_ty = 'layout_ty',
}

export interface YDoc extends Y.Doc {
Expand Down Expand Up @@ -434,23 +438,30 @@ export type YDatabaseFilters = Y.Array<YDatabaseFilter>;

export type YDatabaseSorts = Y.Array<YDatabaseSort>;

export type YDatabaseLayoutSettings = Y.Map<YDatabaseLayoutSetting>;

export type YDatabaseCalculations = Y.Array<YDatabaseCalculation>;

export type SortId = string;

export type GroupId = string;

export interface YDatabaseLayoutSetting extends Y.Map<unknown> {
export interface YDatabaseLayoutSettings extends Y.Map<unknown> {
// DatabaseViewLayout.Board
get(key: '2'): YDatabaseBoardLayoutSetting;
get(key: '1'): YDatabaseBoardLayoutSetting;

// DatabaseViewLayout.Calendar
get(key: '2'): YDatabaseCalendarLayoutSetting;
}

export interface YDatabaseBoardLayoutSetting extends Y.Map<unknown> {
get(key: YjsDatabaseKey.hide_ungrouped_column | YjsDatabaseKey.collapse_hidden_groups): boolean;
}

export interface YDatabaseCalendarLayoutSetting extends Y.Map<unknown> {
get(key: YjsDatabaseKey.first_day_of_week | YjsDatabaseKey.field_id | YjsDatabaseKey.layout_ty): string;

get(key: YjsDatabaseKey.show_week_numbers | YjsDatabaseKey.show_weekends): boolean;
}

export interface YDatabaseGroup extends Y.Map<unknown> {
get(key: YjsDatabaseKey.id): GroupId;

Expand Down
14 changes: 14 additions & 0 deletions frontend/appflowy_web_app/src/application/database-yjs/const.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,16 @@
import { YDatabaseRow, YDoc, YjsDatabaseKey, YjsEditorKey } from '@/application/collab.type';
import * as Y from 'yjs';

export const DEFAULT_ROW_HEIGHT = 37;
export const MIN_COLUMN_WIDTH = 100;

export const getCell = (rowId: string, fieldId: string, rowMetas: Y.Map<YDoc>) => {
const rowMeta = rowMetas.get(rowId);
const meta = rowMeta?.getMap(YjsEditorKey.data_section).get(YjsEditorKey.database_row) as YDatabaseRow;

return meta?.get(YjsDatabaseKey.cells)?.get(fieldId);
};

export const getCellData = (rowId: string, fieldId: string, rowMetas: Y.Map<YDoc>) => {
return getCell(rowId, fieldId, rowMetas)?.get(YjsDatabaseKey.data);
};
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,17 @@ export interface Filter {
id: string;
content: string;
}

export enum CalendarLayout {
MonthLayout = 0,
WeekLayout = 1,
DayLayout = 2,
}

export interface CalendarLayoutSetting {
fieldId: string;
firstDayOfWeek: number;
showWeekNumbers: boolean;
showWeekends: boolean;
layout: CalendarLayout;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface ChecklistCellData {
export function parseChecklistData(data: string): ChecklistCellData | null {
try {
const { options, selected_option_ids } = JSON.parse(data);
const percentage = (selected_option_ids.length / options.length) * 100;
const percentage = selected_option_ids.length / options.length;

return {
percentage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,10 @@ export function checklistFilterCheck(data: string, content: string, condition: n
const percentage = parseChecklistData(data)?.percentage ?? 0;

if (condition === ChecklistFilterCondition.IsComplete) {
return percentage === 100;
return percentage === 1;
}

return percentage !== 100;
return percentage !== 1;
}

export function selectOptionFilterCheck(data: string, content: string, condition: number) {
Expand Down
10 changes: 2 additions & 8 deletions frontend/appflowy_web_app/src/application/database-yjs/group.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { YDatabaseField, YDatabaseRow, YDoc, YjsDatabaseKey, YjsEditorKey } from '@/application/collab.type';
import { YDatabaseField, YDoc, YjsDatabaseKey } from '@/application/collab.type';
import { getCellData } from '@/application/database-yjs/const';
import { FieldType } from '@/application/database-yjs/database.type';
import { parseSelectOptionTypeOptions } from '@/application/database-yjs/fields';
import { Row } from '@/application/database-yjs/selector';
Expand All @@ -12,13 +13,6 @@ export function groupByField(rows: Row[], rowMetas: Y.Map<YDoc>, field: YDatabas
return groupBySelectOption(rows, rowMetas, field);
}

function getCellData(rowId: string, fieldId: string, rowMetas: Y.Map<YDoc>) {
const rowMeta = rowMetas.get(rowId);
const meta = rowMeta?.getMap(YjsEditorKey.data_section).get(YjsEditorKey.database_row) as YDatabaseRow;

return meta?.get(YjsDatabaseKey.cells)?.get(fieldId)?.get(YjsDatabaseKey.data);
}

export function groupBySelectOption(rows: Row[], rowMetas: Y.Map<YDoc>, field: YDatabaseField) {
const fieldId = field.get(YjsDatabaseKey.id);
const result = new Map<string, Row[]>();
Expand Down
119 changes: 113 additions & 6 deletions frontend/appflowy_web_app/src/application/database-yjs/selector.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FieldId, SortId, YDatabaseField, YjsDatabaseKey } from '@/application/collab.type';
import { MIN_COLUMN_WIDTH } from '@/application/database-yjs/const';
import { FieldId, SortId, YDatabaseField, YjsDatabaseKey, YjsFolderKey } from '@/application/collab.type';
import { getCell, MIN_COLUMN_WIDTH } from '@/application/database-yjs/const';
import {
DatabaseContext,
useDatabase,
Expand All @@ -13,10 +13,13 @@ import { filterBy, parseFilter } from '@/application/database-yjs/filter';
import { groupByField } from '@/application/database-yjs/group';
import { sortBy } from '@/application/database-yjs/sort';
import { useViewsIdSelector } from '@/application/folder-yjs';
import { useId } from '@/components/_shared/context-provider/IdProvider';
import { parseYDatabaseCellToCell } from '@/components/database/components/cell/cell.parse';
import { DateTimeCell } from '@/components/database/components/cell/cell.type';
import dayjs from 'dayjs';
import debounce from 'lodash-es/debounce';
import { useContext, useEffect, useMemo, useState } from 'react';
import { FieldType, FieldVisibility, Filter, SortCondition } from './database.type';
import { CalendarLayoutSetting, FieldType, FieldVisibility, Filter, SortCondition } from './database.type';

export interface Column {
fieldId: string;
Expand All @@ -34,7 +37,8 @@ const defaultVisible = [FieldVisibility.AlwaysShown, FieldVisibility.HideWhenEmp

export function useDatabaseViewsSelector() {
const database = useDatabase();
const { viewsId: visibleViewsId } = useViewsIdSelector();
const { objectId: currentViewId } = useId();
const { viewsId: visibleViewsId, views: folderViews } = useViewsIdSelector();
const views = database?.get(YjsDatabaseKey.views);
const [viewIds, setViewIds] = useState<string[]>([]);
const childViews = useMemo(() => {
Expand All @@ -45,7 +49,16 @@ export function useDatabaseViewsSelector() {
if (!views) return;

const observerEvent = () => {
setViewIds(Array.from(views.keys()).filter((id) => visibleViewsId.includes(id)));
setViewIds(
Array.from(views.keys()).filter((id) => {
const view = folderViews?.get(id);

return (
visibleViewsId.includes(id) &&
(view?.get(YjsFolderKey.bid) === currentViewId || view?.get(YjsFolderKey.id) === currentViewId)
);
})
);
};

observerEvent();
Expand All @@ -54,7 +67,7 @@ export function useDatabaseViewsSelector() {
return () => {
views.unobserve(observerEvent);
};
}, [visibleViewsId, views]);
}, [visibleViewsId, views, folderViews, currentViewId]);

return {
childViews,
Expand Down Expand Up @@ -478,3 +491,97 @@ export function useCellSelector({ rowId, fieldId }: { rowId: string; fieldId: st

return cellValue;
}

export interface CalendarEvent {
start?: Date;
end?: Date;
id: string;
}

export function useCalendarEventsSelector() {
const setting = useCalendarLayoutSetting();
const filedId = setting.fieldId;
const { field } = useFieldSelector(filedId);
const rowOrders = useRowOrdersSelector();
const rows = useContext(DatabaseContext)?.rowDocMap;
const [events, setEvents] = useState<CalendarEvent[]>([]);
const [emptyEvents, setEmptyEvents] = useState<CalendarEvent[]>([]);

useEffect(() => {
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[] = [];

rowOrders?.forEach((row) => {
const cell = getCell(row.id, filedId, rows);

if (!cell) {
emptyEvents.push({
id: `${row.id}:${filedId}`,
});
return;
}

const value = parseYDatabaseCellToCell(cell) as DateTimeCell;

if (!value || !value.data) {
emptyEvents.push({
id: `${row.id}:${filedId}`,
});
return;
}

const getDate = (timestamp: string) => {
const dayjsResult = timestamp.length === 10 ? dayjs.unix(Number(timestamp)) : dayjs(timestamp);

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]);

return { events, emptyEvents };
}

export function useCalendarLayoutSetting() {
const view = useDatabaseView();
const layoutSetting = view?.get(YjsDatabaseKey.layout_settings)?.get('2');
const [setting, setSetting] = useState<CalendarLayoutSetting>({
fieldId: '',
firstDayOfWeek: 0,
showWeekNumbers: true,
showWeekends: true,
layout: 0,
});

useEffect(() => {
const observerHandler = () => {
setSetting({
fieldId: layoutSetting?.get(YjsDatabaseKey.field_id) as string,
firstDayOfWeek: Number(layoutSetting?.get(YjsDatabaseKey.first_day_of_week)),
showWeekNumbers: Boolean(layoutSetting?.get(YjsDatabaseKey.show_week_numbers)),
showWeekends: Boolean(layoutSetting?.get(YjsDatabaseKey.show_weekends)),
layout: Number(layoutSetting?.get(YjsDatabaseKey.layout_ty)),
});
};

observerHandler();
layoutSetting?.observe(observerHandler);
return () => {
layoutSetting?.unobserve(observerHandler);
};
}, [layoutSetting]);

return setting;
}
29 changes: 15 additions & 14 deletions frontend/appflowy_web_app/src/application/folder-yjs/selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,39 @@ import { useEffect, useState } from 'react';
export function useViewsIdSelector() {
const folder = useFolderContext();
const [viewsId, setViewsId] = useState<string[]>([]);
const views = folder?.get(YjsFolderKey.views);
const trash = folder?.get(YjsFolderKey.section)?.get(YjsFolderKey.trash);
const meta = folder?.get(YjsFolderKey.meta);

useEffect(() => {
if (!folder) return;
if (!views) return;

const views = folder.get(YjsFolderKey.views);
const trash = folder.get(YjsFolderKey.section)?.get(YjsFolderKey.trash);
const meta = folder.get(YjsFolderKey.meta);
const trashUid = Array.from(trash?.keys())[0];
const userTrash = trash?.get(trashUid);
const trashUid = trash ? Array.from(trash.keys())[0] : null;
const userTrash = trashUid ? trash?.get(trashUid) : null;

const collectIds = () => {
const trashIds = userTrash?.toJSON()?.map((item) => item.id) || [];

return Array.from(views.keys()).filter(
(id) => !trashIds.includes(id) && id !== meta?.get(YjsFolderKey.current_workspace)
);
return Array.from(views.keys()).filter((id) => {
return !trashIds.includes(id) && id !== meta?.get(YjsFolderKey.current_workspace);
});
};

setViewsId(collectIds());
const observerEvent = () => setViewsId(collectIds());

folder.observe(observerEvent);
userTrash.observe(observerEvent);
views.observe(observerEvent);
userTrash?.observe(observerEvent);

return () => {
folder.unobserve(observerEvent);
userTrash.unobserve(observerEvent);
views.unobserve(observerEvent);
userTrash?.unobserve(observerEvent);
};
}, [folder]);
}, [views, trash, meta]);

return {
viewsId,
views,
};
}

Expand Down
Loading
Loading