pre-img swap
This commit is contained in:
@@ -280,42 +280,38 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="calendar-view">
|
||||
<div class="row">
|
||||
{# ── OTS FullCalendar container (replaces legacy CalendarContainer visually) ── #}
|
||||
<div id="ots-fullcalendar"></div>
|
||||
|
||||
{# Legacy container kept hidden so Xibo bundle doesn't error.
|
||||
NOTE: id="Calendar" is intentionally omitted to prevent the Xibo bundle
|
||||
from initialising the legacy calendar and throwing _loadEvents errors.
|
||||
The data-agenda-link is preserved on CalendarContainer for our event-click handler. #}
|
||||
<div class="row d-none" id="ots-legacy-calendar-row">
|
||||
<div id="CalendarContainer"
|
||||
data-agenda-link="{{ url_for("schedule.events", {id: ':id'}) }}"
|
||||
data-calendar-type="{{ settings.CALENDAR_TYPE }}" class="col-sm-12"
|
||||
data-default-lat="{{ defaultLat }}"
|
||||
data-default-long="{{ defaultLong }}">
|
||||
<div class="calendar-view" id="Calendar"></div>
|
||||
<div class="calendar-view" id="ots-legacy-calendar-stub"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="cal-legend">
|
||||
<ul>
|
||||
<li class="event-always"><span
|
||||
class="fa fa-retweet"></span> {% trans "Always showing" %}</li>
|
||||
<li class="event-info"><span
|
||||
class="fa fa-desktop"></span> {% trans "Single Display" %}</li>
|
||||
<li class="event-success"><span
|
||||
class="fa fa-desktop"></span> {% trans "Multi Display" %}</li>
|
||||
<li class="event-important"><span
|
||||
class="fa fa-bullseye"></span> {% trans "Priority" %}</li>
|
||||
<li class="event-special"><span
|
||||
class="fa fa-repeat"></span> {% trans "Recurring" %}</li>
|
||||
<li class="event-inverse"><span
|
||||
class="fa fa-lock"></span> {% trans "View Only" %}</li>
|
||||
<li class="event-command"><span
|
||||
class="fa fa-wrench"></span> {% trans "Command" %}</li>
|
||||
<li class="event-interrupt"><span
|
||||
class="fa fa-hand-paper"></span> {% trans "Interrupt" %}</li>
|
||||
<li class="event-geo-location"><span
|
||||
class="fa fa-map-marker"></span> {% trans "Geo Location" %}</li>
|
||||
<li class="event-action"><span
|
||||
class="fa fa-paper-plane "></span> {% trans "Interactive Action" %}
|
||||
</li>
|
||||
<li class="event-sync"><span
|
||||
class="fa fa-refresh"></span> {% trans "Synchronised" %}</li>
|
||||
<li><span class="fa fa-retweet" style="color:#6366f1"></span> {% trans "Always showing" %}</li>
|
||||
<li><span class="fa fa-desktop" style="color:#0ea5e9"></span> {% trans "Single Display" %}</li>
|
||||
<li><span class="fa fa-desktop" style="color:#10b981"></span> {% trans "Multi Display" %}</li>
|
||||
<li><span class="fa fa-bullseye" style="color:#ef4444"></span> {% trans "Priority" %}</li>
|
||||
<li><span class="fa fa-repeat" style="color:#8b5cf6"></span> {% trans "Recurring" %}</li>
|
||||
<li><span class="fa fa-lock" style="color:#64748b"></span> {% trans "View Only" %}</li>
|
||||
<li><span class="fa fa-wrench" style="color:#f59e0b"></span> {% trans "Command" %}</li>
|
||||
<li><span class="fa fa-hand-paper" style="color:#f97316"></span> {% trans "Interrupt" %}</li>
|
||||
<li><span class="fa fa-map-marker" style="color:#14b8a6"></span> {% trans "Geo Location" %}</li>
|
||||
<li><span class="fa fa-paper-plane" style="color:#ec4899"></span> {% trans "Interactive Action" %}</li>
|
||||
<li><span class="fa fa-refresh" style="color:#06b6d4"></span> {% trans "Synchronised" %}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -356,14 +352,211 @@
|
||||
{# Add page source code bundle ( JS ) #}
|
||||
<script src="{{ theme.rootUri() }}dist/leaflet.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
|
||||
<script src="{{ theme.rootUri() }}dist/pages/schedule-page.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
|
||||
|
||||
{# ── FullCalendar v6 (inlined to bypass MIME issues with /custom/ paths) ── #}
|
||||
<script nonce="{{ cspNonce }}">
|
||||
{% include "fullcalendar-lib.twig" %}
|
||||
</script>
|
||||
|
||||
{# ── OTS FullCalendar integration ── #}
|
||||
<script nonce="{{ cspNonce }}">
|
||||
$(function() {
|
||||
// OTS calendar nav arrows drive FullCalendar directly (do NOT proxy to
|
||||
// Xibo's data-calendar-nav buttons — those trigger the legacy calendar
|
||||
// and cause _loadEvents / options.events errors).
|
||||
$('#ots-cal-prev').on('click', function() {
|
||||
$('button[data-calendar-nav="prev"]').trigger('click');
|
||||
if (otsCalendar) { otsCalendar.prev(); }
|
||||
});
|
||||
$('#ots-cal-next').on('click', function() {
|
||||
$('button[data-calendar-nav="next"]').trigger('click');
|
||||
if (otsCalendar) { otsCalendar.next(); }
|
||||
});
|
||||
|
||||
// ── FullCalendar initialisation ──
|
||||
var otsCalendar = null;
|
||||
var calendarTabActive = false;
|
||||
|
||||
// Map Xibo event data to FullCalendar event object
|
||||
function mapEvent(item) {
|
||||
var title = item.schedule || item.campaign || item.command || item.layout || 'Event #' + item.eventId;
|
||||
var cls = 'fc-event-single';
|
||||
|
||||
// Priority flag overrides everything
|
||||
if (item.isPriority && parseInt(item.isPriority, 10) > 0) {
|
||||
cls = 'fc-event-priority';
|
||||
}
|
||||
// Command events
|
||||
else if (parseInt(item.eventTypeId, 10) === 2) {
|
||||
cls = 'fc-event-command';
|
||||
}
|
||||
// Interrupt events
|
||||
else if (parseInt(item.eventTypeId, 10) === 4) {
|
||||
cls = 'fc-event-interrupt';
|
||||
}
|
||||
// Action events
|
||||
else if (parseInt(item.eventTypeId, 10) === 6) {
|
||||
cls = 'fc-event-action';
|
||||
}
|
||||
// "Always" daypart
|
||||
else if (item.isAlways && parseInt(item.isAlways, 10) === 1) {
|
||||
cls = 'fc-event-always';
|
||||
}
|
||||
// Geo-aware
|
||||
else if (item.isGeoAware && parseInt(item.isGeoAware, 10) === 1) {
|
||||
cls = 'fc-event-geo';
|
||||
}
|
||||
// Recurring
|
||||
else if (item.recurrenceType && item.recurrenceType !== '' && item.recurrenceType !== 'null') {
|
||||
cls = 'fc-event-recurring';
|
||||
}
|
||||
// Multi-display vs single
|
||||
else if (item.displayGroups && item.displayGroups.length > 1) {
|
||||
cls = 'fc-event-multi';
|
||||
}
|
||||
|
||||
// Sync events
|
||||
if (item.syncGroupId && parseInt(item.syncGroupId, 10) > 0) {
|
||||
cls = 'fc-event-sync';
|
||||
}
|
||||
|
||||
return {
|
||||
id: item.eventId,
|
||||
title: title,
|
||||
start: item.fromDt,
|
||||
end: item.toDt,
|
||||
allDay: (item.isAlways && parseInt(item.isAlways, 10) === 1),
|
||||
className: cls,
|
||||
extendedProps: item
|
||||
};
|
||||
}
|
||||
|
||||
// Fetch events from Xibo schedule.search API
|
||||
function fetchEvents(info, successCallback, failureCallback) {
|
||||
var filterData = {
|
||||
fromDt: info.startStr,
|
||||
toDt: info.endStr
|
||||
};
|
||||
|
||||
// Pull in active filter values
|
||||
var $filter = $('#schedule-filter');
|
||||
if ($filter.length) {
|
||||
$filter.find(':input[name]').each(function() {
|
||||
var $el = $(this);
|
||||
var name = $el.attr('name');
|
||||
var val = $el.val();
|
||||
if (val && val !== '' && name !== 'fromDt' && name !== 'toDt') {
|
||||
filterData[name] = val;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: scheduleSearchUrl,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
data: filterData,
|
||||
success: function(response) {
|
||||
var events = [];
|
||||
var rows = response.data || response || [];
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
events.push(mapEvent(rows[i]));
|
||||
}
|
||||
successCallback(events);
|
||||
},
|
||||
error: function(xhr) {
|
||||
console.warn('OTS: FullCalendar event fetch failed', xhr.status);
|
||||
failureCallback(xhr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle event click → open Xibo edit form
|
||||
function handleEventClick(info) {
|
||||
var ev = info.event;
|
||||
var props = ev.extendedProps || {};
|
||||
if (props.eventId) {
|
||||
var url = $('#CalendarContainer').data('agenda-link');
|
||||
if (url) {
|
||||
url = url.replace(':id', props.eventId);
|
||||
XiboFormRender(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialise FullCalendar on first Calendar-tab show
|
||||
function initFullCalendar() {
|
||||
if (otsCalendar) return;
|
||||
var el = document.getElementById('ots-fullcalendar');
|
||||
if (!el || typeof FullCalendar === 'undefined') return;
|
||||
|
||||
otsCalendar = new FullCalendar.Calendar(el, {
|
||||
initialView: 'dayGridMonth',
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
|
||||
},
|
||||
buttonText: {
|
||||
today: '{{ "Today"|trans }}',
|
||||
month: '{{ "Month"|trans }}',
|
||||
week: '{{ "Week"|trans }}',
|
||||
day: '{{ "Day"|trans }}',
|
||||
list: '{{ "List"|trans }}'
|
||||
},
|
||||
firstDay: 1,
|
||||
nowIndicator: true,
|
||||
navLinks: true,
|
||||
editable: false,
|
||||
selectable: false,
|
||||
eventDisplay: 'block',
|
||||
dayMaxEvents: 4,
|
||||
height: 'auto',
|
||||
events: fetchEvents,
|
||||
eventClick: handleEventClick,
|
||||
loading: function(isLoading) {
|
||||
$('#calendar-progress').toggle(isLoading);
|
||||
}
|
||||
});
|
||||
|
||||
otsCalendar.render();
|
||||
}
|
||||
|
||||
// Init when Calendar tab is shown
|
||||
$('a[data-toggle="tab"][href="#calendar-view"]').on('shown.bs.tab', function() {
|
||||
calendarTabActive = true;
|
||||
initFullCalendar();
|
||||
// Hide the Xibo calendar header (we use FC's built-in toolbar)
|
||||
$('.xibo-calendar-header-container').hide();
|
||||
});
|
||||
|
||||
// Re-show Xibo header when switching back to Grid; suspend FC interception
|
||||
$('a[data-toggle="tab"][href="#grid-view"]').on('shown.bs.tab', function() {
|
||||
$('.xibo-calendar-header-container').show();
|
||||
calendarTabActive = false;
|
||||
});
|
||||
|
||||
// Intercept Xibo's data-calendar-nav buttons when calendar tab is active
|
||||
// and drive FullCalendar directly, preventing the legacy calendar from being invoked.
|
||||
$(document).on('click', '[data-calendar-nav]', function(e) {
|
||||
if (!calendarTabActive || !otsCalendar) return; // Grid tab active — let Xibo handle it
|
||||
e.stopImmediatePropagation();
|
||||
var nav = $(this).data('calendar-nav');
|
||||
if (nav === 'prev') { otsCalendar.prev(); }
|
||||
else if (nav === 'next') { otsCalendar.next(); }
|
||||
else if (nav === 'today') { otsCalendar.today(); }
|
||||
});
|
||||
|
||||
// Refetch events when filter changes
|
||||
$('#schedule-filter').on('change', ':input', function() {
|
||||
if (otsCalendar) {
|
||||
otsCalendar.refetchEvents();
|
||||
}
|
||||
});
|
||||
|
||||
// If Calendar tab is active by default (URL hash), init immediately
|
||||
if (window.location.hash === '#calendar-view' || $('#calendar-tab').hasClass('active')) {
|
||||
calendarTabActive = true;
|
||||
setTimeout(initFullCalendar, 100);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user