hotfix jointlist

This commit is contained in:
Mateusz Gruszczyński
2026-09-12 14:46:20 +02:00
parent f66a4d304c
commit 53ee64f56b
5 changed files with 208 additions and 12 deletions
@@ -315,6 +315,53 @@
letter-spacing: .08em;
}
.app-ws-status {
display: inline-flex;
align-items: center;
justify-content: center;
gap: .38rem;
min-height: 2rem;
padding: .34rem .58rem;
border-radius: 999px;
border: 1px solid rgba(255,255,255,.10);
background: rgba(255,255,255,.05);
color: var(--app-text-muted);
font-size: .7rem;
font-weight: 700;
line-height: 1;
letter-spacing: .05em;
user-select: none;
}
.app-ws-status__dot {
width: .58rem;
height: .58rem;
flex: 0 0 .58rem;
border-radius: 50%;
background: #f5b942;
box-shadow: 0 0 0 .18rem rgba(245,185,66,.13);
transition: background-color .15s ease, box-shadow .15s ease;
}
.app-ws-status[data-state="connected"] .app-ws-status__dot {
background: #27d17f;
box-shadow: 0 0 0 .18rem rgba(39,209,127,.14);
}
.app-ws-status[data-state="connecting"] .app-ws-status__dot,
.app-ws-status[data-state="syncing"] .app-ws-status__dot,
.app-ws-status[data-state="reconnecting"] .app-ws-status__dot {
background: #f5b942;
box-shadow: 0 0 0 .18rem rgba(245,185,66,.13);
}
.app-ws-status[data-state="offline"] .app-ws-status__dot,
.app-ws-status[data-state="error"] .app-ws-status__dot {
background: #ef6461;
box-shadow: 0 0 0 .18rem rgba(239,100,97,.14);
}
.app-navbar-toggler {
border-color: rgba(255,255,255,.28);
padding: .3rem .55rem;
@@ -64,6 +64,25 @@
display: inline-flex;
}
.app-ws-status--mobile {
width: 2.15rem;
min-width: 2.15rem;
height: 2.15rem;
min-height: 2.15rem;
padding: 0;
gap: 0;
}
.app-ws-status--mobile .app-ws-status__label {
display: none;
}
.app-ws-status--mobile .app-ws-status__dot {
width: .68rem;
height: .68rem;
flex-basis: .68rem;
}
.app-mobile-menu {
grid-column: 3;
justify-self: end;
@@ -919,3 +938,18 @@
padding: .85rem .9rem;
}
}
@media (max-width: 991.98px) {
.endpoint-view_list .app-navbar__meta--mobile,
.endpoint-shared_list .app-navbar__meta--mobile {
max-width: min(50vw, 14rem);
flex-wrap: nowrap;
}
}
@media (max-width: 430px) {
.endpoint-view_list .app-navbar__meta--mobile,
.endpoint-shared_list .app-navbar__meta--mobile {
max-width: min(46vw, 10rem);
}
}
+7 -3
View File
@@ -1,9 +1,10 @@
const socket = io({
reconnection: true,
reconnectionAttempts: Infinity,
reconnectionDelay: 500,
reconnectionDelayMax: 5000,
timeout: 8000
reconnectionDelay: 350,
reconnectionDelayMax: 2000,
randomizationFactor: 0.2,
timeout: 5000
});
/*──────────────── placeholder pustej listy ────────────────*/
@@ -38,6 +39,9 @@ function setupList(listId, username) {
if (typeof window.ensureCurrentListJoined === 'function') {
window.ensureCurrentListJoined();
}
if (typeof window.refreshListSocketStatus === 'function') {
window.refreshListSocketStatus();
}
const newItemInput = document.getElementById('newItem');
+107 -9
View File
@@ -14,17 +14,74 @@ let currentRoomUsers = [];
// wbudowanemu mechanizmowi Socket.IO zestawić nowe połączenie. Nie wywołujemy
// socket.disconnect(), bo jest to ręczne rozłączenie i może wejść w konflikt
// z automatycznym reconnectem.
const WAKE_CHECK_DEBOUNCE_MS = 250;
const WAKE_ACK_TIMEOUT_MS = 4000;
const WAKE_WATCHDOG_INTERVAL_MS = 5000;
const WAKE_WATCHDOG_GAP_MS = 15000;
const WAKE_RETRY_CONNECT_MS = 1200;
const WAKE_CHECK_DEBOUNCE_MS = 80;
const WAKE_ACK_TIMEOUT_MS = 1800;
const WAKE_WATCHDOG_INTERVAL_MS = 3000;
const WAKE_WATCHDOG_GAP_MS = 9000;
const WAKE_RETRY_CONNECT_MS = 500;
let wakeCheckTimer = null;
let wakeCheckInProgress = false;
let reconnectFallbackTimer = null;
let lastWatchdogTick = Date.now();
const LIST_SOCKET_STATUS_LABELS = {
connected: 'WebSocket: połączono i zsynchronizowano',
connecting: 'WebSocket: łączenie',
syncing: 'WebSocket: synchronizacja listy',
reconnecting: 'WebSocket: ponowne łączenie',
offline: 'WebSocket: brak połączenia z internetem',
error: 'WebSocket: błąd połączenia'
};
let listSocketStatus = 'connecting';
function setListSocketStatus(state, detail = '') {
listSocketStatus = state;
const baseLabel = LIST_SOCKET_STATUS_LABELS[state] || LIST_SOCKET_STATUS_LABELS.connecting;
const label = detail ? `${baseLabel} (${detail})` : baseLabel;
document.querySelectorAll('[data-ws-status]').forEach(el => {
el.dataset.state = state;
el.setAttribute('title', label);
el.setAttribute('aria-label', label);
});
}
function currentSocketTransportName() {
try {
return socket.io && socket.io.engine && socket.io.engine.transport
? socket.io.engine.transport.name
: '';
} catch (_) {
return '';
}
}
function refreshListSocketStatus() {
if (!hasActiveListRoom()) return;
if (typeof navigator !== 'undefined' && navigator.onLine === false) {
setListSocketStatus('offline');
return;
}
if (!socket.connected) {
setListSocketStatus('reconnecting');
return;
}
if (joinedSocketId === socket.id && joinedRoomId === String(window.LIST_ID) && didReceiveFirstFullList) {
setListSocketStatus('connected', currentSocketTransportName());
return;
}
setListSocketStatus('syncing');
}
window.refreshListSocketStatus = refreshListSocketStatus;
function hasActiveListRoom() {
return Boolean(window.LIST_ID && window.usernameForReconnect);
}
@@ -56,6 +113,7 @@ function ensureCurrentListJoined() {
joinedSocketId = socketId;
joinedRoomId = roomId;
setListSocketStatus('syncing');
socket.emit('join_list', {
room: window.LIST_ID,
@@ -77,6 +135,7 @@ function forceCloseStaleTransport() {
wakeCheckInProgress = false;
joinedSocketId = null;
joinedRoomId = null;
setListSocketStatus('reconnecting');
if (window.LIST_ID) disableCheckboxes(true);
@@ -106,9 +165,14 @@ function forceCloseStaleTransport() {
function runWakeCheck() {
if (document.hidden || !hasActiveListRoom()) return;
if (typeof navigator !== 'undefined' && navigator.onLine === false) return;
if (typeof navigator !== 'undefined' && navigator.onLine === false) {
setListSocketStatus('offline');
disableCheckboxes(true);
return;
}
if (!socket.connected) {
setListSocketStatus('reconnecting');
disableCheckboxes(true);
socket.connect();
return;
@@ -122,6 +186,7 @@ function runWakeCheck() {
if (wakeCheckInProgress) return;
wakeCheckInProgress = true;
setListSocketStatus('syncing');
// request_full_list pełni jednocześnie rolę health-checka i synchronizacji.
socket.timeout(WAKE_ACK_TIMEOUT_MS).emit(
@@ -133,6 +198,7 @@ function runWakeCheck() {
if (!err && response && response.ok === true) {
// Jeśli połączenie jest zdrowe, nie wykonujemy reconnectu i nie emitujemy
// ponownego join_list. Samo przełączenie karty pozostaje więc całkowicie ciche.
setListSocketStatus('connected', currentSocketTransportName());
return;
}
@@ -159,7 +225,20 @@ window.addEventListener('pageshow', event => {
if (event.persisted) recoverListSocket();
});
window.addEventListener('online', recoverListSocket);
window.addEventListener('focus', () => {
if (!document.hidden) recoverListSocket();
});
window.addEventListener('online', () => {
if (hasActiveListRoom()) setListSocketStatus('reconnecting');
recoverListSocket();
});
window.addEventListener('offline', () => {
if (!hasActiveListRoom()) return;
setListSocketStatus('offline');
disableCheckboxes(true);
});
// Dla środowisk emitujących zdarzenie resume (np. webview/PWA wrapper).
document.addEventListener('resume', recoverListSocket);
@@ -182,6 +261,7 @@ socket.on('connect', function () {
joinedRoomId = null;
if (hasActiveListRoom()) {
setListSocketStatus('syncing');
disableCheckboxes(true);
ensureCurrentListJoined();
}
@@ -191,18 +271,35 @@ socket.on('disconnect', function () {
wakeCheckInProgress = false;
joinedSocketId = null;
joinedRoomId = null;
if (window.LIST_ID) disableCheckboxes(true);
if (window.LIST_ID) {
setListSocketStatus(
typeof navigator !== 'undefined' && navigator.onLine === false ? 'offline' : 'reconnecting'
);
disableCheckboxes(true);
}
});
socket.on('connect_error', function () {
wakeCheckInProgress = false;
if (window.LIST_ID) disableCheckboxes(true);
if (window.LIST_ID) {
setListSocketStatus(
typeof navigator !== 'undefined' && navigator.onLine === false ? 'offline' : 'error'
);
disableCheckboxes(true);
}
});
if (socket.io && typeof socket.io.on === 'function') {
socket.io.on('reconnect_attempt', () => {
if (hasActiveListRoom()) setListSocketStatus('reconnecting');
});
}
socket.off('joined_confirmation');
socket.on('joined_confirmation', function (data) {
const isInitialVisibleJoin = !didCompleteInitialJoin && !data.silent;
didCompleteInitialJoin = true;
setListSocketStatus('syncing');
if (Array.isArray(data.users)) {
currentRoomUsers = data.users;
@@ -289,6 +386,7 @@ socket.on('full_list', function (data) {
updateListSmoothly(data.items);
disableCheckboxes(false);
wakeCheckInProgress = false;
setListSocketStatus('connected', currentSocketTransportName());
if (typeof window.syncSortModeUI === 'function') {
window.syncSortModeUI();
}
+13
View File
@@ -20,6 +20,7 @@
<link rel="stylesheet" href="{{ static_asset_url('static_bp.serve_css', 'split/pages.css') }}">
{% set hide_login_ui = request.path == '/system-auth' %}
{% set is_live_list_view = request.endpoint in ['view_list', 'shared_list'] %}
{% set exclude_paths = ['/system-auth'] %}
{% if (exclude_paths | select("in", request.path) | list | length == 0)
@@ -68,6 +69,12 @@
</div>
{% endif %}
{% endif %}
{% if is_live_list_view %}
<div class="app-ws-status" data-ws-status data-state="connecting" role="status" aria-label="WebSocket: łączenie" title="WebSocket: łączenie">
<span class="app-ws-status__dot" aria-hidden="true"></span>
<span class="app-ws-status__label" aria-hidden="true">WS</span>
</div>
{% endif %}
</div>
<div class="d-lg-none app-navbar__meta app-navbar__meta--mobile ms-auto">
@@ -84,6 +91,12 @@
</div>
{% endif %}
{% endif %}
{% if is_live_list_view %}
<div class="app-ws-status app-ws-status--mobile" data-ws-status data-state="connecting" role="status" aria-label="WebSocket: łączenie" title="WebSocket: łączenie">
<span class="app-ws-status__dot" aria-hidden="true"></span>
<span class="app-ws-status__label" aria-hidden="true">WS</span>
</div>
{% endif %}
</div>
<div class="dropdown d-lg-none app-mobile-menu">