diff --git a/web/app/src/assets/base.css b/web/app/src/assets/base.css index 1269c431..4d5899a7 100644 --- a/web/app/src/assets/base.css +++ b/web/app/src/assets/base.css @@ -67,7 +67,7 @@ body { color-scheme: dark; font-family: var(--font-family-body); font-size: var(--font-size-base); - height: 100vh; + height: calc(100vh); margin: 0; padding: 0; } @@ -215,6 +215,8 @@ footer { font-size: var(--font-size-sm); grid-area: footer; padding: var(--spacer-sm); + + background-color: var(--color-background-column); } .btn-bar { @@ -360,7 +362,7 @@ ul.status-filter-bar .status-filter-indicator .indicator { background-color: transparent; } -.tabulator-row { +.job-list .tabulator-row, .tasks-list .tabulator-row { cursor: pointer; } @@ -371,3 +373,36 @@ ul.status-filter-bar .status-filter-indicator .indicator { .tabulator-row.active-row.tabulator-row-even { background-color: var(--table-color-background-row-active-even); } + +footer.window-footer { + cursor: pointer; +} + +section.footer-popup { + position: absolute; + bottom: var(--grid-gap); + left: var(--grid-gap); + right: var(--grid-gap); + height: 20vh; + + z-index: 42; + padding: 0.2rem 0.5rem; + + background-color: var(--color-background-column); + border-radius: 0.3rem; + border: thin solid var(--color-border); + + box-shadow: 0 0 2rem black; +} + +section.footer-popup header { + display: flex; +} + +section.footer-popup header h3 { + margin: 0 auto 0 0; +} + +section.footer-popup button { + float: right; +} diff --git a/web/app/src/components/FooterPopup.vue b/web/app/src/components/FooterPopup.vue new file mode 100644 index 00000000..b211c93c --- /dev/null +++ b/web/app/src/components/FooterPopup.vue @@ -0,0 +1,66 @@ + + + +
+ diff --git a/web/app/src/datetime.js b/web/app/src/datetime.js index 73b84fb4..076c5e78 100644 --- a/web/app/src/datetime.js +++ b/web/app/src/datetime.js @@ -5,17 +5,25 @@ const relativeTimeDefaultOptions = { format: DateTime.DATE_MED_WITH_WEEKDAY, } +/** + * Convert the given timestamp to a Luxon time object. + * + * @param {Date | string} timestamp either a Date object or an ISO time string. + * @returns Luxon time object. + */ +function parseTimestamp(timestamp) { + if (timestamp instanceof Date) { + return DateTime.fromJSDate(timestamp); + } + return DateTime.fromISO(timestamp); +} + // relativeTime parses the timestamp (can be ISO-formatted string or JS Date // object) and returns it in string form. The returned string is either "xxx // time ago" if it's a relatively short time ago, or the formatted absolute time // otherwise. export function relativeTime(timestamp, options) { - let parsedTimestamp = null; - if (timestamp instanceof Date) { - parsedTimestamp = DateTime.fromJSDate(timestamp); - } else { - parsedTimestamp = DateTime.fromISO(timestamp); - } + const parsedTimestamp = parseTimestamp(timestamp); if (!options) options = relativeTimeDefaultOptions; @@ -25,3 +33,12 @@ export function relativeTime(timestamp, options) { return parsedTimestamp.toLocaleString(options.format); return parsedTimestamp.toRelative({style: "narrow"}); } + +export function shortened(timestamp) { + const parsedTimestamp = parseTimestamp(timestamp); + const now = DateTime.local(); + const ageInHours = now.diff(parsedTimestamp).as('hours'); + if (ageInHours < 24) + return parsedTimestamp.toLocaleString(DateTime.TIME_24_SIMPLE); + return parsedTimestamp.toLocaleString(DateTime.DATE_MED_WITH_WEEKDAY); +} diff --git a/web/app/src/stores/notifications.js b/web/app/src/stores/notifications.js index 9be88171..9b10d09d 100644 --- a/web/app/src/stores/notifications.js +++ b/web/app/src/stores/notifications.js @@ -8,12 +8,19 @@ const MESSAGE_HIDE_DELAY_MS = 5000; */ export const useNotifs = defineStore('notifications', { state: () => ({ - /** @type {{ msg: string, time: Date }[]} */ + /** + * History of notifications. + * + * The 'id' is just for Tabulator to uniquely identify rows, in order to be + * able to scroll to them. + * + * @type {{ id: Number, msg: string, time: Date }[]} */ history: [], - /** @type { msg: string, time: Date } */ + /** @type { id: Number, msg: string, time: Date } */ last: "", hideTimerID: 0, + lastID: 0, }), actions: { /** @@ -21,9 +28,10 @@ export const useNotifs = defineStore('notifications', { * @param {string} message */ add(message) { - const notif = {msg: message, time: new Date()}; + const notif = {id: this._generateID(), msg: message, time: new Date()}; this.history.push(notif); this.last = notif; + console.log("New notification:", plain(notif)); this._prune(); this._restartHideTimer(); }, @@ -44,5 +52,8 @@ export const useNotifs = defineStore('notifications', { last: "", }); }, + _generateID() { + return ++this.lastID; + } }, }) diff --git a/web/app/src/views/JobsView.vue b/web/app/src/views/JobsView.vue index 9cd7c8c7..3bd93af2 100644 --- a/web/app/src/views/JobsView.vue +++ b/web/app/src/views/JobsView.vue @@ -9,12 +9,13 @@