Skip to content

Expose toUpdatedEvents, toInsertedEvents, toDeletedEvents + Fixed event drag/drop issues. #1369

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

Merged
merged 4 commits into from
Dec 4, 2024
Merged
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
209 changes: 175 additions & 34 deletions client/packages/lowcoder-comps/src/comps/calendarComp/calendarComp.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { default as Form } from "antd/es/form";
import { default as Input } from "antd/es/input";
import { default as ColorPicker } from "antd/es/color-picker";
import { default as Switch } from "antd/es/switch";
import { trans, getCalendarLocale } from "../../i18n/comps";
import { createRef, useContext, useRef, useState, useEffect, useCallback, useMemo, Suspense } from "react";
import dayjs from "dayjs";
@@ -11,14 +12,15 @@ import adaptivePlugin from "@fullcalendar/adaptive";
import dayGridPlugin from "@fullcalendar/daygrid";
import multiMonthPlugin from '@fullcalendar/multimonth';
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction";
import interactionPlugin, { EventResizeDoneArg } from "@fullcalendar/interaction";
import listPlugin from "@fullcalendar/list";
import allLocales from "@fullcalendar/core/locales-all";
import { EventContentArg, DateSelectArg } from "@fullcalendar/core";
import { EventContentArg, DateSelectArg, EventDropArg } from "@fullcalendar/core";
import momentPlugin from "@fullcalendar/moment";

import ErrorBoundary from "./errorBoundary";
import { default as Tabs } from "antd/es/tabs";
import { differenceBy, differenceWith, isEqual, filter, includes } from "lodash";

