Merge pull request #26360 from rutwikhdev/upgrade-fullcalendar

feat: upgrade fullcalendar
This commit is contained in:
Ankush Menat 2024-05-13 13:38:34 +05:30 committed by GitHub
commit 49fb60a0dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 222 additions and 173 deletions

View file

@ -0,0 +1,8 @@
import { Calendar as FullCalendar } from "@fullcalendar/core";
import dayGridPlugin from "@fullcalendar/daygrid";
import listPlugin from "@fullcalendar/list";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction";
frappe.FullCalendar = FullCalendar;
frappe.FullCalendar.Plugins = [listPlugin, dayGridPlugin, timeGridPlugin, interactionPlugin];

View file

@ -28,7 +28,7 @@ frappe.views.CalendarView = class CalendarView extends frappe.views.ListView {
setup_defaults() {
return super.setup_defaults().then(() => {
this.page_title = __("{0} Calendar", [this.page_title]);
this.calendar_settings = frappe.views.calendar[this.doctype] || {};
this.calendar_settings = frappe.views.Calendar[this.doctype] || {};
this.calendar_name = frappe.get_route()[3];
});
}
@ -101,15 +101,7 @@ frappe.views.CalendarView = class CalendarView extends frappe.views.ListView {
}
get required_libs() {
let assets = [
"assets/frappe/js/lib/fullcalendar/fullcalendar.min.css",
"assets/frappe/js/lib/fullcalendar/fullcalendar.min.js",
];
let user_language = frappe.boot.lang;
if (user_language && user_language !== "en") {
assets.push("assets/frappe/js/lib/fullcalendar/locale-all.js");
}
return assets;
return "calendar.bundle.js";
}
};
@ -133,10 +125,10 @@ frappe.views.Calendar = class Calendar {
}
get_default_options() {
return new Promise((resolve) => {
let defaultView = localStorage.getItem("cal_defaultView");
let initialView = localStorage.getItem("cal_initialView");
let weekends = localStorage.getItem("cal_weekends");
let defaults = {
defaultView: defaultView ? defaultView : "month",
initialView: initialView ? initialView : "dayGridMonth",
weekends: weekends ? weekends : true,
};
resolve(defaults);
@ -162,13 +154,13 @@ frappe.views.Calendar = class Calendar {
});
$(this.parent).on("show", function () {
me.$cal.fullCalendar("refetchEvents");
me.$cal.fullCalendar.refetchEvents();
});
}
make() {
this.$wrapper = this.parent;
this.$cal = $("<div>").appendTo(this.$wrapper);
this.$cal = $("<div id='fc-calendar-wrapper'>").appendTo(this.$wrapper);
this.footnote_area = frappe.utils.set_footnote(
this.footnote_area,
this.$wrapper,
@ -176,7 +168,9 @@ frappe.views.Calendar = class Calendar {
);
this.footnote_area.css({ "border-top": "0px" });
this.$cal.fullCalendar(this.cal_options);
this.fullCalendar = new frappe.FullCalendar(this.$cal[0], this.cal_options);
this.fullCalendar.render();
this.set_css();
}
setup_view_mode_button(defaults) {
@ -194,143 +188,152 @@ frappe.views.Calendar = class Calendar {
const me = this;
let btn_group = me.$wrapper.find(".fc-button-group");
btn_group.on("click", ".btn", function () {
let value = $(this).hasClass("fc-agendaWeek-button")
? "agendaWeek"
: $(this).hasClass("fc-agendaDay-button")
? "agendaDay"
: "month";
me.set_localStorage_option("cal_defaultView", value);
let value = $(this).hasClass("fc-dayGridWeek-button")
? "dayGridWeek"
: $(this).hasClass("fc-dayGridDay-button")
? "dayGridDay"
: "dayGridMonth";
me.set_localStorage_option("cal_initialView", value);
});
me.$wrapper.on("click", ".btn-weekend", function () {
me.cal_options.weekends = !me.cal_options.weekends;
me.$cal.fullCalendar("option", "weekends", me.cal_options.weekends);
me.fullCalendar.setOption("weekends", me.cal_options.weekends);
me.set_localStorage_option("cal_weekends", me.cal_options.weekends);
me.set_css();
me.setup_view_mode_button(me.cal_options);
});
}
set_css() {
// flatify buttons
const viewButtons =
".fc-dayGridMonth-button, .fc-dayGridWeek-button, .fc-dayGridDay-button, .fc-today-button";
const fcViewButtonClasses = "fc-button fc-button-primary fc-button-active";
// remove fc-button styles
this.$wrapper
.find("button.fc-state-default")
.removeClass("fc-state-default")
.find("button.fc-button")
.removeClass(fcViewButtonClasses)
.addClass("btn btn-default");
this.$wrapper
.find(".fc-month-button, .fc-agendaWeek-button, .fc-agendaDay-button")
.wrapAll('<div class="btn-group" />');
// group all view buttons
this.$wrapper.find(viewButtons).wrapAll('<div class="btn-group" />');
// add icons
this.$wrapper
.find(".fc-prev-button span")
.find(`.fc-prev-button span`)
.attr("class", "")
.html(frappe.utils.icon("left"));
this.$wrapper
.find(".fc-next-button span")
.find(`.fc-next-button span`)
.attr("class", "")
.html(frappe.utils.icon("right"));
if (this.$wrapper.find(".fc-today-button svg").length == 0)
this.$wrapper.find(".fc-today-button").prepend(frappe.utils.icon("today"));
this.$wrapper.find(".fc-today-button").prepend(frappe.utils.icon("today"));
this.$wrapper.find(".fc-day-number").wrap('<div class="fc-day"></div>');
// v6.x of fc has weird behaviour which removes all the custom classes
// on header buttons on click, event below re-adds all the classes
var btn_group = this.$wrapper.find(".fc-button-group");
btn_group.find(".fc-state-active").addClass("active");
btn_group.find(".fc-button-active").addClass("active");
btn_group.find(".btn").on("click", function () {
btn_group.find(".btn").removeClass("active");
btn_group
.find(viewButtons)
.removeClass(`active ${fcViewButtonClasses}`)
.addClass("btn btn-default");
$(this).addClass("active");
});
}
get_system_datetime(date) {
date._offset = moment(date).tz(frappe.sys_defaults.time_zone)._offset;
return frappe.datetime.convert_to_system_tz(moment(date).locale("en"));
return frappe.datetime.convert_to_system_tz(date, true);
}
setup_options(defaults) {
var me = this;
defaults.meridiem = "false";
this.cal_options = {
plugins: frappe.FullCalendar.Plugins,
initialView: defaults.initialView || "dayGridMonth",
locale: frappe.boot.lang,
header: {
left: "prev, title, next",
right: "today, month, agendaWeek, agendaDay",
firstDay: 1,
headerToolbar: {
left: "prev,title,next",
center: "",
right: "today,dayGridMonth,dayGridWeek,dayGridDay",
},
editable: true,
droppable: true,
selectable: true,
selectHelper: true,
selectMirror: true,
forceEventDuration: true,
displayEventTime: true,
defaultView: defaults.defaultView,
weekends: defaults.weekends,
nowIndicator: true,
themeSystem: null,
buttonText: {
today: __("Today"),
month: __("Month"),
week: __("Week"),
day: __("Day"),
},
events: function (start, end, timezone, callback) {
events: function (info, successCallback, failureCallback) {
return frappe.call({
method: me.get_events_method || "frappe.desk.calendar.get_events",
type: "GET",
args: me.get_args(start, end),
args: me.get_args(info.start, info.end),
callback: function (r) {
var events = r.message || [];
events = me.prepare_events(events);
callback(events);
successCallback(events);
},
});
},
displayEventEnd: true,
eventRender: function (event, element) {
element.attr("title", event.tooltip);
},
eventClick: function (event) {
eventClick: function (info) {
// edit event description or delete
var doctype = event.doctype || me.doctype;
var doctype = info.doctype || me.doctype;
if (frappe.model.can_read(doctype)) {
frappe.set_route("Form", doctype, event.name);
frappe.set_route("Form", doctype, info.event.id);
}
},
eventDrop: function (event, delta, revertFunc) {
me.update_event(event, revertFunc);
eventDrop: function (info) {
me.update_event(info.event, info.revert);
},
eventResize: function (event, delta, revertFunc) {
me.update_event(event, revertFunc);
eventResize: function (info) {
me.update_event(info.event, info.revert);
},
select: function (startDate, endDate, jsEvent, view) {
if (view.name === "month" && endDate - startDate === 86400000) {
select: function (info) {
const seconds = info.end - info.start;
const allDay = seconds === 86400000;
if (info.view.type === "dayGridMonth" && allDay) {
// detect single day click in month view
return;
}
var event = frappe.model.get_new_doc(me.doctype);
event[me.field_map.start] = me.get_system_datetime(startDate);
event[me.field_map.start] = me.get_system_datetime(info.start);
if (me.field_map.end) event[me.field_map.end] = me.get_system_datetime(info.end);
if (me.field_map.end) event[me.field_map.end] = me.get_system_datetime(endDate);
if (me.field_map.allDay) {
var all_day = startDate._ambigTime && endDate._ambigTime ? 1 : 0;
event[me.field_map.allDay] = all_day;
if (all_day)
event[me.field_map.end] = me.get_system_datetime(
moment(endDate).subtract(1, "s")
);
if (seconds >= 86400000) {
if (allDay) {
// all-day click
event[me.field_map.allDay] = 1;
}
// incase of all day or multiple day events -1 sec
event[me.field_map.end] = me.get_system_datetime(info.end - 1);
}
frappe.set_route("Form", me.doctype, event.name);
},
dayClick: function (date, jsEvent, view) {
if (view.name === "month") {
const $date_cell = $("td[data-date=" + date.format("YYYY-MM-DD") + "]");
dateClick: function (info) {
if (info.view.type === "dayGridMonth") {
const $date_cell = $(
"td[data-date=" + info.date.toISOString().slice(0, 10) + "]"
);
if ($date_cell.hasClass("date-clicked")) {
me.$cal.fullCalendar("changeView", "agendaDay");
me.$cal.fullCalendar("gotoDate", date);
me.fullCalendar.changeView("timeGridDay", info.date);
me.$wrapper.find(".date-clicked").removeClass("date-clicked");
// update "active view" btn
@ -340,6 +343,13 @@ frappe.views.Calendar = class Calendar {
me.$wrapper.find(".date-clicked").removeClass("date-clicked");
$date_cell.addClass("date-clicked");
// explicitly remove the fc primary button styling that is append on view change
// from month -> day
$("#fc-calendar-wrapper")
.find("button.fc-button")
.removeClass("fc-button fc-button-primary fc-button-active")
.addClass("btn btn-default");
}
return false;
},
@ -361,7 +371,7 @@ frappe.views.Calendar = class Calendar {
return args;
}
refresh() {
this.$cal.fullCalendar("refetchEvents");
this.fullCalendar.refetchEvents();
}
prepare_events(events) {
var me = this;
@ -400,7 +410,6 @@ frappe.views.Calendar = class Calendar {
d.end = frappe.datetime.add_days(d.start, 1);
}
me.fix_end_date_for_event_render(d);
me.prepare_colors(d);
d.title = frappe.utils.html2text(d.title);
@ -431,7 +440,7 @@ frappe.views.Calendar = class Calendar {
}
update_event(event, revertFunc) {
var me = this;
frappe.model.remove_from_locals(me.doctype, event.name);
frappe.model.remove_from_locals(me.doctype, event.id);
return frappe.call({
method: me.update_event_method || "frappe.desk.calendar.update_event",
args: me.get_update_args(event),
@ -449,13 +458,14 @@ frappe.views.Calendar = class Calendar {
get_update_args(event) {
var me = this;
var args = {
name: event[this.field_map.id],
name: event.id,
};
args[this.field_map.start] = me.get_system_datetime(event.start);
if (this.field_map.allDay)
args[this.field_map.allDay] = event.start._ambigTime && event.end._ambigTime ? 1 : 0;
if (this.field_map.allDay) {
args[this.field_map.allDay] = event.end - event.start === 86400000 ? 1 : 0;
}
if (this.field_map.end) {
if (!event.end) {
@ -463,11 +473,8 @@ frappe.views.Calendar = class Calendar {
}
args[this.field_map.end] = me.get_system_datetime(event.end);
if (args[this.field_map.allDay]) {
args[this.field_map.end] = me.get_system_datetime(
moment(event.end).subtract(1, "s")
);
args[this.field_map.end] = me.get_system_datetime(new Date(event.end - 1000));
}
}
@ -475,14 +482,4 @@ frappe.views.Calendar = class Calendar {
return { args: args, field_map: this.field_map };
}
fix_end_date_for_event_render(event) {
if (event.allDay) {
// We use inclusive end dates. This workaround fixes the rendering of events
event.start = event.start ? $.fullCalendar.moment(event.start).stripTime() : null;
event.end = event.end
? $.fullCalendar.moment(event.end).add(1, "day").stripTime()
: null;
}
}
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,52 +1,53 @@
.fc-unthemed {
.fc-theme-standard {
padding: 20px;
color: var(--text-light) !important;
}
.fc-theme-standard a {
color: var(--text-light);
}
.fc-toolbar {
// padding-top: 30px;
padding-bottom: 15px;
margin-bottom: 0px !important;
}
.fc-toolbar-chunk div {
display: flex;
}
.fc-view-container {
margin-left: -1px;
margin-right: -1px;
}
.fc-head-container {
// border-top: 0 !important;
border: none !important;
}
th.fc-widget-header {
border: none !important;
th.fc-col-header-cell {
color: var(--gray-500);
font-weight: 600;
}
// th {
// border: none !important;
// }
.fc-unthemed td,
.fc-unthemed hr,
.fc-unthemed thead,
.fc-unthemed tbody,
.fc-unthemed .fc-row,
.fc-unthemed .fc-popover {
.fc-theme-standard td,
.fc-theme-standard hr,
.fc-theme-standard thead,
.fc-theme-standard tbody,
.fc-theme-standard .fc-row,
.fc-theme-standard .fc-popover {
border-color: var(--gray-300) !important;
}
.fc-unthemed td.fc-sun {
.fc-theme-standard td.fc-day-sun {
background: var(--highlight-color);
}
.fc-unthemed .fc-today {
.fc-theme-standard .fc-day-today {
background-color: var(--fg-color) !important;
.fc-day-number {
background-color: var(--blue-500);
.fc-daygrid-day-number {
background-color: var(--gray-700);
border-radius: 50%;
color: $white;
height: 22px;
@ -55,61 +56,64 @@ th.fc-widget-header {
display: flex;
justify-content: center;
text-align: center;
padding: 0;
}
}
// .fc-highlight {
// background-color: $light-yellow !important;
// }
.fc-event {
// border: 1px solid #E8DDFF; /* default BORDER color */
background-color: #e8ddff;
background-color: rgb(237, 246, 253);
border: none !important;
}
.fc-event-main .fc-event-time {
display: none;
}
.fc-time-grid-event {
border: none !important;
}
// @media (max-width: $screen-xs) {
// .fc-scroller {
// height: auto !important;
// }
// }
.fc-day-top {
padding: 5px 10px 0 0 !important;
}
.fc-day {
margin-left: 10px;
.fc-day-number {
.fc-daygrid-day-top {
margin: 5px 0 0 10px;
flex-direction: row !important;
.fc-daygrid-day-number {
float: left !important;
}
}
th.fc-day-header {
th.fc-col-header-cell {
padding: 10px 12px 10px 0 !important;
text-transform: uppercase;
font-size: 12px;
}
.fc-event-container .fc-content {
.fc-daygrid-dot-event {
padding: 3px;
display: flex;
flex-direction: column-reverse;
align-items: normal;
color: rgb(0, 112, 204) !important;
.fc-time {
.fc-event-time {
font-weight: normal;
margin-top: 2px;
}
.fc-title {
.fc-event-title {
font-weight: 600;
}
.fc-daygrid-event-dot {
display: none;
}
}
.fc-left h2 {
font-size: $font-size-lg;
.fc-toolbar-title {
font-size: $font-size-lg !important;
font-weight: 500;
line-height: 28px;
height: 28px;
@ -120,9 +124,6 @@ th.fc-day-header {
font-size: var(--text-md) !important;
outline: none !important;
text-transform: capitalize;
// .fc-icon {
// top: -1px !important;
// }
}
.fc-right button {
@ -131,29 +132,51 @@ th.fc-day-header {
.fc-left button {
width: 80px;
// svg {
// margin-right: 5px;
// }
}
.fc-state-active {
.fc-button-active {
box-shadow: none !important;
background: var(--gray-500) !important;
color: var(--fg-color) !important;
z-index: 0 !important;
}
.fc-day-grid-event {
//override default and fc-button styles
.fc-dayGridMonth-button,
.fc-dayGridWeek-button,
.fc-dayGridDay-button {
border: none !important;
border-radius: 0;
background-color: var(--control-bg);
color: var(--text-color);
}
.fc-dayGridMonth-button {
border-top-left-radius: var(--border-radius) !important;
border-bottom-left-radius: var(--border-radius) !important;
}
.fc-dayGridDay-button {
border-top-right-radius: var(--border-radius) !important;
border-bottom-right-radius: var(--border-radius) !important;
}
.fc-prev-button {
margin-right: 10px !important;
}
.fc-next-button {
margin-left: 10px;
}
.fc-today-button {
margin-right: 10px;
border-radius: var(--border-radius) !important;
}
.fc-daygrid-event {
border: none !important;
margin: 5px 4px 0 !important;
padding: 1px 5px !important;
}
// .result .footnote-area {
// padding: 15px 10px 0 30px;
// }
.fc-time-grid .fc-slats .fc-minor td {
border-top-style: none !important;
}
@ -185,7 +208,6 @@ th.fc-day-header {
.fc-day-grid {
border-bottom: 1px solid var(--gray-300);
// height: 2em !important;
}
.fc-divider {

View file

@ -892,12 +892,12 @@ class TestAddNewUser(BaseTestCommands):
class TestBenchBuild(BaseTestCommands):
def test_build_assets_size_check(self):
with cli(frappe.commands.utils.build, "--force --production") as result:
with cli(frappe.commands.utils.build, "--force --production --app frappe") as result:
self.assertEqual(result.exit_code, 0)
self.assertEqual(result.exception, None)
CURRENT_SIZE = 3.5 # MB
JS_ASSET_THRESHOLD = 0.1
CURRENT_SIZE = 3.3 # MB
JS_ASSET_THRESHOLD = 0.05
hooks = frappe.get_hooks()
default_bundle = hooks["app_include_js"]

View file

@ -22,6 +22,11 @@
"dependencies": {
"@editorjs/editorjs": "^2.28.2",
"@frappe/esbuild-plugin-postcss2": "^0.1.3",
"@fullcalendar/core": "^6.1.11",
"@fullcalendar/daygrid": "^6.1.11",
"@fullcalendar/list": "^6.1.11",
"@fullcalendar/timegrid": "^6.1.11",
"@fullcalendar/interaction": "^6.1.11",
"@headlessui/vue": "^1.7.16",
"@popperjs/core": "^2.11.2",
"@redis/client": "^1.5.8",
@ -31,7 +36,7 @@
"@vue/component-compiler": "^4.2.4",
"@vueuse/core": "^9.5.0",
"ace-builds": "^1.4.8",
"air-datepicker": "github:frappe/air-datepicker",
"air-datepicker": "git+https://github.com/frappe/air-datepicker",
"autoprefixer": "10",
"awesomplete": "^1.1.5",
"bootstrap": "4.6.2",
@ -87,4 +92,4 @@
"bufferutil": "^4.0.8",
"utf-8-validate": "^6.0.3"
}
}
}

View file

@ -55,6 +55,35 @@
stylus "^0.x"
tmp "^0.2.1"
"@fullcalendar/core@^6.1.11":
version "6.1.11"
resolved "https://registry.yarnpkg.com/@fullcalendar/core/-/core-6.1.11.tgz#f9630e83ae977e774992507635b1e7af4c339d37"
integrity sha512-TjG7c8sUz+Vkui2FyCNJ+xqyu0nq653Ibe99A66LoW95oBo6tVhhKIaG1Wh0GVKymYiqAQN/OEdYTuj4ay27kA==
dependencies:
preact "~10.12.1"
"@fullcalendar/daygrid@^6.1.11", "@fullcalendar/daygrid@~6.1.11":
version "6.1.11"
resolved "https://registry.yarnpkg.com/@fullcalendar/daygrid/-/daygrid-6.1.11.tgz#83a5d4a94c314cf3a14b06bebba03b1b40e6d2ba"
integrity sha512-hF5jJB7cgUIxWD5MVjj8IU407HISyLu7BWXcEIuTytkfr8oolOXeCazqnnjmRbnFOncoJQVstTtq6SIhaT32Xg==
"@fullcalendar/interaction@^6.1.11":
version "6.1.11"
resolved "https://registry.yarnpkg.com/@fullcalendar/interaction/-/interaction-6.1.11.tgz#baa3beec8f5c489fb6904973b175a5f4797abdf3"
integrity sha512-ynOKjzuPwEAMgTQ6R/Z2zvzIIqG4p8/Qmnhi1q0vzPZZxSIYx3rlZuvpEK2WGBZZ1XEafDOP/LGfbWoNZe+qdg==
"@fullcalendar/list@^6.1.11":
version "6.1.11"
resolved "https://registry.yarnpkg.com/@fullcalendar/list/-/list-6.1.11.tgz#4cd23700ea48b382b37387e29a706f2da692e174"
integrity sha512-9Qx8uvik9pXD12u50FiHwNzlHv4wkhfsr+r03ycahW7vEeIAKCsIZGTkUfFP+96I5wHihrfLazu1cFQG4MPiuw==
"@fullcalendar/timegrid@^6.1.11":
version "6.1.11"
resolved "https://registry.yarnpkg.com/@fullcalendar/timegrid/-/timegrid-6.1.11.tgz#76b2fc4446d1e97819a4395dab4f3a7e44c7a9eb"
integrity sha512-0seUHK/ferH89IeuCvV4Bib0zWjgK0nsptNdmAc9wDBxD/d9hm5Mdti0URJX6bDoRtsSfRDu5XsRcrzwoc+AUQ==
dependencies:
"@fullcalendar/daygrid" "~6.1.11"
"@headlessui/vue@^1.7.16":
version "1.7.16"
resolved "https://registry.yarnpkg.com/@headlessui/vue/-/vue-1.7.16.tgz#bdc9d32d329248910325539b99e6abfce0c69f89"
@ -398,9 +427,9 @@ acorn@^7.1.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
"air-datepicker@github:frappe/air-datepicker":
"air-datepicker@git+https://github.com/frappe/air-datepicker":
version "2.2.3"
resolved "https://codeload.github.com/frappe/air-datepicker/tar.gz/ed37b94d95c68d8544357e330be0c89d044a3eea"
resolved "git+https://github.com/frappe/air-datepicker#ed37b94d95c68d8544357e330be0c89d044a3eea"
dependencies:
jquery ">=2.0.0 <4.0.0"
@ -2666,6 +2695,11 @@ postcss@^7.0.36:
picocolors "^0.2.1"
source-map "^0.6.1"
preact@~10.12.1:
version "10.12.1"
resolved "https://registry.yarnpkg.com/preact/-/preact-10.12.1.tgz#8f9cb5442f560e532729b7d23d42fd1161354a21"
integrity sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==
"prettier@^1.18.2 || ^2.0.0":
version "2.8.8"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"