-Feature: Ab sofort werden alle Configdateien über eine Example.config.php definiert. über das Dashboard kann nun über den neuen Punkt "Config" die eigentliche config.php bearbeitet werden. Änderungen durch Programmupdates werden jetzt automatisch in der example.config.php definiert und beim nächsten Speichern der config-datei über die Website angepasst.
-Feature: Scrollen der Listen können einzeln abeschalten werden
This commit is contained in:
367
Packmonitor.php
367
Packmonitor.php
@@ -21,175 +21,228 @@ $Epi = new Epirent();
|
||||
<link href="css/sticky-footer.css" rel="stylesheet">
|
||||
<script src="https://kit.fontawesome.com/93d71de8bc.js" crossorigin="anonymous"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
// === Höhe der Scroll-Container bis Viewport-Ende anpassen ===
|
||||
function sizeScrollContainers() {
|
||||
$('.tableFixHead').each(function () {
|
||||
const rect = this.getBoundingClientRect();
|
||||
const bottomMargin = 16;
|
||||
const available = window.innerHeight - rect.top - bottomMargin;
|
||||
this.style.maxHeight = (available > 120 ? available : 120) + 'px';
|
||||
});
|
||||
<script type="text/javascript">
|
||||
/* ===========================
|
||||
Feature-Toggles aus PHP
|
||||
=========================== */
|
||||
const SCROLL_FLAGS = {
|
||||
checkout: <?php echo (defined('EnableScrollingCheckOut') && EnableScrollingCheckOut) ? 'true' : 'false'; ?>,
|
||||
checkin: <?php echo (defined('EnableScrollingCheckIn') && EnableScrollingCheckIn) ? 'true' : 'false'; ?>
|
||||
};
|
||||
|
||||
const SCROLLER_KEYS = new Map([
|
||||
['checkout-scroll', 'checkout'],
|
||||
['checkin-scroll', 'checkin']
|
||||
]);
|
||||
|
||||
function keyForEl(el) { return el && el.id ? SCROLLER_KEYS.get(el.id) : null; }
|
||||
function isEnabledByEl(el) { const k = keyForEl(el); return !!(k && SCROLL_FLAGS[k]); }
|
||||
function isEnabledByKey(key) { return !!SCROLL_FLAGS[key]; }
|
||||
|
||||
/* ===========================
|
||||
Höhe der Scroll-Container
|
||||
- 2 nebeneinander: volle Höhe bis Viewport-Ende
|
||||
- gestapelt: je 1/2 Fensterhöhe
|
||||
=========================== */
|
||||
function sizeScrollContainers() {
|
||||
const nodes = [
|
||||
document.querySelector('#checkout-scroll'),
|
||||
document.querySelector('#checkin-scroll')
|
||||
].filter(Boolean);
|
||||
|
||||
if (!nodes.length) return;
|
||||
|
||||
const tops = nodes.map(n => n.getBoundingClientRect().top);
|
||||
const ROW_TOL = 8;
|
||||
const firstTop = tops[0];
|
||||
const isOneRow = tops.every(t => Math.abs(t - firstTop) <= ROW_TOL);
|
||||
|
||||
const bottomMargin = 16;
|
||||
const minH = 120;
|
||||
|
||||
nodes.forEach(node => {
|
||||
const rect = node.getBoundingClientRect();
|
||||
if (isOneRow) {
|
||||
const available = window.innerHeight - rect.top - bottomMargin;
|
||||
node.style.maxHeight = Math.max(minH, available) + 'px';
|
||||
} else {
|
||||
const half = Math.floor(window.innerHeight / 2) - bottomMargin;
|
||||
node.style.maxHeight = Math.max(minH, half) + 'px';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// === Scroll-/Interaktionsstatus je Scroller ===
|
||||
const scrollState = new WeakMap(); // { userActive:boolean, autoTimer:number|null, loopHeight:number }
|
||||
/* ===========================
|
||||
Scroll-/Interaktionsstatus
|
||||
=========================== */
|
||||
const scrollState = new WeakMap(); // { userActive:boolean, autoTimer:number|null, loopHeight:number }
|
||||
|
||||
function ensureState(el) {
|
||||
if (!scrollState.get(el)) scrollState.set(el, { userActive: false, autoTimer: null, loopHeight: 0 });
|
||||
return scrollState.get(el);
|
||||
}
|
||||
function ensureState(el) {
|
||||
if (!scrollState.get(el)) scrollState.set(el, { userActive: false, autoTimer: null, loopHeight: 0 });
|
||||
return scrollState.get(el);
|
||||
}
|
||||
|
||||
function attachScrollGuards($scroller) {
|
||||
const el = $scroller.get(0);
|
||||
if (!el || el.__guardsBound) return;
|
||||
const state = ensureState(el);
|
||||
function attachScrollGuards($scroller) {
|
||||
const el = $scroller.get(0);
|
||||
if (!el || el.__guardsBound) return;
|
||||
if (!isEnabledByEl(el)) return;
|
||||
const state = ensureState(el);
|
||||
|
||||
const markActive = () => {
|
||||
state.userActive = true;
|
||||
clearTimeout(state._quietT);
|
||||
state._quietT = setTimeout(() => { state.userActive = false; }, 800);
|
||||
const markActive = () => {
|
||||
state.userActive = true;
|
||||
clearTimeout(state._quietT);
|
||||
state._quietT = setTimeout(() => { state.userActive = false; }, 800);
|
||||
stopAutoScroll($scroller);
|
||||
};
|
||||
|
||||
$scroller.on('wheel touchstart touchmove keydown mousedown mouseenter', markActive);
|
||||
$scroller.on('mouseleave', () => setTimeout(() => maybeStartAutoScroll($scroller), 600));
|
||||
el.__guardsBound = true;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Nahtloser Loop (Clone)
|
||||
=========================== */
|
||||
function buildSeamlessLoop($table, $scroller) {
|
||||
$table.find('tbody.__loopClone').remove();
|
||||
|
||||
const scEl = $scroller.get(0);
|
||||
const enabled = isEnabledByEl(scEl);
|
||||
const st = ensureState(scEl);
|
||||
st.loopHeight = 0;
|
||||
|
||||
if (!enabled) return;
|
||||
|
||||
const $main = $table.find('tbody').first();
|
||||
if ($main.length === 0 || $main.children().length === 0) return;
|
||||
|
||||
const needScroll = $main.get(0).offsetHeight > scEl.clientHeight + 1;
|
||||
if (!needScroll) return;
|
||||
|
||||
const $clone = $main.clone(false, false).addClass('__loopClone').attr('aria-hidden', 'true');
|
||||
$table.append($clone);
|
||||
st.loopHeight = $main.get(0).offsetHeight;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Auto-Scroll
|
||||
=========================== */
|
||||
function startAutoScroll($scroller, speedPx = 1, stepMs = 40) {
|
||||
const el = $scroller.get(0);
|
||||
if (!isEnabledByEl(el)) return;
|
||||
|
||||
const st = ensureState(el);
|
||||
if (st.autoTimer) return;
|
||||
if (st.loopHeight <= 0) return;
|
||||
|
||||
const tick = () => {
|
||||
if (st.userActive) { stopAutoScroll($scroller); return; }
|
||||
el.scrollTop += speedPx;
|
||||
if (el.scrollTop >= st.loopHeight) el.scrollTop -= st.loopHeight;
|
||||
};
|
||||
|
||||
st.autoTimer = setInterval(tick, stepMs);
|
||||
}
|
||||
|
||||
function stopAutoScroll($scroller) {
|
||||
const el = $scroller.get(0);
|
||||
const st = ensureState(el);
|
||||
if (st.autoTimer) {
|
||||
clearInterval(st.autoTimer);
|
||||
st.autoTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function maybeStartAutoScroll($scroller) {
|
||||
const el = $scroller.get(0);
|
||||
if (!isEnabledByEl(el)) { stopAutoScroll($scroller); return; }
|
||||
const st = ensureState(el);
|
||||
if (st.userActive) return;
|
||||
if (st.loopHeight > 0) startAutoScroll($scroller, 1, 40);
|
||||
else stopAutoScroll($scroller);
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Smart AJAX Reload
|
||||
=========================== */
|
||||
function smartLoad($scroller, $table, $target, url, intervalMs) {
|
||||
const scEl = $scroller.get(0);
|
||||
const st = ensureState(scEl);
|
||||
const enabled = isEnabledByEl(scEl);
|
||||
|
||||
const oldLoop = st.loopHeight > 0 ? st.loopHeight : Math.max(1, scEl.scrollHeight - scEl.clientHeight);
|
||||
const posInLoop = st.loopHeight > 0 ? (scEl.scrollTop % oldLoop) : scEl.scrollTop;
|
||||
const posRatio = Math.min(1, posInLoop / oldLoop);
|
||||
|
||||
$target.load(url, function () {
|
||||
if (enabled) {
|
||||
buildSeamlessLoop($table, $scroller);
|
||||
if (st.loopHeight > 0) {
|
||||
const newPos = Math.floor(posRatio * st.loopHeight);
|
||||
if (Math.abs(scEl.scrollTop - newPos) > 1) {
|
||||
requestAnimationFrame(() => { scEl.scrollTop = newPos; });
|
||||
}
|
||||
stopAutoScroll($scroller);
|
||||
};
|
||||
|
||||
$scroller.on('wheel touchstart touchmove keydown mousedown mouseenter', markActive);
|
||||
$scroller.on('mouseleave', () => setTimeout(() => maybeStartAutoScroll($scroller), 600));
|
||||
el.__guardsBound = true;
|
||||
}
|
||||
|
||||
// === Loop aufbauen: nahtloses Doppel nur bei Bedarf ===
|
||||
// Klont den ersten <tbody> als __loopClone ans Tabellenende, wenn Inhalt > Sichtbereich.
|
||||
function buildSeamlessLoop($table, $scroller) {
|
||||
startAutoScroll($scroller, 1, 40);
|
||||
} else {
|
||||
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
|
||||
stopAutoScroll($scroller);
|
||||
}
|
||||
} else {
|
||||
$table.find('tbody.__loopClone').remove();
|
||||
|
||||
const $main = $table.find('tbody').first();
|
||||
const scEl = $scroller.get(0);
|
||||
const st = ensureState(scEl);
|
||||
|
||||
if ($main.length === 0 || $main.children().length === 0) {
|
||||
st.loopHeight = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const mainH = $main.get(0).offsetHeight;
|
||||
const needScroll = mainH > scEl.clientHeight + 1;
|
||||
|
||||
if (!needScroll) {
|
||||
st.loopHeight = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const $clone = $main.clone(false, false).addClass('__loopClone').attr('aria-hidden', 'true');
|
||||
$table.append($clone);
|
||||
st.loopHeight = mainH; // Höhe des Original-Inhalts als Loop-Länge
|
||||
stopAutoScroll($scroller);
|
||||
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
|
||||
}
|
||||
|
||||
// === Auto-Scroll (nahtlos) ===
|
||||
function startAutoScroll($scroller, speedPx = 1, stepMs = 40) {
|
||||
const el = $scroller.get(0);
|
||||
const st = ensureState(el);
|
||||
setTimeout(() => smartLoad($scroller, $table, $target, url, intervalMs), intervalMs);
|
||||
});
|
||||
}
|
||||
|
||||
if (st.autoTimer) return; // schon aktiv
|
||||
if (st.loopHeight <= 0) return; // kein Overflow => nicht scrollen
|
||||
/* ===========================
|
||||
Initialisierung
|
||||
=========================== */
|
||||
$(document).ready(function () {
|
||||
sizeScrollContainers();
|
||||
|
||||
const tick = () => {
|
||||
if (st.userActive) { stopAutoScroll($scroller); return; }
|
||||
el.scrollTop += speedPx;
|
||||
// Nahtlos: sobald wir das Ende des Original-Blocks überschreiten, ziehen wir loopHeight ab
|
||||
if (el.scrollTop >= st.loopHeight) {
|
||||
el.scrollTop -= st.loopHeight;
|
||||
}
|
||||
};
|
||||
|
||||
st.autoTimer = setInterval(tick, stepMs);
|
||||
}
|
||||
|
||||
function stopAutoScroll($scroller) {
|
||||
const el = $scroller.get(0);
|
||||
const st = ensureState(el);
|
||||
if (st.autoTimer) {
|
||||
clearInterval(st.autoTimer);
|
||||
st.autoTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function maybeStartAutoScroll($scroller) {
|
||||
const el = $scroller.get(0);
|
||||
const st = ensureState(el);
|
||||
if (st.userActive) return;
|
||||
if (st.loopHeight > 0) startAutoScroll($scroller, 1, 40);
|
||||
else stopAutoScroll($scroller);
|
||||
}
|
||||
|
||||
// === AJAX-Reload: relative Position innerhalb der Loop erhalten ===
|
||||
// Wir merken die Position modulo loopHeight; nach dem Reload setzen wir proportional um.
|
||||
function smartLoad($scroller, $table, $target, url, intervalMs) {
|
||||
const scEl = $scroller.get(0);
|
||||
const st = ensureState(scEl);
|
||||
|
||||
const oldLoop = st.loopHeight > 0 ? st.loopHeight : Math.max(1, scEl.scrollHeight - scEl.clientHeight);
|
||||
const posInLoop = st.loopHeight > 0 ? (scEl.scrollTop % oldLoop) : scEl.scrollTop;
|
||||
const posRatio = Math.min(1, posInLoop / oldLoop);
|
||||
|
||||
$target.load(url, function () {
|
||||
// Loop neu bewerten/aufbauen
|
||||
buildSeamlessLoop($table, $scroller);
|
||||
|
||||
if (st.loopHeight > 0) {
|
||||
// Neue relative Position setzen (proportional)
|
||||
const newPos = Math.floor(posRatio * st.loopHeight);
|
||||
if (Math.abs(scEl.scrollTop - newPos) > 1) {
|
||||
// rAF für flüssiges Setzen außerhalb des load()-Layouts
|
||||
requestAnimationFrame(() => { scEl.scrollTop = newPos; });
|
||||
}
|
||||
// Auto-Scroll (wieder) starten
|
||||
stopAutoScroll($scroller);
|
||||
startAutoScroll($scroller, 1, 40);
|
||||
} else {
|
||||
// kein Overflow -> ganz oben und Auto-Scroll aus
|
||||
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
|
||||
stopAutoScroll($scroller);
|
||||
}
|
||||
|
||||
setTimeout(() => smartLoad($scroller, $table, $target, url, intervalMs), intervalMs);
|
||||
});
|
||||
}
|
||||
|
||||
// === Initialisierung ===
|
||||
$(document).ready(function () {
|
||||
sizeScrollContainers();
|
||||
$(window).on('resize', function () {
|
||||
sizeScrollContainers();
|
||||
// Nach Layoutwechsel neu entscheiden (nur 2 Tabellen)
|
||||
['#checkout', '#checkin'].forEach(prefix => {
|
||||
const $scroller = $(`${prefix}-scroll`);
|
||||
const $table = $(`${prefix}-table`);
|
||||
buildSeamlessLoop($table, $scroller);
|
||||
maybeStartAutoScroll($scroller);
|
||||
});
|
||||
});
|
||||
|
||||
// Guards pro Scroller (nur 2)
|
||||
attachScrollGuards($('#checkout-scroll'));
|
||||
attachScrollGuards($('#checkin-scroll'));
|
||||
|
||||
// Erstladen je Tabelle: laden -> Loop bauen -> ggf. Auto-Scroll -> zyklisch refreshen
|
||||
function initOne(scrollerSel, tableSel, tbodySel, url, ms) {
|
||||
const $scroller = $(scrollerSel);
|
||||
const $table = $(tableSel);
|
||||
const $target = $(tbodySel);
|
||||
|
||||
$target.load(url, function () {
|
||||
buildSeamlessLoop($table, $scroller);
|
||||
maybeStartAutoScroll($scroller);
|
||||
setTimeout(() => smartLoad($scroller, $table, $target, url, ms), ms);
|
||||
});
|
||||
}
|
||||
|
||||
initOne('#checkout-scroll', '#checkout-table', '#getCheckOutTableHolder', 'sources/getCheckOutTable.php', 5000);
|
||||
initOne('#checkin-scroll', '#checkin-table', '#getCheckInTableHolder', 'sources/getCheckInTable.php', 5000);
|
||||
$(window).on('resize', function () {
|
||||
sizeScrollContainers();
|
||||
['#checkout', '#checkin'].forEach(prefix => {
|
||||
const $scroller = $(`${prefix}-scroll`);
|
||||
const $table = $(`${prefix}-table`);
|
||||
buildSeamlessLoop($table, $scroller);
|
||||
maybeStartAutoScroll($scroller);
|
||||
});
|
||||
</script>
|
||||
});
|
||||
|
||||
// Guards nur für aktivierte Scroller
|
||||
[['#checkout-scroll','checkout'], ['#checkin-scroll','checkin']].forEach(([sel,key]) => {
|
||||
if (isEnabledByKey(key)) attachScrollGuards($(sel));
|
||||
});
|
||||
|
||||
// Erstladen je Tabelle
|
||||
function initOne(scrollerSel, tableSel, tbodySel, url, ms, key) {
|
||||
const $scroller = $(scrollerSel);
|
||||
const $table = $(tableSel);
|
||||
const $target = $(tbodySel);
|
||||
const enabled = isEnabledByKey(key);
|
||||
|
||||
$target.load(url, function () {
|
||||
if (enabled) {
|
||||
buildSeamlessLoop($table, $scroller);
|
||||
maybeStartAutoScroll($scroller);
|
||||
} else {
|
||||
$table.find('tbody.__loopClone').remove();
|
||||
stopAutoScroll($scroller);
|
||||
}
|
||||
setTimeout(() => smartLoad($scroller, $table, $target, url, ms), ms);
|
||||
});
|
||||
}
|
||||
|
||||
initOne('#checkout-scroll', '#checkout-table', '#getCheckOutTableHolder', 'sources/getCheckOutTable.php', 5000, 'checkout');
|
||||
initOne('#checkin-scroll', '#checkin-table', '#getCheckInTableHolder', 'sources/getCheckInTable.php', 5000, 'checkin');
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<style>
|
||||
/* Scroll-Wrapper je Tabelle */
|
||||
|
||||
Reference in New Issue
Block a user