From 0975d00ee70ab3c3a807af84f71a0c09c7f509ea Mon Sep 17 00:00:00 2001 From: Markus Beckschulte Date: Fri, 7 Jun 2024 15:42:48 +0200 Subject: [PATCH 1/3] add small seat previews, preselected date to current time --- js/base/style.css | 26 +++++- js/views/bookings.js | 26 ++++-- js/views/zone.js | 176 +++++++++++++++++++++++++++++++-------- warp/config.py | 7 ++ warp/templates/zone.html | 31 +++++-- warp/view.py | 51 +++++++++--- 6 files changed, 254 insertions(+), 63 deletions(-) diff --git a/js/base/style.css b/js/base/style.css index 8023ae2..542cfef 100644 --- a/js/base/style.css +++ b/js/base/style.css @@ -196,10 +196,32 @@ NAV { } .zonemap_help { - position: fixed; - right: 0px; color: #9e9e9e; cursor: pointer; + margin-left: auto; +} + +.zonemap_menu_left_bottom { + position: fixed; + right: 0px; + display: flex; + flex-direction: column; + justify-content: flex-end; + overflow: auto; + padding: 10px; + z-index: 1000; +} + +.zonemap_menu_right_bottom { + position: fixed; + right: 0px; + bottom: 0px; + display: flex; + flex-direction: column; + justify-content: flex-end; + overflow: auto; + padding: 10px; + z-index: 1000; } .zone_action_btn { diff --git a/js/views/bookings.js b/js/views/bookings.js index cfe9f74..a1fdbfa 100644 --- a/js/views/bookings.js +++ b/js/views/bookings.js @@ -72,10 +72,16 @@ document.addEventListener("DOMContentLoaded", function(e) { showClearBtn: true, format: "yyyy-mm-dd", onClose: function() { - success({ - fromTS: fromDatePicker.value? Math.round(Date.parse(fromDatePicker.value)/1000): null, - toTS: toDatePicker.value? Math.round(Date.parse(toDatePicker.value)/1000)+24*3600-1: null - }); + let fromTS = fromDatePicker.value? Math.round(Date.parse(fromDatePicker.value)/1000): null + let toTS = toDatePicker.value? Math.round(Date.parse(toDatePicker.value)/1000)+24*3600-1: null + if (fromTS !== null && toTS !== null) { + success({ + fromTS: fromDatePicker.value? Math.round(Date.parse(fromDatePicker.value)/1000): null, + toTS: toDatePicker.value? Math.round(Date.parse(toDatePicker.value)/1000)+24*3600-1: null + }); + } else { + cancel(); + }; } }; @@ -189,10 +195,14 @@ document.addEventListener("DOMContentLoaded", function(e) { } else { columns.push( - {title:TR("Time"), field: "fromTS", width: 275, - formatter:mergedTsFormatter, - headerFilter:mergedDateFilterEditor, - headerFilterFunc:function(){} }, + { + title: TR("Time"), + field: "fromTS", + width: 275, + formatter: mergedTsFormatter, + headerFilter: mergedDateFilterEditor, + headerFilterFunc: function(){} + }, ); columns.splice(0,0, diff --git a/js/views/zone.js b/js/views/zone.js index 2c63ac3..d10b507 100644 --- a/js/views/zone.js +++ b/js/views/zone.js @@ -9,7 +9,7 @@ import BookAs from './modules/bookas.js'; import noUiSlider from 'nouislider'; import "./css/zone/nouislider_materialize.scss"; -function downloadSeatData(seatFactory) { +function downloadSeatData(seatFactory, previewCheckbox, zoneMap) { var url = window.warpGlobals.URLs['getSeat']; @@ -22,6 +22,10 @@ function downloadSeatData(seatFactory) { seatFactory.setSeatsData(v.response); seatFactory.updateAllStates( getSelectedDates()); + + if (previewCheckbox.checked) { + handleSmallPreviews(seatFactory, zoneMap); + } }) } @@ -75,7 +79,7 @@ function initSeats() { var seatFactory = new WarpSeatFactory( window.warpGlobals.URLs['seatSprite'], - "zonemap", + "zonemap-seats", window.warpGlobals.login); // register WarpSeats for updates @@ -94,48 +98,129 @@ function initSeats() { return seatFactory; } -function initSeatPreview(seatFactory) { - - var zoneMap = document.getElementById("zonemap"); - - seatFactory.on( 'mouseover', function() { +function initPreviewDiv(seat_name, position, with_title) { - var previewDiv = document.createElement("div"); - previewDiv.className = 'seat_preview'; + var previewDiv = document.createElement("div"); + previewDiv.className = 'seat_preview'; + if (with_title) { var previewTitle = previewDiv.appendChild(document.createElement("div")); - previewTitle.innerText = TR("Seat %{seat_name}",{seat_name: this.getName()}); + previewTitle.innerText = TR("Seat %{seat_name}",{seat_name: seat_name}); previewTitle.className = "seat_preview_title"; + } + + previewDiv.style.right = position.right; + previewDiv.style.left = position.left; + previewDiv.style.top = position.top; + previewDiv.style.bottom = position.bottom; + + return previewDiv; +} + +const all_small_seat_previews = [] +function initSmallPreviews(seatFactory, zoneMap) { + for (const value in seatFactory.instances) { + var seat = seatFactory.instances[value] + if (seat.otherZone) { + continue; + } + // position of the frame + var pands = seat.getPositionAndSize(); + var seat_name = seat.getName(); + var bookings = seat.getBookings(); + + var position = { + left: pands.x + "px", + right: "", + top: (pands.y + pands.size * 0.70) + "px", + bottom: "" + }; + + var previewDiv = initPreviewDiv(seat_name, position, true); + + var header = null + if (bookings.length) { + header = previewDiv.appendChild(document.createElement("span")); + header.appendChild(document.createTextNode(bookings[0].username)); + header.className = "seat_preview_header"; + } + + zoneMap.appendChild(previewDiv); + + all_small_seat_previews.push({ + previewDiv: previewDiv, + header: header, + seat: seat + }) + } +} + +function updateAllSmallPreviews() { + for (const d of all_small_seat_previews) { + var bookings = d.seat.getBookings(); + if (d.header !== null) { + d.header.remove() + d.header = null + } + if (bookings.length) { + d.header = d.previewDiv.appendChild(document.createElement("span")) + d.header.appendChild(document.createTextNode(bookings[0].username)); + d.header.className = "seat_preview_header"; + } + } +} + +function handleSmallPreviews(seatFactory, zoneMap) { + if (all_small_seat_previews.length !== 0) { + updateAllSmallPreviews() + } else { + initSmallPreviews(seatFactory, zoneMap) + } +} + +function initSeatPreview(seatFactory, previewCheckbox, zoneMap) { + let currentPreview = null + seatFactory.on( 'mouseover', function() { + var assignments = Object.values(this.getAssignments()); // position of the frame var pands = this.getPositionAndSize(); + var seat_name = this.getName(); + var bookings = this.getBookings(); + + if (previewCheckbox.checked && !assignments.length && !bookings.length) { + return + } var parentWidth = zoneMap.clientWidth; var clientPosX = pands.x - zoneMap.scrollLeft; + var position = new Object(); + if (clientPosX < parentWidth / 2) { - previewDiv.style.right = ""; - previewDiv.style.left = (pands.x + pands.size * 0.70) + "px"; + position.right = ""; + position.left = (pands.x + pands.size * 0.70) + "px"; } else { - previewDiv.style.left = ""; - previewDiv.style.right = (parentWidth - pands.x - pands.size * 0.30) + "px"; + position.left = ""; + position.right = (parentWidth - pands.x - pands.size * 0.30) + "px"; } var parentHeight = zoneMap.clientHeight; var clientPosY = pands.y; - if (clientPosY < parentHeight / 2) { - previewDiv.style.top = (pands.y + pands.size * 0.70) + "px"; - previewDiv.style.bottom = ""; + if (previewCheckbox.checked || (clientPosY > parentHeight / 2)) { + position.top = ""; + position.bottom = (parentHeight - pands.y - pands.size * 0.30) + "px"; } else { - previewDiv.style.top = ""; - previewDiv.style.bottom = (parentHeight - pands.y - pands.size * 0.30) + "px"; + position.top = (pands.y + pands.size * 0.70) + "px"; + position.bottom = ""; } + var previewDiv = initPreviewDiv(seat_name, position, !previewCheckbox.checked); + // content of the frame - var assignments = Object.values(this.getAssignments()); if (assignments.length) { var header = previewDiv.appendChild(document.createElement("span")); @@ -149,7 +234,6 @@ function initSeatPreview(seatFactory) { } } - var bookings = this.getBookings(); if (bookings.length) { var header = previewDiv.appendChild(document.createElement("span")) @@ -178,12 +262,31 @@ function initSeatPreview(seatFactory) { } zoneMap.appendChild(previewDiv); + currentPreview = previewDiv + }); + + previewCheckbox.addEventListener("change", function() { + if (previewCheckbox.checked) { + initSmallPreviews(seatFactory, zoneMap); + } else { + var previewDivs = document.querySelectorAll('.seat_preview'); + for (const d of previewDivs) { + d.remove(); + } + all_small_seat_previews.length = 0; + } }); + var slider = document.getElementById('timeslider'); + slider.noUiSlider.on('update', updateAllSmallPreviews); + for (var e of document.getElementsByClassName('date_checkbox')) { + e.addEventListener('change', updateAllSmallPreviews); + } + seatFactory.on( 'mouseout', function() { - var previewDivs = document.getElementsByClassName('seat_preview'); - for (var d of previewDivs) { - d.remove(); + if (currentPreview !== null) { + currentPreview.remove() + currentPreview = null } }); @@ -252,7 +355,7 @@ function initAssignedSeatsModal(seat) { } -function initActionMenu(seatFactory) { +function initActionMenu(seatFactory, previewCheckbox, zoneMap) { if (window.warpGlobals.isZoneViewer) return; @@ -451,9 +554,9 @@ function initActionMenu(seatFactory) { else WarpModal.getInstance().open(TR("Warning"),msg); - downloadSeatData(seatFactory); + downloadSeatData(seatFactory, previewCheckbox, zoneMap); }).catch( (value) => { - downloadSeatData(seatFactory); + downloadSeatData(seatFactory, previewCheckbox, zoneMap); }); }; @@ -469,9 +572,14 @@ function initDateSelectorStorage() { var storage = window.sessionStorage; - // restore values from session storage - var restoredSelections = storage.getItem('zoneSelections'); - restoredSelections = restoredSelections? JSON.parse(restoredSelections): window.warpGlobals['defaultSelectedDates']; + var restoreSelectedDates = window.warpGlobals['restoreSelectedDates'] + if (restoreSelectedDates) { + // restore values from session storage + var restoredSelections = storage.getItem('zoneSelections'); + restoredSelections = restoredSelections? JSON.parse(restoredSelections): window.warpGlobals['defaultSelectedDates']; + } else { + var restoredSelections = window.warpGlobals['defaultSelectedDates']; + } let cleanCBSelections = []; // used to clean up the list of checkboxes doesn't exist anymore @@ -622,12 +730,14 @@ document.addEventListener("DOMContentLoaded", function() { initShiftSelectDates(); var seatFactory = initSeats(); - initSeatPreview(seatFactory); - initActionMenu(seatFactory); + const previewCheckbox = document.getElementById("preview-checkbox"); + const zoneMap = document.getElementById("zonemap"); + initSeatPreview(seatFactory, previewCheckbox, zoneMap); + initActionMenu(seatFactory, previewCheckbox, zoneMap); initZoneHelp(); initZoneSidepanel(); - downloadSeatData(seatFactory); + downloadSeatData(seatFactory, previewCheckbox, zoneMap); if (window.warpGlobals.isZoneAdmin) { ZoneUserData.init(); diff --git a/warp/config.py b/warp/config.py index 04beb80..1244059 100644 --- a/warp/config.py +++ b/warp/config.py @@ -59,6 +59,13 @@ class DefaultSettings(object): # MELLON_ENDPOINT # MELLON_DEFAULT_GROUP + # Date/Time defaults + PRESELECTED_DATES_STRATEGY = "tomorrow" + RESTORE_SELECTED_DATES = True + PRESELECTED_TIMES_STRATEGY = "predefined_timespan" + PRESELECTED_TIMES_START = 9 + PRESELECTED_TIMES_END = 17 + class DevelopmentSettings(DefaultSettings): DATABASE = "postgresql://postgres:postgres_password@127.0.0.1:5432/postgres" diff --git a/warp/templates/zone.html b/warp/templates/zone.html index 8ad9630..db94cac 100644 --- a/warp/templates/zone.html +++ b/warp/templates/zone.html @@ -9,6 +9,7 @@ window.warpGlobals.URLs['seatSprite'] = "{{ url_for('static',filename='images/seat_icons.png') }}"; window.warpGlobals['defaultSelectedDates'] = {{ defaultSelectedDates | tojson }}; + window.warpGlobals['restoreSelectedDates'] = {{ restoreSelectedDates }}; {% if isZoneAdmin %} window.warpGlobals.isZoneAdmin = true; @@ -143,14 +144,15 @@

-
- - -
+
+ + +
+ +
+ close +
-
- close -
@@ -178,15 +180,26 @@

- -
+
+
+
+
+ +
+
+ +
+
{% endblock %} diff --git a/warp/view.py b/warp/view.py index 639daac..fbe139d 100644 --- a/warp/view.py +++ b/warp/view.py @@ -1,3 +1,6 @@ +from datetime import datetime +import json + import flask from warp.db import * @@ -70,16 +73,40 @@ def zone(zid): if zoneRole is None: flask.abort(403) + + preselected_times_strategy = flask.current_app.config['PRESELECTED_TIMES_STRATEGY'] + match preselected_times_strategy: + case 'now': + now = datetime.now() + last_quarter_minute = now.minute // 15 + slider_start = (now.hour * 3600) + (last_quarter_minute * 60 * 15) + slider_end = slider_start + (4*3600) + defaultSelectedDates = { + "slider": [slider_start, slider_end] + } + case 'predefined_timespan': + preselected_times_start = flask.current_app.config['PRESELECTED_TIMES_START'] + preselected_times_end = flask.current_app.config['PRESELECTED_TIMES_END'] + defaultSelectedDates = { + "slider": [preselected_times_start*3600, preselected_times_end*3600] + } + + preselected_dates_strategy = flask.current_app.config['PRESELECTED_DATES_STRATEGY'] + + match preselected_dates_strategy: + case 'tomorrow': + nextWeek = utils.getNextWeek() + for d in nextWeek[1:]: + if not d['isWeekend']: + defaultSelectedDates['cb'] = [d['timestamp']] + break + case 'today': + nextWeek = utils.getNextWeek() + for d in nextWeek: + if not d['isWeekend']: + defaultSelectedDates['cb'] = [d['timestamp']] + break - nextWeek = utils.getNextWeek() - defaultSelectedDates = { - "slider": [9*3600, 17*3600] - } - - for d in nextWeek[1:]: - if not d['isWeekend']: - defaultSelectedDates['cb'] = [d['timestamp']] - break if zoneRole <= ZONE_ROLE_ADMIN: zoneRole = {'isZoneAdmin': True} @@ -89,13 +116,15 @@ def zone(zid): zoneRole = {'isZoneViewer': True} else: raise Exception('Undefined role') - + + restore_selected_dates = json.dumps(flask.current_app.config['RESTORE_SELECTED_DATES']) return flask.render_template('zone.html', **zoneRole, zid = zid, nextWeek=nextWeek, - defaultSelectedDates=defaultSelectedDates) + defaultSelectedDates=defaultSelectedDates, + restoreSelectedDates=restore_selected_dates) @bp.route("/zone/image/") def zoneImage(zid): From 83ae95b225fdadb2a18cf445bc32a8d18694ac6b Mon Sep 17 00:00:00 2001 From: Markus Beckschulte <73887906+markus-96@users.noreply.github.com> Date: Tue, 11 Jun 2024 15:41:48 +0200 Subject: [PATCH 2/3] add semicolons --- js/views/bookings.js | 4 ++-- js/views/zone.js | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/js/views/bookings.js b/js/views/bookings.js index a1fdbfa..e88a9b0 100644 --- a/js/views/bookings.js +++ b/js/views/bookings.js @@ -72,8 +72,8 @@ document.addEventListener("DOMContentLoaded", function(e) { showClearBtn: true, format: "yyyy-mm-dd", onClose: function() { - let fromTS = fromDatePicker.value? Math.round(Date.parse(fromDatePicker.value)/1000): null - let toTS = toDatePicker.value? Math.round(Date.parse(toDatePicker.value)/1000)+24*3600-1: null + let fromTS = fromDatePicker.value? Math.round(Date.parse(fromDatePicker.value)/1000): null; + let toTS = toDatePicker.value? Math.round(Date.parse(toDatePicker.value)/1000)+24*3600-1: null; if (fromTS !== null && toTS !== null) { success({ fromTS: fromDatePicker.value? Math.round(Date.parse(fromDatePicker.value)/1000): null, diff --git a/js/views/zone.js b/js/views/zone.js index d10b507..11f8fc6 100644 --- a/js/views/zone.js +++ b/js/views/zone.js @@ -117,10 +117,10 @@ function initPreviewDiv(seat_name, position, with_title) { return previewDiv; } -const all_small_seat_previews = [] +const all_small_seat_previews = []; function initSmallPreviews(seatFactory, zoneMap) { for (const value in seatFactory.instances) { - var seat = seatFactory.instances[value] + var seat = seatFactory.instances[value]; if (seat.otherZone) { continue; } @@ -138,7 +138,7 @@ function initSmallPreviews(seatFactory, zoneMap) { var previewDiv = initPreviewDiv(seat_name, position, true); - var header = null + var header = null; if (bookings.length) { header = previewDiv.appendChild(document.createElement("span")); header.appendChild(document.createTextNode(bookings[0].username)); @@ -159,11 +159,11 @@ function updateAllSmallPreviews() { for (const d of all_small_seat_previews) { var bookings = d.seat.getBookings(); if (d.header !== null) { - d.header.remove() - d.header = null + d.header.remove(); + d.header = null; } if (bookings.length) { - d.header = d.previewDiv.appendChild(document.createElement("span")) + d.header = d.previewDiv.appendChild(document.createElement("span")); d.header.appendChild(document.createTextNode(bookings[0].username)); d.header.className = "seat_preview_header"; } @@ -172,14 +172,14 @@ function updateAllSmallPreviews() { function handleSmallPreviews(seatFactory, zoneMap) { if (all_small_seat_previews.length !== 0) { - updateAllSmallPreviews() + updateAllSmallPreviews(); } else { - initSmallPreviews(seatFactory, zoneMap) + initSmallPreviews(seatFactory, zoneMap); } } function initSeatPreview(seatFactory, previewCheckbox, zoneMap) { - let currentPreview = null + let currentPreview = null; seatFactory.on( 'mouseover', function() { var assignments = Object.values(this.getAssignments()); @@ -189,7 +189,7 @@ function initSeatPreview(seatFactory, previewCheckbox, zoneMap) { var bookings = this.getBookings(); if (previewCheckbox.checked && !assignments.length && !bookings.length) { - return + return; } var parentWidth = zoneMap.clientWidth; @@ -236,7 +236,7 @@ function initSeatPreview(seatFactory, previewCheckbox, zoneMap) { if (bookings.length) { - var header = previewDiv.appendChild(document.createElement("span")) + var header = previewDiv.appendChild(document.createElement("span")); header.appendChild(document.createTextNode(TR("Bookings:"))); header.className = "seat_preview_header"; @@ -262,7 +262,7 @@ function initSeatPreview(seatFactory, previewCheckbox, zoneMap) { } zoneMap.appendChild(previewDiv); - currentPreview = previewDiv + currentPreview = previewDiv; }); previewCheckbox.addEventListener("change", function() { @@ -285,8 +285,8 @@ function initSeatPreview(seatFactory, previewCheckbox, zoneMap) { seatFactory.on( 'mouseout', function() { if (currentPreview !== null) { - currentPreview.remove() - currentPreview = null + currentPreview.remove(); + currentPreview = null; } }); From c1bbd368fdff693bd20d1e3c4734e7cd91a3304c Mon Sep 17 00:00:00 2001 From: Markus Beckschulte <73887906+markus-96@users.noreply.github.com> Date: Fri, 21 Jun 2024 11:57:29 +0200 Subject: [PATCH 3/3] linebreaks in small previews --- js/views/zone.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/js/views/zone.js b/js/views/zone.js index 11f8fc6..0094d24 100644 --- a/js/views/zone.js +++ b/js/views/zone.js @@ -140,8 +140,12 @@ function initSmallPreviews(seatFactory, zoneMap) { var header = null; if (bookings.length) { - header = previewDiv.appendChild(document.createElement("span")); - header.appendChild(document.createTextNode(bookings[0].username)); + header = previewDiv.appendChild(document.createElement("div")); + let headerSpan = header.appendChild(document.createElement("span")); + bookings[0].username.split(' ').forEach(function(n) { + headerSpan.appendChild(document.createTextNode(n)); + headerSpan.appendChild(document.createElement('br')); + }) header.className = "seat_preview_header"; } @@ -163,8 +167,12 @@ function updateAllSmallPreviews() { d.header = null; } if (bookings.length) { - d.header = d.previewDiv.appendChild(document.createElement("span")); - d.header.appendChild(document.createTextNode(bookings[0].username)); + d.header = d.previewDiv.appendChild(document.createElement("div")); + let headerSpan = d.header.appendChild(document.createElement("span")); + bookings[0].username.split(' ').forEach(function(n) { + headerSpan.appendChild(document.createTextNode(n)); + headerSpan.appendChild(document.createElement('br')); + }) d.header.className = "seat_preview_header"; } }