commit 3a3a51ef76f17c3c5f8da37ef960c376738104db
parent 82b6a3d784c7de061dc353b207a9e48d27aee964
Author: Katja (ctucx) <git@ctu.cx>
Date: Sat, 19 Apr 2025 00:23:56 +0200
parent 82b6a3d784c7de061dc353b207a9e48d27aee964
Author: Katja (ctucx) <git@ctu.cx>
Date: Sat, 19 Apr 2025 00:23:56 +0200
replace html-tables with css grids
13 files changed, 298 insertions(+), 334 deletions(-)
M
|
95
++++++++++++++++++++++++++++++++++++-------------------------------------------
M
|
124
++++++++++++++++++++++++++++++++++++++-----------------------------------------
D
|
81
-------------------------------------------------------------------------------
diff --git a/src/departuresView.js b/src/departuresView.js @@ -7,13 +7,13 @@ import { processLeg } from './app_functions.js'; import { getHafasClient } from './hafasClient.js'; import { t } from './languages.js'; -import { headerStyles, cardStyles, departuresViewStyles } from './styles.js'; +import { headerStyles, tableStyles, departuresViewStyles } from './styles.js'; class DeparturesView extends BaseView { static styles = [ super.styles, headerStyles, - cardStyles, + tableStyles, departuresViewStyles ]; @@ -54,56 +54,6 @@ class DeparturesView extends BaseView { if (previous.has('isOffline') && this.viewState === null) await this.updateViewState(); } - renderView = () => [ - html` - <div class="header-container"> - <header> - <a class="icon-back ${history.length !== 1 ? '': 'invisible'}" title="${t('back')}" @click=${() => history.back()}></a> - <div class="container"> - <h3>Departures from ${this.viewState !== null ? this.viewState.name : '...'}</h3> - </div> - <a class="icon-reload ${!this.isUpdating ? '' : 'spinning'} ${!this.isOffline ? '' : 'invisible'}" title="${t("reload")}" @click=${this.updateViewState}></a> - </header> - </div> - `, - - this.viewState !== null ? html` - <div class="container"> - <div class="card"> - <table> - <thead> - <tr> - <th>Time</th> - <th class="station"></th> - <th>${t('platform')}</th> - </tr> - </thead> - <tbody> - ${(this.viewState.departures || []).map(departure => html` - <tr @click=${() => window.location = `#/t/${this.profile}/${departure.tripId}`}> - <td class="${departure.cancelled ? 'cancelled' : nothing}"> - <span>${timeTemplate(departure)}</span> - </td> - <td class="${departure.cancelled ? 'cancelled' : nothing}"> - <span>${departure.line.name}${departure.direction ? html` → ${departure.direction}` : nothing}</span> - </td> - ${departure.cancelled ? html` - <td><span class="cancelled-text">${t('cancelled-ride')}</span></td> - ` : html` - <td>${platformTemplate(departure)}</td> - `} - </tr> - `)} - </tbody> - </table> - </div> - </div> - <footer-component></footer-component> - ` : !this.isOffline ? - html`<div class="spinner"></div>` - : html`<div class="offline"></div>` - ]; - updateViewState = async () => { if (this.isOffline !== false) return; @@ -131,6 +81,47 @@ class DeparturesView extends BaseView { } this.isUpdating = false; } + + renderView = () => [ + html` + <div class="header-container"> + <header> + <a class="icon-back ${history.length !== 1 ? '': 'invisible'}" title="${t('back')}" @click=${() => history.back()}></a> + <div class="container"> + <h3>Departures from ${this.viewState !== null ? this.viewState.name : '...'}</h3> + </div> + <a class="icon-reload ${!this.isUpdating ? '' : 'spinning'} ${!this.isOffline ? '' : 'invisible'}" title="${t("reload")}" @click=${this.updateViewState}></a> + </header> + </div> + `, + + this.viewState !== null ? html` + <div class="container"> + <div class="table" style="grid-template-columns: 1fr 3.5fr 1fr"> + <div class="row head"> + <span>Time</span> + <span></span> + <span>${t('platform')}</span> + </div> + ${(this.viewState.departures || []).map(departure => html` + <a class="row" href="#/t/${this.profile}/${departure.tripId}"> + <span class="${departure.cancelled ? 'cancelled' : nothing}">${timeTemplate(departure)}</span> + <span style="justify-content:unset" class="${departure.cancelled ? 'cancelled' : nothing}">${departure.line.name}${departure.direction ? html` → ${departure.direction}` : nothing}</span> + ${departure.cancelled ? html` + <span class="cancelled-text">${t('cancelled-ride')}</span> + ` : html` + <span>${platformTemplate(departure)}</span> + `} + </a> + `)} + </tbody> + </div> + </div> + <footer-component></footer-component> + ` : !this.isOffline ? + html`<div class="spinner"></div>` + : html`<div class="offline"></div>` + ]; } customElements.define('departures-view', DeparturesView);
diff --git a/src/journeyView.js b/src/journeyView.js @@ -11,13 +11,13 @@ import { remarksModal, platformTemplate, stopTemplate, timeTemplate } from './te import { formatPoint, formatDuration, formatPrice, formatTrainTypes, formatLineAdditionalName, formatLineDisplayName } from './formatters.js'; import { cachedCoachSequence } from './coach-sequence/index.js'; -import { headerStyles, cardStyles, journeyViewStyles } from './styles.js'; +import { headerStyles, tableStyles, journeyViewStyles } from './styles.js'; class JourneyView extends BaseView { static styles = [ super.styles, headerStyles, - cardStyles, + tableStyles, journeyViewStyles ]; @@ -61,30 +61,36 @@ class JourneyView extends BaseView { renderView = () => [ html` - <div class="header-container"> - <header> - <a id="back" class="icon-back ${history.length !== 1 ? '': 'invisible'}" title="${t('back')}" @click=${() => history.back()}></a> - <div class="container"> - <a class="icon-reload ${this.isUpdating ? 'spinning' : ''} ${!this.isOffline ? '' : 'invisible'}" title="${t("reload")}" @click=${this.refreshJourney}></a> - ${this.viewState !== null ? html` - <h3>${formatPoint(this.viewState.legs[0].origin)} → ${formatPoint(this.viewState.legs[this.viewState.legs.length - 1].destination)}</h3> - <p><b>${t('duration')}: ${formatDuration(this.viewState.duration)} | ${t('changes')}: ${this.viewState.changes-1} | ${t('date')}: ${this.viewState.legs[0].plannedDeparture.formatDate()}${this.settingsState.showPrices && this.viewState.price ? html` | ${t('price')}: <td><span>${formatPrice(this.viewState.price)}</span></td>` : nothing}</b></p> - ` : html` - <h3>... → ...</h3> - <p><b>${t('duration')}: ... | ${t('changes')}: ... | ${t('date')}: ...</b></p> - `} - </div> - <a class="icon-dots" title="${t("more")}" @click=${this.moreModal}></a> - </header> - </div> - `, - this.viewState !== null ? html` - <div class="container journeyView"> - ${this.viewState.legs.map(leg => this.legTemplate(leg))} - </div> - <footer-component></footer-component> + <div class="header-container"> + <header> + <a id="back" class="icon-back ${history.length !== 1 ? '': 'invisible'}" title="${t('back')}" @click=${() => history.back()}></a> + <div class="container"> + <a class="icon-reload ${this.isUpdating ? 'spinning' : ''} ${!this.isOffline ? '' : 'invisible'}" title="${t("reload")}" @click=${this.refreshJourney}></a> + ${this.viewState !== null ? html` + <h3>${formatPoint(this.viewState.legs[0].origin)} → ${formatPoint(this.viewState.legs[this.viewState.legs.length - 1].destination)}</h3> + <p><b> + ${t('duration')}: ${formatDuration(this.viewState.duration)} | + ${t('changes')}: ${this.viewState.changes-1} | + ${t('date')}: ${this.viewState.legs[0].plannedDeparture.formatDate()} + ${this.settingsState.showPrices && this.viewState.price ? + html` | ${t('price')}: <span>${formatPrice(this.viewState.price)}</span>` + : nothing} + </b></p> + ` : html` + <h3>... → ...</h3> + <p><b>${t('duration')}: ... | ${t('changes')}: ... | ${t('date')}: ...</b></p> + `} + </div> + <a class="icon-dots" title="${t("more")}" @click=${this.moreModal}></a> + </header> + </div> + `, this.viewState !== null ? html` + <div class="container"> + ${this.viewState.legs.map(leg => this.legTemplate(leg))} + </div> + <footer-component></footer-component> ` : html` - <div class="spinner"></div> + <div class="spinner"></div> `, ]; @@ -98,45 +104,35 @@ class JourneyView extends BaseView { } else { return html` <div class="card"> - <table> - <thead> - <tr> - <td colspan="4"> - <div class="flex-center"><a href="#/t/${this.profile}/${leg.tripId}">${formatLineDisplayName(leg.line)}${leg.direction ? html` → ${leg.direction}` : nothing}</a> - ${leg.cancelled ? html`<b class="cancelled-text">${t('cancelled-ride')}</b>` : nothing} - ${leg.remarks.length !== 0 ? html` - <a class="link ${leg.remarksIcon}" @click=${() => remarksModal(this, leg.remarks)}></a> - ` : nothing}</div> - </td> - </tr> - <tr> - <td colspan="4"> - <div class="train-details flex-center"> - ${formatLineAdditionalName(leg.line) ? html`<div>Trip: ${formatLineAdditionalName(leg.line)}</div>` : nothing} - ${leg.line.trainType ? html`<div>${t('trainType')}: ${leg.line.trainType}</div>` : nothing} - ${(leg.arrival && leg.departure) ? html`<div>${t('duration')}: ${formatDuration(leg.arrival - leg.departure)}</div>` : nothing} - ${leg.loadFactor ? html`<div>${t(`load-${leg.loadFactor}`)}</div>` : nothing} - </div> - </td> - </tr> - <tr> - <th>${t('arrival')}</th> - <th>${t('departure')}</th> - <th class="station">${t('station')}</th> - <th>${t('platform')}</th> - </tr> - </thead> - <tbody> - ${(leg.stopovers || []).map(stop => html` - <tr class="stop ${stop.cancelled ? 'cancelled' : nothing}"> - <td><span>${timeTemplate(stop, 'arrival')}</span></td> - <td><span>${timeTemplate(stop, 'departure')}</span></td> - <td>${stopTemplate(this.profile, stop.stop)}</td> - <td><span>${platformTemplate(stop)}</span></td> - </tr> - `)} - </tbody> - </table> + <div class="head flex-center"> + <a href="#/t/${this.profile}/${leg.tripId}">${formatLineDisplayName(leg.line)}${!leg.direction ? nothing : html` → ${leg.direction}`}</a> + ${!leg.cancelled ? nothing : html`<b class="cancelled-text">${t('cancelled-ride')}</b>`} + ${leg.remarks.length === 0 ? nothing : html` + <a class="${leg.remarksIcon}" @click=${() => remarksModal(this, leg.remarks)}></a> + `} + </div> + <div class="head flex-center"> + ${formatLineAdditionalName(leg.line) === null ? nothing : html`<span>Trip: ${formatLineAdditionalName(leg.line)}</span>`} + ${!leg.line.trainType ? nothing : html`<span>${t('trainType')}: ${leg.line.trainType}</span>`} + ${(!leg.arrival && !leg.departure) ? nothing : html`<span>${t('duration')}: ${formatDuration(leg.arrival - leg.departure)}</span>`} + ${!leg.loadFactor ? nothing : html`<span>${t(`load-${leg.loadFactor}`)}</span>`} + </div> + <div class="table"> + <div class="row head"> + <span>${t('arrival')}</span> + <span>${t('departure')}</span> + <span>${t('station')}</span> + <span>${t('platform')}</span> + </div> + ${(leg.stopovers || []).map(stop => html` + <div class="row"> + <span>${timeTemplate(stop, 'arrival')}</span> + <span>${timeTemplate(stop, 'departure')}</span> + <span>${stopTemplate(this.profile, stop.stop)}</span> + <span>${platformTemplate(stop)}</span> + </div> + `)} + </div> </div> `; }
diff --git a/src/journeysView.js b/src/journeysView.js @@ -7,7 +7,7 @@ import { timeTemplate } from './templates.js'; import { settings } from './settings.js'; import { t } from './languages.js'; -import { headerStyles, cardStyles, journeysViewStyles } from './styles.js'; +import { headerStyles, tableStyles, journeysViewStyles } from './styles.js'; import { JourneysCanvas } from './journeysCanvas.js'; @@ -15,7 +15,7 @@ export class JourneysView extends JourneysCanvas { static styles = [ super.styles, headerStyles, - cardStyles, + tableStyles, journeysViewStyles ]; @@ -117,24 +117,21 @@ export class JourneysView extends JourneysCanvas { <div class="container"> ${!this.viewState.earlierRef ? nothing : html` <a class="arrowButton icon-arrow2 flipped flex-center" title="${t('label_earlier')}" @click=${() => this.moreJourneys('earlier')}></a> + `} - <div class="card"> - <table> - <thead> - <tr> - <th>${t('departure')}</th> - <th>${t('arrival')}</th> - <th>${t('duration')}</th> - <th>${t('changes')}</th> - <th>${t('products')}</th> - ${this.settingsState.showPrices && this.viewState.profile === 'db' ? html`<th>${t('price')}</th>` : nothing} - <th></th> - </tr> - </thead> - <tbody> - ${this.viewState.journeys.map(journey => this.journeyTemplate(journey))} - </tbody> - </table> + <div class="table" style="grid-template-columns: repeat(${this.settingsState.showPrices && this.viewState.profile === 'db' ? '6' : '5'}, 1fr) .3fr"> + <div class="row head"> + <span>${t('departure')}</span> + <span>${t('arrival')}</span> + <span>${t('duration')}</span> + <span>${t('changes')}</span> + <span>${t('products')}</span> + ${this.settingsState.showPrices && this.viewState.profile === 'db' ? html` + <span>${t('price')}</spanv> + ` : nothing} + <span></span> + </div> + ${this.viewState.journeys.map(journey => this.journeyTemplate(journey))} </div> ${!this.viewState.laterRef ? nothing : html` @@ -151,21 +148,21 @@ export class JourneysView extends JourneysCanvas { const lastLeg = journey.legs[journey.legs.length - 1]; return html` - <tr @click=${() => window.location = `#/j/${this.viewState.profile}/${journey.refreshToken}`}> - <td class="${!journey.cancelled ? nothing : 'cancelled'}">${timeTemplate(firstLeg, 'departure')}</td> + <a class="row" href="#/j/${this.viewState.profile}/${journey.refreshToken}"> + <span class="${!journey.cancelled ? nothing : 'cancelled'}">${timeTemplate(firstLeg, 'departure')}</span> ${journey.cancelled ? html` - <td><span class="cancelled-text">${t('cancelled-ride')}</span></td> + <span class="cancelled-text">${t('cancelled-ride')}</span> ` : html` - <td>${timeTemplate(lastLeg, 'arrival')}</td> + <span>${timeTemplate(lastLeg, 'arrival')}</span> `} - <td class="${!journey.cancelled ? nothing : 'cancelled'}" title="${journey.changesDuration > 0 ? 'including '+formatDuration(journey.changesDuration)+' transfer durations' : ''}">${formatDuration(journey.duration)}</td> - <td>${journey.changes-1}</td> - <td>${journey.products}</td> + <span class="${!journey.cancelled ? nothing : 'cancelled'}" title="${journey.changesDuration > 0 ? 'including '+formatDuration(journey.changesDuration)+' transfer durations' : ''}">${formatDuration(journey.duration)}</span> + <span>${journey.changes-1}</span> + <span>${journey.products}</span> ${this.settingsState.showPrices && this.viewState.profile === 'db' ? html` - <td>${formatPrice(journey.price)}</td> + <span>${formatPrice(journey.price)}</span> ` : nothing} - <td><a class="icon-arrow1"></a></td> - </tr> + <span><i class="icon-arrow1"></i></span> + </a> `; }
diff --git a/src/styles.js b/src/styles.js @@ -2,7 +2,7 @@ import baseStyles from './styles/base.css' assert { type: 'css' }; import flexboxStyles from './styles/flexbox.css' assert { type: 'css' }; import buttonInputStyles from './styles/buttonInput.css' assert { type: 'css' }; import headerStyles from './styles/header.css' assert { type: 'css' }; -import cardStyles from './styles/card.css' assert { type: 'css' }; +import tableStyles from './styles/table.css' assert { type: 'css' }; import iconStyles from './styles/icons.css' assert { type: 'css' }; import overlaysStyles from './styles/overlays.css' assert { type: 'css' }; import searchViewStyles from './styles/searchView.css' assert { type: 'css' }; @@ -12,5 +12,5 @@ import departuresViewStyles from './styles/departuresView.css' assert { type: 'c import settingsViewStyles from './styles/settingsView.css' assert { type: 'css' }; import footerStyles from './styles/footer.css' assert { type: 'css' }; -export { baseStyles, flexboxStyles, buttonInputStyles, headerStyles, cardStyles, iconStyles, overlaysStyles, footerStyles }; +export { baseStyles, flexboxStyles, buttonInputStyles, headerStyles, tableStyles, iconStyles, overlaysStyles, footerStyles }; export { searchViewStyles, journeysViewStyles, journeyViewStyles, departuresViewStyles, settingsViewStyles };
diff --git a/src/styles/base.css b/src/styles/base.css @@ -20,6 +20,7 @@ body { a { color: inherit; + text-decoration: none; } .container {
diff --git a/src/styles/card.css b/src/styles/card.css @@ -1,81 +0,0 @@ -.card { - overflow-x: auto; - - table { - border-bottom: 1px solid rgba(0, 0, 0, 0.3); - width: 100%; - background-color: white; - min-width: 390px; - max-width: 1000px; - } - - table a { - padding: 5px 3px; - text-decoration: none; - } - - thead { - .center { - padding: 5px 3px; - } - - tr:not(:last-child) { - background-color: #eee; - } - } - - tbody { - tr { - cursor: pointer; - border-top: 1px solid #ccc; - } - - tr:hover { - background-color: #ddd; - } - - tr:hover td { - background-color: transparent; - } - } - - td, th { - text-align: center; - overflow: hidden; - } - - th { - padding: 10px 5px; - } - - th.station { - width: 60%; - } - - .train-details { - flex-wrap: wrap; - padding: 0 !important; - - div { - margin: .4em 2em; - } - } - - .cancelled { - text-decoration-line: line-through; - } - - .cancelled-text { - font-weight: bold; - color: red !important; - } -} - -@media (min-width: 800px) { - table { - overflow: hidden; - border: none; - margin: 50px auto; - width: 80vw; - } -}
diff --git a/src/styles/departuresView.css b/src/styles/departuresView.css @@ -1,7 +1,5 @@ -tbody td:nth-child(2) { - text-align: unset; -} - -tbody td { - padding: 5px 3px; +@media (min-width: 800px) { + .table { + margin: 50px auto; + } }
diff --git a/src/styles/icons.css b/src/styles/icons.css @@ -6,18 +6,6 @@ content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M17.65 6.35A7.96 7.96 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z" fill="white"/></svg>'); } -.icon-hint { - content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M5 3h14a2 2 0 0 1 2 2v14c0 .53-.21 1.04-.59 1.41-.37.38-.88.59-1.41.59H5c-.53 0-1.04-.21-1.41-.59C3.21 20.04 3 19.53 3 19V5c0-1.11.89-2 2-2m6 6h2V7h-2zm3 8v-2h-1v-4h-3v2h1v2h-1v2z"/></svg>'); -} - -.icon-status { - content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2m8 10V7h-2v6zm0 4v-2h-2v2z"/></svg>'); -} - -.icon-warning { - content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M13 13h-2V7h2m-2 8h2v2h-2m4.73-14H8.27L3 8.27v7.46L8.27 21h7.46L21 15.73V8.27z"/></svg>'); -} - .icon-other { content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m1 17h-2v-2h2zm2.07-7.75-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25"/></svg>'); }
diff --git a/src/styles/journeyView.css b/src/styles/journeyView.css @@ -1,9 +1,21 @@ -tbody:not(:last-child) { - border-bottom: 1px solid rgba(0, 0, 0, .2); -} +.card { + .flex-center { + flex-wrap: wrap; + background-color: rgb(238, 238, 238); + padding: .5em .2em; + + span { + margin: 0 2em; + } + } + + .table { + grid-template-columns: 1fr 1fr 3.5fr 1fr; -thead>tr:nth-child(2) { - border-bottom: 2px solid #ccc; + .head span { + border-top: 2px solid rgb(204, 204, 204); + } + } } p { @@ -36,9 +48,24 @@ p.transfer::before { content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="m21.71 11.29-9-9a.996.996 0 0 0-1.41 0l-9 9a.996.996 0 0 0 0 1.41l9 9c.39.39 1.02.39 1.41 0l9-9a.996.996 0 0 0 0-1.41M14 14.5V12h-4v3H8v-4c0-.55.45-1 1-1h5V7.5l3.5 3.5z" fill="white"/></svg>'); } -.link { - vertical-align: bottom; - cursor: pointer; - max-inline-size: 26px; +a[class^="icon-"] { margin: 0 .3em; -}- \ No newline at end of file +} + +.icon-hint { + content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M5 3h14a2 2 0 0 1 2 2v14c0 .53-.21 1.04-.59 1.41-.37.38-.88.59-1.41.59H5c-.53 0-1.04-.21-1.41-.59C3.21 20.04 3 19.53 3 19V5c0-1.11.89-2 2-2m6 6h2V7h-2zm3 8v-2h-1v-4h-3v2h1v2h-1v2z"/></svg>'); +} + +.icon-status { + content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2m8 10V7h-2v6zm0 4v-2h-2v2z"/></svg>'); +} + +.icon-warning { + content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M13 13h-2V7h2m-2 8h2v2h-2m4.73-14H8.27L3 8.27v7.46L8.27 21h7.46L21 15.73V8.27z"/></svg>'); +} + +@media (min-width: 800px) { + .card { + margin: 50px auto; + } +}
diff --git a/src/styles/journeysView.css b/src/styles/journeysView.css @@ -1,3 +1,13 @@ +.table { + .head span { + padding: .65em .2em; + } + + span { + padding: .45em .2em; + } +} + .icon-table { content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M3 9h14V7H3zm0 4h14v-2H3zm0 4h14v-2H3zm16 0h2v-2h-2zm0-10v2h2V7zm0 6h2v-2h-2z" fill="white" /></svg>'); } @@ -6,6 +16,10 @@ content: url('data:image/svg+xml;utf8,<svg version="1.1" viewBox="0 0 24 24" height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m11 5v14h2v-14zm-4-2v14h2v-14zm10 4h-2v14h2z" fill="white"/></svg>'); } +.relative { + position: relative; +} + @media (max-width: 799px) { .arrowButton { margin: 15px auto;
diff --git a/src/styles/table.css b/src/styles/table.css @@ -0,0 +1,35 @@ +.table { + display: grid; + overflow-x: auto; + background-color: white; + + .row { + display: contents; + } + + .row:not(.head):hover span { + background-color: #ddd; + } + + .head span { + font-weight: bold; + padding: .55em .2em; + } + + span { + display: flex; + align-items: center; + justify-content: center; + padding: .35em .2em; + border-bottom: 1px solid rgba(0, 0, 0, 0.3); + } +} + +.cancelled { + text-decoration-line: line-through; +} + +.cancelled-text { + font-weight: bold; + color: red !important; +}
diff --git a/src/templates.js b/src/templates.js @@ -38,7 +38,7 @@ export const stopTemplate = (profile, stop) => { if (ds100 !== null) stopName += ` (${ds100})`; } - return html`<a class="flex-center" href="#/d/${profile}/${stop.id}">${stopName}</a>`; + return html`<a href="#/d/${profile}/${stop.id}">${stopName}</a>`; } export const platformTemplate = data => {
diff --git a/src/tripView.js b/src/tripView.js @@ -9,13 +9,13 @@ import { formatDuration, formatLineAdditionalName, formatLineDisplayName, format import { getHafasClient } from './hafasClient.js'; import { t } from './languages.js'; -import { headerStyles, cardStyles, journeyViewStyles } from './styles.js'; +import { headerStyles, tableStyles, journeyViewStyles } from './styles.js'; class TripView extends BaseView { static styles = [ super.styles, headerStyles, - cardStyles, + tableStyles, journeyViewStyles ]; @@ -55,80 +55,6 @@ class TripView extends BaseView { if (previous.has('isOffline') && this.viewState === null) await this.updateViewState(); } - renderView = () => [ - html` - <div class="header-container"> - <header> - <a id="back" class="icon-back ${history.length !== 1 ? '': 'invisible'}" title="${t('back')}" @click=${() => history.back()}></a> - <div class="container"> - ${this.viewState !== null ? - html`<h3>Trip of ${formatLineDisplayName(this.viewState.trip.line)} to ${this.viewState.trip.direction}</h3>` - : html`<h3>Trip of ...</h3> - `} - </div> - <a class="icon-reload ${this.isUpdating ? 'spinning' : ''} ${!this.isOffline ? '' : 'invisible'}" title="${t("reload")}" @click=${this.updateViewState}></a> - </header> - </div> - `, - - this.viewState !== null ? html` - <div class="container"> - <div class="card"> - <table> - <thead> - <tr> - <td colspan="4"> - <div class="center"> - ${this.viewState.trip.bahnExpertUrl ? html` - <a href="${this.viewState.trip.bahnExpertUrl}">${formatLineDisplayName(this.viewState.trip.line)}${this.viewState.trip.direction ? html` → ${this.viewState.trip.direction}` : ''}</a> - ` : html ` - ${formatLineDisplayName(this.viewState.trip.line)}${this.viewState.trip.direction ? html` → ${this.viewState.trip.direction}` : ''} - `} - ${this.viewState.trip.cancelled ? html`<b class="cancelled-text">${t('cancelled-ride')}</b>` : ''} - ${this.viewState.trip.remarks.length !== 0 ? html` - <a class="link ${this.viewState.trip.remarksIcon}" @click=${() => remarksModal(this, this.viewState.trip.remarks)}></a> - ` : nothing} - </div> - </td> - </tr> - <tr> - <td colspan="4"> - <div class="train-details flex-center"> - ${formatLineAdditionalName(this.viewState.trip.line) ? html`<div>Trip: ${formatLineAdditionalName(this.viewState.trip.line)}</div>` : nothing} - ${this.viewState.trip.line.trainType ? html`<div>Train type: ${this.viewState.trip.line.trainType}</div>` : nothing} - <div class="${!this.viewState.trip.cancelled ? nothing : 'cancelled'}"> - ${t('duration')}: ${formatDuration(this.viewState.trip.arrival - (this.viewState.trip.departure ? this.viewState.trip.departure : this.viewState.trip.plannedDeparture))} ${this.viewState.trip.departure ? '' : ('(' + t('planned') + ')')} - </div> - ${this.viewState.trip.loadFactor ? html`<div>${t("load-"+this.viewState.trip.loadFactor)}</div>` : nothing} - </div> - </td> - </tr> - <tr> - <th>${t('arrival')}</th> - <th>${t('departure')}</th> - <th class="station">${t('station')}</th> - <th>${t('platform')}</th> - </tr> - </thead> - <tbody> - ${(this.viewState.trip.stopovers || []).map(stop => html` - <tr class="${stop.cancelled ? 'cancelled' : ''}"> - <td>${timeTemplate(stop, 'arrival')}</td> - <td>${timeTemplate(stop, 'departure')}</td> - <td>${stopTemplate(this.profile, stop.stop)}</td> - <td>${platformTemplate(stop)}</td> - </tr> - `)} - </tbody> - </table> - </div> - </div> - <footer-component></footer-component> - ` : !this.isOffline ? - html`<div class="spinner"></div>` - : html`<div class="offline"></div>` - ]; - updateViewState = async () => { if (this.isOffline !== false) return; @@ -169,6 +95,79 @@ class TripView extends BaseView { } this.isUpdating = false; } + + renderView = () => [ + html` + <div class="header-container"> + <header> + <a id="back" class="icon-back ${history.length !== 1 ? '': 'invisible'}" title="${t('back')}" @click=${() => history.back()}></a> + <div class="container"> + ${this.viewState !== null ? html` + <h3>Trip of ${formatLineDisplayName(this.viewState.trip.line)} to ${this.viewState.trip.direction}</h3> + ` : html` + <h3>Trip of ...</h3> + `} + </div> + <a class="icon-reload ${this.isUpdating ? 'spinning' : ''} ${!this.isOffline ? '' : 'invisible'}" title="${t("reload")}" @click=${this.updateViewState}></a> + </header> + </div> + `, + + this.viewState !== null ? html` + <div class="container"> + <div class="card"> + <div class="head flex-center"> + ${!this.viewState.trip.bahnExpertUrl ? nothing : html`<a href="${this.viewState.trip.bahnExpertUrl}">`} + ${formatLineDisplayName(this.viewState.trip.line)}${this.viewState.trip.direction ? html` → ${this.viewState.trip.direction}` : ''} + ${!this.viewState.trip.bahnExpertUrl ? nothing : html`</a>`} + + ${!this.viewState.trip.cancelled ? nothing : html`<b class="cancelled-text">${t('cancelled-ride')}</b>`} + + ${this.viewState.trip.remarks.length === 0 ? nothing : html` + <a class="${this.viewState.trip.remarksIcon}" @click=${() => remarksModal(this, this.viewState.trip.remarks)}></a> + `} + </div> + + <div class="head flex-center"> + ${formatLineAdditionalName(this.viewState.trip.line) !== "" ? nothing : html` + <span>Trip: ${formatLineAdditionalName(this.viewState.trip.line)}</span> + `} + ${!this.viewState.trip.line.trainType ? nothing : html` + <span>Train type: ${this.viewState.trip.line.trainType}</span> + `} + <span class="${!this.viewState.trip.cancelled ? nothing : 'cancelled'}"> + ${t('duration')}: + ${formatDuration(this.viewState.trip.arrival - (this.viewState.trip.departure ? this.viewState.trip.departure : this.viewState.trip.plannedDeparture))} + ${this.viewState.trip.departure ? '' : ('(' + t('planned') + ')')} + </span> + ${!this.viewState.trip.loadFactor ? nothing : html` + <span>${t("load-"+this.viewState.trip.loadFactor)}</span> + `} + </div> + + <div class="table"> + <div class="row head"> + <span>${t('arrival')}</span> + <span>${t('departure')}</span> + <span>${t('station')}</span> + <span>${t('platform')}</span> + </div> + ${(this.viewState.trip.stopovers || []).map(stop => html` + <div class="row ${!stop.cancelled ? '' : 'cancelled'}"> + <span>${timeTemplate(stop, 'arrival')}</span> + <span>${timeTemplate(stop, 'departure')}</span> + <span>${stopTemplate(this.profile, stop.stop)}</span> + <span>${platformTemplate(stop)}</span> + </div> + `)} + </div> + </div> + </div> + <footer-component></footer-component> + ` : !this.isOffline ? + html`<div class="spinner"></div>` + : html`<div class="offline"></div>` + ]; } customElements.define('trip-view', TripView);