Optimize landscape layout: 4-column grid, transport icons, improved sizing and spacing
This commit is contained in:
548
departures.js
548
departures.js
@@ -1,8 +1,14 @@
|
||||
// Calculate minutes until arrival
|
||||
function calculateMinutesUntilArrival(scheduledTime) {
|
||||
// Calculate minutes until arrival using expected time (or scheduled if expected not available)
|
||||
function calculateMinutesUntilArrival(departure) {
|
||||
const now = new Date();
|
||||
const scheduled = new Date(scheduledTime);
|
||||
return Math.round((scheduled - now) / (1000 * 60));
|
||||
// Use expected time if available (accounts for delays), otherwise use scheduled
|
||||
const arrivalTime = departure.expected ? new Date(departure.expected) : new Date(departure.scheduled);
|
||||
return Math.round((arrivalTime - now) / (1000 * 60));
|
||||
}
|
||||
|
||||
// Get the time to display (expected if available, otherwise scheduled)
|
||||
function getDepartureTime(departure) {
|
||||
return departure.expected || departure.scheduled;
|
||||
}
|
||||
|
||||
// Get transport icon based on transport mode
|
||||
@@ -33,31 +39,46 @@ function createDepartureCard(departure) {
|
||||
departureCard.dataset.journeyId = departure.journey.id;
|
||||
|
||||
const displayTime = departure.display;
|
||||
const scheduledTime = formatDateTime(departure.scheduled);
|
||||
const departureTime = getDepartureTime(departure);
|
||||
const timeDisplay = formatDateTime(departureTime);
|
||||
|
||||
// Check if departure is within the next hour
|
||||
const departureTime = new Date(departure.scheduled);
|
||||
const departureTimeDate = new Date(departureTime);
|
||||
const now = new Date();
|
||||
const diffMinutes = Math.round((departureTime - now) / (1000 * 60));
|
||||
const diffMinutes = Math.round((departureTimeDate - now) / (1000 * 60));
|
||||
const isWithinNextHour = diffMinutes <= 60;
|
||||
|
||||
// Add condensed class if within next hour
|
||||
departureCard.className = isWithinNextHour ? 'departure-card condensed' : 'departure-card';
|
||||
|
||||
// Check if the display time is just a time (HH:MM) or a countdown
|
||||
const isTimeOnly = /^\d{1,2}:\d{2}$/.test(displayTime);
|
||||
|
||||
// If it's just a time, calculate minutes until arrival
|
||||
// Calculate minutes until arrival using expected time (accounts for delays)
|
||||
const minutesUntil = calculateMinutesUntilArrival(departure);
|
||||
let countdownText = displayTime;
|
||||
if (isTimeOnly) {
|
||||
const minutesUntil = calculateMinutesUntilArrival(departure.scheduled);
|
||||
if (minutesUntil <= 0) {
|
||||
countdownText = 'Now';
|
||||
} else if (minutesUntil === 1) {
|
||||
countdownText = '1 min';
|
||||
let countdownClass = '';
|
||||
|
||||
// Determine color class based on minutesUntil, regardless of displayTime format
|
||||
if (minutesUntil <= 0 || displayTime === 'Nu' || displayTime.toLowerCase() === 'nu') {
|
||||
countdownText = 'Nu';
|
||||
countdownClass = 'now';
|
||||
} else if (minutesUntil < 5) {
|
||||
// Less than 5 minutes - red
|
||||
const minMatch = displayTime.match(/(\d+)\s*min/i);
|
||||
if (minMatch) {
|
||||
countdownText = minutesUntil === 1 ? '1 min' : `${minutesUntil} min`;
|
||||
} else {
|
||||
countdownText = `${minutesUntil} min`;
|
||||
countdownText = minutesUntil === 1 ? '1 min' : `${minutesUntil} min`;
|
||||
}
|
||||
countdownClass = 'urgent'; // Red: less than 5 minutes
|
||||
} else {
|
||||
// 5+ minutes - white
|
||||
const isTimeOnly = /^\d{1,2}:\d{2}$/.test(displayTime);
|
||||
if (isTimeOnly) {
|
||||
countdownText = `${minutesUntil} min`;
|
||||
} else {
|
||||
// Use displayTime as-is (e.g., "5 min", "8 min")
|
||||
countdownText = displayTime;
|
||||
}
|
||||
// No class = white (default)
|
||||
}
|
||||
|
||||
// Get transport icon based on transport mode and line
|
||||
@@ -72,8 +93,8 @@ function createDepartureCard(departure) {
|
||||
<span class="line-destination">${departure.destination}</span>
|
||||
</span>
|
||||
<span class="time">
|
||||
<span class="arrival-time">${scheduledTime}</span>
|
||||
<span class="countdown">(${countdownText})</span>
|
||||
<span class="arrival-time">${timeDisplay}</span>
|
||||
<span class="countdown ${countdownClass}">(${countdownText})</span>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
@@ -81,92 +102,156 @@ function createDepartureCard(departure) {
|
||||
return departureCard;
|
||||
}
|
||||
|
||||
// Display departures grouped by line number
|
||||
// Display departures grouped by line number - New card-based layout
|
||||
function displayGroupedDeparturesByLine(groups, container) {
|
||||
groups.forEach(group => {
|
||||
// Create a card for this line number
|
||||
const groupCard = document.createElement('div');
|
||||
groupCard.className = 'departure-card line-card';
|
||||
|
||||
// Create card header
|
||||
const header = document.createElement('div');
|
||||
header.className = 'departure-header';
|
||||
// Get transport mode for styling - ensure we use the API value
|
||||
const apiTransportMode = group.line?.transportMode || '';
|
||||
const transportMode = apiTransportMode.toLowerCase();
|
||||
|
||||
// Get transport icon based on transport mode and line
|
||||
// Create large line number box on the left
|
||||
const lineNumberBox = document.createElement('div');
|
||||
lineNumberBox.className = `line-number-box ${transportMode}`;
|
||||
|
||||
// Get transport icon instead of text label
|
||||
const transportIcon = getTransportIcon(group.line?.transportMode, group.line);
|
||||
|
||||
// Add line number with transport icon
|
||||
const lineNumber = document.createElement('span');
|
||||
lineNumber.className = 'line-number';
|
||||
lineNumberBox.innerHTML = `
|
||||
<div class="transport-mode-icon">${transportIcon}</div>
|
||||
<div class="line-number-large">${group.lineNumber}</div>
|
||||
`;
|
||||
groupCard.appendChild(lineNumberBox);
|
||||
|
||||
// Use the first destination as the main one for the header
|
||||
const mainDestination = group.directions[0]?.destination || '';
|
||||
// Create directions wrapper on the right
|
||||
const directionsWrapper = document.createElement('div');
|
||||
directionsWrapper.className = 'directions-wrapper';
|
||||
|
||||
lineNumber.innerHTML = `${transportIcon} ${group.lineNumber} <span class="line-destination">${mainDestination}</span>`;
|
||||
header.appendChild(lineNumber);
|
||||
groupCard.appendChild(header);
|
||||
|
||||
// Create the directions container
|
||||
const directionsContainer = document.createElement('div');
|
||||
directionsContainer.className = 'directions-container';
|
||||
|
||||
// Process each direction
|
||||
group.directions.forEach(direction => {
|
||||
// Sort departures by time
|
||||
direction.departures.sort((a, b) => new Date(a.scheduled) - new Date(b.scheduled));
|
||||
// Process each direction (up to 2 directions side-by-side)
|
||||
const maxDirections = 2;
|
||||
group.directions.slice(0, maxDirections).forEach(direction => {
|
||||
// Sort departures by expected time (or scheduled if expected not available)
|
||||
direction.departures.sort((a, b) => {
|
||||
const timeA = getDepartureTime(a);
|
||||
const timeB = getDepartureTime(b);
|
||||
return new Date(timeA) - new Date(timeB);
|
||||
});
|
||||
|
||||
// Create a row for this direction
|
||||
const directionRow = document.createElement('div');
|
||||
directionRow.className = 'direction-row';
|
||||
|
||||
// Add direction info
|
||||
// Add direction info (arrow + destination)
|
||||
const directionInfo = document.createElement('div');
|
||||
directionInfo.className = 'direction-info';
|
||||
|
||||
// Determine direction arrow
|
||||
const directionArrow = direction.direction === 1 ? '→' : '←';
|
||||
// Determine direction arrow and styling from API data
|
||||
// Get direction from the first departure in this direction group
|
||||
const firstDep = direction.departures[0];
|
||||
if (!firstDep) return; // Skip if no departures
|
||||
|
||||
directionInfo.innerHTML = `<span class="direction-arrow">${directionArrow}</span> <span class="direction-destination">${direction.destination}</span>`;
|
||||
// Use direction_code from API: 1 = going TO that direction, 2 = going FROM that direction
|
||||
// For arrows: direction_code 1 = left arrow, direction_code 2 = right arrow
|
||||
const directionCode = firstDep.direction_code !== undefined ? firstDep.direction_code :
|
||||
firstDep.directionCode !== undefined ? firstDep.directionCode :
|
||||
null;
|
||||
|
||||
// Map direction_code to arrow direction
|
||||
// direction_code 1 = left arrow (←), direction_code 2 = right arrow (→)
|
||||
const isRight = directionCode === 2;
|
||||
|
||||
if (directionCode === null || directionCode === undefined) {
|
||||
console.warn('No direction_code found for:', direction.destination, firstDep);
|
||||
}
|
||||
|
||||
const arrowBox = document.createElement('div');
|
||||
arrowBox.className = `direction-arrow-box ${isRight ? 'right' : 'left'}`;
|
||||
arrowBox.textContent = isRight ? '→' : '←';
|
||||
|
||||
const destinationSpan = document.createElement('span');
|
||||
destinationSpan.className = 'direction-destination';
|
||||
destinationSpan.textContent = direction.destination;
|
||||
|
||||
directionInfo.appendChild(arrowBox);
|
||||
directionInfo.appendChild(destinationSpan);
|
||||
directionRow.appendChild(directionInfo);
|
||||
|
||||
// Add times container
|
||||
const timesContainer = document.createElement('div');
|
||||
timesContainer.className = 'times-container';
|
||||
|
||||
// Add up to 2 departure times per direction
|
||||
const maxTimes = 2;
|
||||
direction.departures.slice(0, maxTimes).forEach(departure => {
|
||||
const timeElement = document.createElement('span');
|
||||
timeElement.className = 'time';
|
||||
// Get first two departures for time range
|
||||
const firstDeparture = direction.departures[0];
|
||||
const secondDeparture = direction.departures[1];
|
||||
|
||||
if (firstDeparture) {
|
||||
const displayTime = firstDeparture.display;
|
||||
const departureTime = getDepartureTime(firstDeparture);
|
||||
const timeDisplay = formatDateTime(departureTime);
|
||||
|
||||
const displayTime = departure.display;
|
||||
const scheduledTime = formatDateTime(departure.scheduled);
|
||||
|
||||
// Check if the display time is just a time (HH:MM) or a countdown
|
||||
const isTimeOnly = /^\d{1,2}:\d{2}$/.test(displayTime);
|
||||
|
||||
// If it's just a time, calculate minutes until arrival
|
||||
// Calculate minutes until arrival using expected time (accounts for delays)
|
||||
const minutesUntil = calculateMinutesUntilArrival(firstDeparture);
|
||||
let countdownText = displayTime;
|
||||
if (isTimeOnly) {
|
||||
const minutesUntil = calculateMinutesUntilArrival(departure.scheduled);
|
||||
if (minutesUntil <= 0) {
|
||||
countdownText = 'Now';
|
||||
} else if (minutesUntil === 1) {
|
||||
countdownText = '1 min';
|
||||
let countdownClass = '';
|
||||
|
||||
// Determine color class based on minutesUntil, regardless of displayTime format
|
||||
if (minutesUntil <= 0 || displayTime === 'Nu' || displayTime.toLowerCase() === 'nu') {
|
||||
countdownText = 'Nu';
|
||||
countdownClass = 'now';
|
||||
} else if (minutesUntil < 5) {
|
||||
// Use the number from displayTime if it's "X min", otherwise use calculated minutesUntil
|
||||
const minMatch = displayTime.match(/(\d+)\s*min/i);
|
||||
if (minMatch) {
|
||||
countdownText = `${minMatch[1]}`;
|
||||
} else {
|
||||
countdownText = `${minutesUntil} min`;
|
||||
countdownText = `${minutesUntil}`;
|
||||
}
|
||||
countdownClass = 'urgent'; // Red: less than 5 minutes
|
||||
} else {
|
||||
// 5+ minutes - use displayTime as-is or calculate
|
||||
const minMatch = displayTime.match(/(\d+)\s*min/i);
|
||||
if (minMatch) {
|
||||
countdownText = `${minMatch[1]}`;
|
||||
} else if (/^\d{1,2}:\d{2}$/.test(displayTime)) {
|
||||
countdownText = `${minutesUntil}`;
|
||||
} else {
|
||||
countdownText = displayTime;
|
||||
}
|
||||
// No class = white (default)
|
||||
}
|
||||
|
||||
timeElement.innerHTML = `${scheduledTime} <span class="countdown">(${countdownText})</span>`;
|
||||
timesContainer.appendChild(timeElement);
|
||||
});
|
||||
// Create time display element
|
||||
const timeDisplayElement = document.createElement('div');
|
||||
timeDisplayElement.className = 'time-display';
|
||||
|
||||
const countdownSpan = document.createElement('span');
|
||||
countdownSpan.className = `countdown-large ${countdownClass}`;
|
||||
countdownSpan.textContent = countdownText;
|
||||
|
||||
timeDisplayElement.appendChild(countdownSpan);
|
||||
|
||||
// Add time range (show expected times)
|
||||
const timeRangeSpan = document.createElement('span');
|
||||
timeRangeSpan.className = 'time-range';
|
||||
if (secondDeparture) {
|
||||
const secondTime = formatDateTime(getDepartureTime(secondDeparture));
|
||||
timeRangeSpan.textContent = `${timeDisplay} - ${secondTime}`;
|
||||
} else {
|
||||
timeRangeSpan.textContent = timeDisplay;
|
||||
}
|
||||
timeDisplayElement.appendChild(timeRangeSpan);
|
||||
|
||||
timesContainer.appendChild(timeDisplayElement);
|
||||
}
|
||||
|
||||
directionRow.appendChild(timesContainer);
|
||||
directionsContainer.appendChild(directionRow);
|
||||
directionsWrapper.appendChild(directionRow);
|
||||
});
|
||||
|
||||
groupCard.appendChild(directionsContainer);
|
||||
groupCard.appendChild(directionsWrapper);
|
||||
|
||||
// Add to container
|
||||
container.appendChild(groupCard);
|
||||
@@ -176,8 +261,12 @@ function displayGroupedDeparturesByLine(groups, container) {
|
||||
// Display grouped departures (legacy function)
|
||||
function displayGroupedDepartures(groups, container) {
|
||||
groups.forEach(group => {
|
||||
// Sort departures by time
|
||||
group.departures.sort((a, b) => new Date(a.scheduled) - new Date(b.scheduled));
|
||||
// Sort departures by expected time (or scheduled if expected not available)
|
||||
group.departures.sort((a, b) => {
|
||||
const timeA = getDepartureTime(a);
|
||||
const timeB = getDepartureTime(b);
|
||||
return new Date(timeA) - new Date(timeB);
|
||||
});
|
||||
|
||||
// Create a card for this group
|
||||
const groupCard = document.createElement('div');
|
||||
@@ -212,29 +301,40 @@ function displayGroupedDepartures(groups, container) {
|
||||
timeElement.style.marginBottom = '2px';
|
||||
|
||||
const displayTime = departure.display;
|
||||
const scheduledTime = formatDateTime(departure.scheduled);
|
||||
const departureTime = getDepartureTime(departure);
|
||||
const timeDisplay = formatDateTime(departureTime);
|
||||
|
||||
// Check if the display time is just a time (HH:MM) or a countdown
|
||||
const isTimeOnly = /^\d{1,2}:\d{2}$/.test(displayTime);
|
||||
|
||||
// If it's just a time, calculate minutes until arrival
|
||||
// Calculate minutes until arrival using expected time (accounts for delays)
|
||||
const minutesUntil = calculateMinutesUntilArrival(departure);
|
||||
let countdownText = displayTime;
|
||||
if (isTimeOnly) {
|
||||
const minutesUntil = calculateMinutesUntilArrival(departure.scheduled);
|
||||
if (minutesUntil <= 0) {
|
||||
countdownText = 'Now';
|
||||
} else if (minutesUntil === 1) {
|
||||
countdownText = '1 min';
|
||||
let countdownClass = '';
|
||||
|
||||
// Determine color class based on minutesUntil, regardless of displayTime format
|
||||
if (minutesUntil <= 0 || displayTime === 'Nu' || displayTime.toLowerCase() === 'nu') {
|
||||
countdownText = 'Nu';
|
||||
countdownClass = 'now';
|
||||
} else if (minutesUntil < 5) {
|
||||
// Less than 5 minutes - red
|
||||
const minMatch = displayTime.match(/(\d+)\s*min/i);
|
||||
if (minMatch) {
|
||||
countdownText = minutesUntil === 1 ? '1 min' : `${minutesUntil} min`;
|
||||
} else {
|
||||
countdownText = `${minutesUntil} min`;
|
||||
countdownText = minutesUntil === 1 ? '1 min' : `${minutesUntil} min`;
|
||||
}
|
||||
countdownClass = 'urgent'; // Red: less than 5 minutes
|
||||
} else {
|
||||
// 5+ minutes - white
|
||||
const isTimeOnly = /^\d{1,2}:\d{2}$/.test(displayTime);
|
||||
if (isTimeOnly) {
|
||||
countdownText = `${minutesUntil} min`;
|
||||
} else {
|
||||
// Use displayTime as-is (e.g., "5 min", "8 min")
|
||||
countdownText = displayTime;
|
||||
}
|
||||
// No class = white (default)
|
||||
}
|
||||
|
||||
if (isTimeOnly) {
|
||||
timeElement.innerHTML = `${scheduledTime} <span class="countdown">(${countdownText})</span>`;
|
||||
} else {
|
||||
timeElement.innerHTML = `${scheduledTime} <span class="countdown">(${displayTime})</span>`;
|
||||
}
|
||||
timeElement.innerHTML = `${scheduledTime} <span class="countdown ${countdownClass}">(${countdownText})</span>`;
|
||||
|
||||
timesContainer.appendChild(timeElement);
|
||||
});
|
||||
@@ -271,7 +371,7 @@ function formatRelativeTime(dateTimeString) {
|
||||
const diffMinutes = Math.round((departureTime - now) / (1000 * 60));
|
||||
|
||||
if (diffMinutes <= 0) {
|
||||
return 'Now';
|
||||
return 'Nu';
|
||||
} else if (diffMinutes === 1) {
|
||||
return 'In 1 minute';
|
||||
} else if (diffMinutes < 60) {
|
||||
@@ -301,11 +401,17 @@ function groupDeparturesByLineNumber(departures) {
|
||||
};
|
||||
}
|
||||
|
||||
const directionKey = `${departure.direction}-${departure.destination}`;
|
||||
// Get direction_code from API: 1 = going TO that direction, 2 = going FROM that direction
|
||||
const departureDirection = departure.direction_code !== undefined ? departure.direction_code :
|
||||
departure.directionCode !== undefined ? departure.directionCode :
|
||||
departure.direction !== undefined ? departure.direction :
|
||||
1; // Default to 1 (left arrow) if not found
|
||||
|
||||
const directionKey = `${departureDirection}-${departure.destination}`;
|
||||
|
||||
if (!groups[lineNumber].directions[directionKey]) {
|
||||
groups[lineNumber].directions[directionKey] = {
|
||||
direction: departure.direction,
|
||||
direction: departureDirection,
|
||||
destination: departure.destination,
|
||||
departures: []
|
||||
};
|
||||
@@ -433,31 +539,58 @@ function updateExistingCards(newDepartures) {
|
||||
// Update only the content that has changed in an existing card
|
||||
function updateCardContent(card, departure) {
|
||||
const displayTime = departure.display;
|
||||
const scheduledTime = formatDateTime(departure.scheduled);
|
||||
const departureTime = getDepartureTime(departure);
|
||||
|
||||
// Check if the display time is just a time (HH:MM) or a countdown
|
||||
const isTimeOnly = /^\d{1,2}:\d{2}$/.test(displayTime);
|
||||
|
||||
// If it's just a time, calculate minutes until arrival
|
||||
// Calculate minutes until arrival using expected time (accounts for delays)
|
||||
const minutesUntil = calculateMinutesUntilArrival(departure);
|
||||
let countdownText = displayTime;
|
||||
if (isTimeOnly) {
|
||||
const minutesUntil = calculateMinutesUntilArrival(departure.scheduled);
|
||||
if (minutesUntil <= 0) {
|
||||
countdownText = 'Now';
|
||||
} else if (minutesUntil === 1) {
|
||||
countdownText = '1 min';
|
||||
let countdownClass = '';
|
||||
|
||||
// Determine color class based on minutesUntil, regardless of displayTime format
|
||||
if (minutesUntil <= 0 || displayTime === 'Nu' || displayTime.toLowerCase() === 'nu') {
|
||||
countdownText = 'Nu';
|
||||
countdownClass = 'now';
|
||||
} else if (minutesUntil < 5) {
|
||||
// Less than 5 minutes - red
|
||||
const minMatch = displayTime.match(/(\d+)\s*min/i);
|
||||
if (minMatch) {
|
||||
countdownText = minutesUntil === 1 ? '1 min' : `${minutesUntil} min`;
|
||||
} else {
|
||||
countdownText = `${minutesUntil} min`;
|
||||
countdownText = minutesUntil === 1 ? '1 min' : `${minutesUntil} min`;
|
||||
}
|
||||
countdownClass = 'urgent'; // Red: less than 5 minutes
|
||||
} else {
|
||||
// 5+ minutes - white
|
||||
const isTimeOnly = /^\d{1,2}:\d{2}$/.test(displayTime);
|
||||
if (isTimeOnly) {
|
||||
countdownText = `${minutesUntil} min`;
|
||||
} else {
|
||||
// Use displayTime as-is (e.g., "5 min", "8 min")
|
||||
countdownText = displayTime;
|
||||
}
|
||||
// No class = white (default)
|
||||
}
|
||||
|
||||
// Only update the countdown time which changes frequently
|
||||
const countdownElement = card.querySelector('.countdown');
|
||||
|
||||
// Update with subtle highlight effect for changes
|
||||
if (countdownElement && countdownElement.textContent !== `(${countdownText})`) {
|
||||
countdownElement.textContent = `(${countdownText})`;
|
||||
highlightElement(countdownElement);
|
||||
// Update class for "now" and "urgent" states
|
||||
if (countdownElement) {
|
||||
// Remove all state classes first
|
||||
countdownElement.classList.remove('now', 'urgent');
|
||||
|
||||
// Add the appropriate class
|
||||
if (countdownClass === 'now') {
|
||||
countdownElement.classList.add('now');
|
||||
} else if (countdownClass === 'urgent') {
|
||||
countdownElement.classList.add('urgent');
|
||||
}
|
||||
|
||||
// Update with subtle highlight effect for changes
|
||||
if (countdownElement.textContent !== `(${countdownText})`) {
|
||||
countdownElement.textContent = `(${countdownText})`;
|
||||
highlightElement(countdownElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,202 +632,11 @@ function displayMultipleSites(sites) {
|
||||
|
||||
// Process departures for this site
|
||||
if (site.data && site.data.departures) {
|
||||
// Group departures by line number
|
||||
const lineGroups = {};
|
||||
// Group departures by line number using the existing function
|
||||
const lineGroups = groupDeparturesByLineNumber(site.data.departures);
|
||||
|
||||
site.data.departures.forEach(departure => {
|
||||
const lineNumber = departure.line.designation;
|
||||
if (!lineGroups[lineNumber]) {
|
||||
lineGroups[lineNumber] = [];
|
||||
}
|
||||
lineGroups[lineNumber].push(departure);
|
||||
});
|
||||
|
||||
// Process each line group
|
||||
Object.entries(lineGroups).forEach(([lineNumber, lineDepartures]) => {
|
||||
// Create a line container for side-by-side display
|
||||
const lineContainer = document.createElement('div');
|
||||
lineContainer.className = 'line-container';
|
||||
|
||||
// Group by direction
|
||||
const directionGroups = {};
|
||||
|
||||
lineDepartures.forEach(departure => {
|
||||
const directionKey = `${departure.direction}-${departure.destination}`;
|
||||
if (!directionGroups[directionKey]) {
|
||||
directionGroups[directionKey] = {
|
||||
direction: departure.direction,
|
||||
destination: departure.destination,
|
||||
departures: []
|
||||
};
|
||||
}
|
||||
directionGroups[directionKey].departures.push(departure);
|
||||
});
|
||||
|
||||
// Get all direction groups
|
||||
const directions = Object.values(directionGroups);
|
||||
|
||||
// Handle single direction case (like bus 4)
|
||||
if (directions.length === 1) {
|
||||
// Create a full-width card for this direction
|
||||
const directionGroup = directions[0];
|
||||
|
||||
// Sort departures by time
|
||||
directionGroup.departures.sort((a, b) => new Date(a.scheduled) - new Date(b.scheduled));
|
||||
|
||||
// Create a card for this direction
|
||||
const directionCard = document.createElement('div');
|
||||
directionCard.className = 'departure-card';
|
||||
// Don't set width to 100% as it causes the card to stick out
|
||||
|
||||
// Create a simplified layout with line number and times on the same row
|
||||
const cardContent = document.createElement('div');
|
||||
cardContent.className = 'departure-header';
|
||||
cardContent.style.display = 'flex';
|
||||
cardContent.style.justifyContent = 'space-between';
|
||||
cardContent.style.alignItems = 'center';
|
||||
|
||||
// Get transport icon based on transport mode and line
|
||||
const transportIcon = getTransportIcon(directionGroup.departures[0].line?.transportMode, directionGroup.departures[0].line);
|
||||
|
||||
// Add line number with transport icon and destination
|
||||
const lineNumberElement = document.createElement('span');
|
||||
lineNumberElement.className = 'line-number';
|
||||
lineNumberElement.innerHTML = `${transportIcon} ${lineNumber} <span class="line-destination">${directionGroup.destination}</span>`;
|
||||
|
||||
// Add times container
|
||||
const timesContainer = document.createElement('div');
|
||||
timesContainer.className = 'times-container';
|
||||
timesContainer.style.display = 'flex';
|
||||
timesContainer.style.flexDirection = 'column';
|
||||
timesContainer.style.alignItems = 'flex-end';
|
||||
|
||||
// Add up to 2 departure times
|
||||
const maxTimes = 2;
|
||||
directionGroup.departures.slice(0, maxTimes).forEach(departure => {
|
||||
const timeElement = document.createElement('div');
|
||||
timeElement.className = 'time';
|
||||
timeElement.style.fontSize = '1.1em';
|
||||
timeElement.style.marginBottom = '2px';
|
||||
timeElement.style.whiteSpace = 'nowrap';
|
||||
timeElement.style.textAlign = 'right';
|
||||
|
||||
const displayTime = departure.display;
|
||||
const scheduledTime = formatDateTime(departure.scheduled);
|
||||
|
||||
// Check if the display time is just a time (HH:MM) or a countdown
|
||||
const isTimeOnly = /^\d{1,2}:\d{2}$/.test(displayTime);
|
||||
|
||||
// If it's just a time, calculate minutes until arrival
|
||||
let countdownText = displayTime;
|
||||
if (isTimeOnly) {
|
||||
const minutesUntil = calculateMinutesUntilArrival(departure.scheduled);
|
||||
if (minutesUntil <= 0) {
|
||||
countdownText = 'Now';
|
||||
} else if (minutesUntil === 1) {
|
||||
countdownText = '1 min';
|
||||
} else {
|
||||
countdownText = `${minutesUntil} min`;
|
||||
}
|
||||
}
|
||||
|
||||
timeElement.textContent = `${scheduledTime} (${countdownText})`;
|
||||
timeElement.style.width = '140px'; // Fixed width to prevent overflow
|
||||
timeElement.style.width = '140px'; // Fixed width to prevent overflow
|
||||
|
||||
timesContainer.appendChild(timeElement);
|
||||
});
|
||||
|
||||
cardContent.appendChild(lineNumberElement);
|
||||
cardContent.appendChild(timesContainer);
|
||||
directionCard.appendChild(cardContent);
|
||||
siteContainer.appendChild(directionCard);
|
||||
} else {
|
||||
// Create cards for each direction, with max 2 per row
|
||||
// Create a new line container for every 2 directions
|
||||
for (let i = 0; i < directions.length; i += 2) {
|
||||
// Create a new line container for this pair of directions
|
||||
const rowContainer = document.createElement('div');
|
||||
rowContainer.className = 'line-container';
|
||||
|
||||
// Process up to 2 directions for this row
|
||||
for (let j = i; j < i + 2 && j < directions.length; j++) {
|
||||
const directionGroup = directions[j];
|
||||
|
||||
// Sort departures by time
|
||||
directionGroup.departures.sort((a, b) => new Date(a.scheduled) - new Date(b.scheduled));
|
||||
|
||||
// Create a card for this direction
|
||||
const directionCard = document.createElement('div');
|
||||
directionCard.className = 'departure-card direction-card';
|
||||
|
||||
// Create a simplified layout with line number and times on the same row
|
||||
const cardContent = document.createElement('div');
|
||||
cardContent.className = 'departure-header';
|
||||
cardContent.style.display = 'flex';
|
||||
cardContent.style.justifyContent = 'space-between';
|
||||
cardContent.style.alignItems = 'center';
|
||||
|
||||
// Get transport icon based on transport mode and line
|
||||
const transportIcon = getTransportIcon(directionGroup.departures[0].line?.transportMode, directionGroup.departures[0].line);
|
||||
|
||||
// Add line number with transport icon and destination
|
||||
const lineNumberElement = document.createElement('span');
|
||||
lineNumberElement.className = 'line-number';
|
||||
lineNumberElement.innerHTML = `${transportIcon} ${lineNumber} <span class="line-destination">${directionGroup.destination}</span>`;
|
||||
|
||||
// Add times container
|
||||
const timesContainer = document.createElement('div');
|
||||
timesContainer.className = 'times-container';
|
||||
timesContainer.style.display = 'flex';
|
||||
timesContainer.style.flexDirection = 'column';
|
||||
timesContainer.style.alignItems = 'flex-end';
|
||||
|
||||
// Add up to 2 departure times
|
||||
const maxTimes = 2;
|
||||
directionGroup.departures.slice(0, maxTimes).forEach(departure => {
|
||||
const timeElement = document.createElement('div');
|
||||
timeElement.className = 'time';
|
||||
timeElement.style.fontSize = '1.1em';
|
||||
timeElement.style.marginBottom = '2px';
|
||||
timeElement.style.whiteSpace = 'nowrap';
|
||||
timeElement.style.textAlign = 'right';
|
||||
|
||||
const displayTime = departure.display;
|
||||
const scheduledTime = formatDateTime(departure.scheduled);
|
||||
|
||||
// Check if the display time is just a time (HH:MM) or a countdown
|
||||
const isTimeOnly = /^\d{1,2}:\d{2}$/.test(displayTime);
|
||||
|
||||
// If it's just a time, calculate minutes until arrival
|
||||
let countdownText = displayTime;
|
||||
if (isTimeOnly) {
|
||||
const minutesUntil = calculateMinutesUntilArrival(departure.scheduled);
|
||||
if (minutesUntil <= 0) {
|
||||
countdownText = 'Now';
|
||||
} else if (minutesUntil === 1) {
|
||||
countdownText = '1 min';
|
||||
} else {
|
||||
countdownText = `${minutesUntil} min`;
|
||||
}
|
||||
}
|
||||
|
||||
timeElement.textContent = `${scheduledTime} (${countdownText})`;
|
||||
|
||||
timesContainer.appendChild(timeElement);
|
||||
});
|
||||
|
||||
cardContent.appendChild(lineNumberElement);
|
||||
cardContent.appendChild(timesContainer);
|
||||
directionCard.appendChild(cardContent);
|
||||
rowContainer.appendChild(directionCard);
|
||||
}
|
||||
|
||||
// Add this row to the site container
|
||||
siteContainer.appendChild(rowContainer);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Use the new card-based layout function
|
||||
displayGroupedDeparturesByLine(lineGroups, siteContainer);
|
||||
} else if (site.error) {
|
||||
// Display error for this site
|
||||
const errorElement = document.createElement('div');
|
||||
|
||||
Reference in New Issue
Block a user