import {
isValidColor,
@@ -54,6 +56,8 @@ import {
migrateOldData,
controlItem,
depsConfig,
stateComp,
JSONObject,
} from 'lowcoder-sdk';

import {
@@ -79,6 +83,7 @@ import {
resourceTimeGridHeaderToolbar,
} from "./calendarConstants";
import { EventOptionControl } from "./eventOptionsControl";
import { EventImpl } from "@fullcalendar/core/internal";

function fixOldData(oldData: any) {
if(!Boolean(oldData)) return;
@@ -196,6 +201,10 @@ let childrenMap: any = {
currentPremiumView: dropdownControl(DefaultWithPremiumViewOptions, "resourceTimelineDay"),
animationStyle: styleControl(AnimationStyle, 'animationStyle'),
showVerticalScrollbar: withDefault(BoolControl, false),
initialData: stateComp<JSONObject>({}),
updatedEvents: stateComp<JSONObject>({}),
insertedEvents: stateComp<JSONObject>({}),
deletedEvents: stateComp<JSONObject>({}),
};

// this should ensure backwards compatibility with older versions of the SDK
@@ -233,8 +242,9 @@ let CalendarBasicComp = (function () {
currentFreeView?: string;
currentPremiumView?: string;
animationStyle?:any;
modalStyle?:any
showVerticalScrollbar?:boolean
modalStyle?:any;
showVerticalScrollbar?:boolean;
initialData: Array<EventType>;
}, dispatch: any) => {
const comp = useContext(EditorContext)?.getUICompByName(
useContext(CompNameContext)
@@ -243,11 +253,13 @@ let CalendarBasicComp = (function () {
const theme = useContext(ThemeContext);
const ref = createRef<HTMLDivElement>();
const editEvent = useRef<EventType>();
const initData = useRef<boolean>(false);
const [form] = Form.useForm();
const [left, setLeft] = useState<number | undefined>(undefined);
const [licensed, setLicensed] = useState<boolean>(props.licenseKey !== "");
const [currentSlotLabelFormat, setCurrentSlotLabelFormat] = useState(slotLabelFormat);

const [initDataMap, setInitDataMap] = useState<Record<string, number>>({});

useEffect(() => {
setLicensed(props.licenseKey !== "");
}, [props.licenseKey]);
@@ -290,27 +302,53 @@ let CalendarBasicComp = (function () {
start: dayjs(item.start, DateParser).format(),
end: dayjs(item.end, DateParser).format(),
allDay: item.allDay,
resourceId: item.resourceId ? item.resourceId : null,
groupId: item.groupId ? item.groupId : null,
...(item.resourceId ? { resourceId: item.resourceId } : {}),
...(item.groupId ? { groupId: item.groupId } : {}),
backgroundColor: item.backgroundColor,
extendedProps: {
color: isValidColor(item.color || "") ? item.color : theme?.theme?.primary,
...(item.groupId ? { groupId: item.groupId } : {}), // Ensure color is in extendedProps
detail: item.detail,
titleColor:item.titleColor,
detailColor:item.detailColor,
titleFontWeight:item.titleFontWeight,
titleFontStyle:item.titleFontStyle,
detailFontWeight:item.detailFontWeight,
detailFontStyle:item.detailFontStyle,
animation:item?.animation,
animationDelay:item?.animationDelay,
animationDuration:item?.animationDuration,
animationIterationCount:item?.animationIterationCount
}}
extendedProps: { // Ensure color is in extendedProps
color: isValidColor(item.color || "") ? item.color : theme?.theme?.primary,
detail: item.detail,
titleColor:item.titleColor,
detailColor:item.detailColor,
titleFontWeight:item.titleFontWeight,
titleFontStyle:item.titleFontStyle,
detailFontWeight:item.detailFontWeight,
detailFontStyle:item.detailFontStyle,
animation:item?.animation,
animationDelay:item?.animationDelay,
animationDuration:item?.animationDuration,
animationIterationCount:item?.animationIterationCount
}
}
}) : [currentEvents];
}, [currentEvents, theme])

useEffect(() => {
const mapData: Record<string, number> = {};
events?.forEach((item: any, index: number) => {
mapData[`${item.id}`] = index;
})

if (initData.current) {
const difference = differenceWith(events, props.initialData, isEqual);
const inserted = differenceBy(difference, Object.keys(initDataMap)?.map(id => ({ id })), 'id')
const updated = filter(difference, obj => includes(Object.keys(initDataMap), String(obj.id)));
const deleted = differenceBy(props.initialData, Object.keys(mapData)?.map(id => ({ id })), 'id')

comp.children?.comp.children?.updatedEvents.dispatchChangeValueAction(updated);
comp.children?.comp.children?.insertedEvents.dispatchChangeValueAction(inserted);
comp.children?.comp.children?.deletedEvents.dispatchChangeValueAction(deleted);
}

if (!initData.current && events?.length && comp?.children?.comp?.children?.initialData) {
setInitDataMap(mapData);
comp?.children?.comp?.children?.initialData?.dispatch?.(
comp?.children?.comp?.children?.initialData?.changeValueAction?.([...events])
);
initData.current = true;
}
}, [JSON.stringify(events), comp?.children?.comp?.children?.initialData]);

const resources = useMemo(() => props.resources.value, [props.resources.value]);

// list all plugins for Fullcalendar
@@ -370,12 +408,12 @@ let CalendarBasicComp = (function () {
}, [slotLabelFormat, slotLabelFormatWeek, slotLabelFormatMonth]);

const handleEventDataChange = useCallback((data: Array<Record<string,any>>) => {
comp.children?.comp.children.events.children.manual.children.manual.dispatch(
comp.children?.comp.children.events.children.manual.children.manual.setChildrensAction(
comp?.children?.comp.children.events.children.manual.children.manual.dispatch(
comp?.children?.comp.children.events.children.manual.children.manual.setChildrensAction(
data
)
);
comp.children?.comp.children.events.children.mapData.children.data.dispatchChangeValueAction(
comp?.children?.comp.children.events.children.mapData.children.data.dispatchChangeValueAction(
JSON.stringify(data)
);
props.onEvent("change");
@@ -506,6 +544,24 @@ let CalendarBasicComp = (function () {
>
<Input />
</Form.Item>
<Form.Item
label={trans("calendar.eventStartTime")}
name="start"
>
<Input />
</Form.Item>
<Form.Item
label={trans("calendar.eventEndTime")}
name="end"
>
<Input />
</Form.Item>
<Form.Item
label={trans("calendar.eventAllDay")}
name="allDay"
>
<Switch />
</Form.Item>
</FormWrapper>
</Tabs.TabPane>
<Tabs.TabPane tab={trans("calendar.colorStyles")} key="2">
@@ -768,12 +824,35 @@ let CalendarBasicComp = (function () {
showModal(event, false);
}, [editEvent, showModal]);

const handleDrop = useCallback(() => {
const updateEventsOnDragOrResize = useCallback((eventInfo: EventImpl) => {
const {extendedProps, title, ...event} = eventInfo.toJSON();

let eventsList = [...props.events];
const eventIdx = eventsList.findIndex(
(item: EventType) => item.id === event.id
);
if (eventIdx > -1) {
eventsList[eventIdx] = {
label: title,
...event,
...extendedProps,
};
handleEventDataChange(eventsList);
}
}, [props.events, handleEventDataChange]);

const handleDrop = useCallback((eventInfo: EventDropArg) => {
updateEventsOnDragOrResize(eventInfo.event);

if (typeof props.onDropEvent === 'function') {
props.onDropEvent("dropEvent");
props.onDropEvent("drop");
}
}, [props.onDropEvent]);

}, [props.onDropEvent, updateEventsOnDragOrResize]);

const handleResize = useCallback((eventInfo: EventResizeDoneArg) => {
updateEventsOnDragOrResize(eventInfo.event);
}, [props.onDropEvent, updateEventsOnDragOrResize]);

return (
<Wrapper
ref={ref}
@@ -790,7 +869,7 @@ let CalendarBasicComp = (function () {
slotEventOverlap={false}
events={ events }
dayHeaders={true}
dayHeaderFormat={{ weekday: 'short', month: 'numeric', day: 'numeric', omitCommas: true }}
// dayHeaderFormat={{ weekday: 'short', month: 'numeric', day: 'numeric', omitCommas: true }}
expandRows={true}
multiMonthMinWidth={250}
nowIndicator={true}
@@ -880,11 +959,13 @@ let CalendarBasicComp = (function () {
props.onEvent("change");
}
}}
eventDragStop={(info) => {
if (info.view) {
handleDrop();
eventDragStart={() => {
if (typeof props.onDropEvent === 'function') {
props.onDropEvent("drag");
}
}}
eventDrop={handleDrop}
eventResize={handleResize}
/>
</ErrorBoundary>
</Wrapper>
@@ -1007,6 +1088,30 @@ const TmpCalendarComp = withExposingConfigs(CalendarBasicComp, [
return input.events.filter(event => Boolean(event.resourceId));
},
}),
depsConfig({
name: "toUpdatedEvents",
desc: trans("calendar.updatedEvents"),
depKeys: ["updatedEvents"],
func: (input: { updatedEvents: any[]; }) => {
return input.updatedEvents;
},
}),
depsConfig({
name: "toInsertedEvents",
desc: trans("calendar.insertedEvents"),
depKeys: ["insertedEvents"],
func: (input: { insertedEvents: any[]; }) => {
return input.insertedEvents;
},
}),
depsConfig({
name: "toDeletedEvents",
desc: trans("calendar.deletedEvents"),
depKeys: ["deletedEvents"],
func: (input: { deletedEvents: any[]; }) => {
return input.deletedEvents;
},
}),
]);

let CalendarComp = withMethodExposing(TmpCalendarComp, [
@@ -1124,7 +1229,43 @@ let CalendarComp = withMethodExposing(TmpCalendarComp, [
const viewKey = comp.children.licenseKey.getView() === "" ? 'defaultFreeView' : 'defaultPremiumView';
comp.children["viewKey"].dispatchChangeValueAction("multiMonthYear");
}
}
},
{
method: {
name: "clearUpdatedEvents",
detail: "Clear updated events list",
params: [],
},
execute: (comp) => {
comp?.children?.updatedEvents.dispatch(
comp?.children?.updatedEvents.changeValueAction([])
);
}
},
{
method: {
name: "clearInsertedEvents",
detail: "Clear inserted events list",
params: [],
},
execute: (comp) => {
comp?.children?.insertedEvents.dispatch(
comp?.children?.insertedEvents.changeValueAction([])
);
}
},
{
method: {
name: "clearDeletedEvents",
detail: "Clear deleted events list",
params: [],
},
execute: (comp) => {
comp?.children?.deletedEvents.dispatch(
comp?.children?.deletedEvents.changeValueAction([])
);
}
},
]);


3 changes: 3 additions & 0 deletions client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts
Original file line number Diff line number Diff line change
@@ -271,6 +271,9 @@ export const en = {
resourcesDefault: "Rooms",
resourcesName: "Resource Name",
resourcesEvents : "Resources Events Data",
deletedEvents : "List of deleted events",
updatedEvents : "List of updated events",
insertedEvents : "List of inserted events",
editable: "Editable",
license: "Licence Key",
licenseTooltip: "Get your licence key from https://fullcalendar.io/purchase to enable premium views like Resource Timeline and Resource Grid.",