From 609edebbcee22cb9dd6521fa4984af8062b6bccb Mon Sep 17 00:00:00 2001 From: Quentin Ligier Date: Wed, 23 Oct 2024 21:32:09 +0200 Subject: [PATCH] Work --- src/scss/header.scss | 30 ++++++++++++++++ src/scss/index.scss | 4 ++- src/scss/notifications.scss | 59 ++++++++++++++++++++++++++++++++ src/ts/app.ts | 13 ++++--- src/ts/countries.ts | 57 +++++++++++++++++++++++++++++++ src/ts/dom.ts | 10 ++++-- src/ts/notification.ts | 36 ++++++++++++++++++-- static/index.html | 68 +++++++++++++++++++++++++++---------- 8 files changed, 249 insertions(+), 28 deletions(-) create mode 100644 src/scss/notifications.scss diff --git a/src/scss/header.scss b/src/scss/header.scss index f515171..b7e744c 100644 --- a/src/scss/header.scss +++ b/src/scss/header.scss @@ -11,15 +11,45 @@ header { border: 1px solid #ccc; display: inline-block; } + h1 { display: inline-block; margin-left: 20px; color: $oc-blue-9; font-weight: 800; } + #shoebill-version { color: $oc-gray-6; margin-left: 10px; font-size: 0.8rem; } + + div { + float: right; + width: 400px; + } + + #refresh-data { + svg { + width: 24px; + height: 24px; + color: $oc-gray-5; + } + + &.active svg { + animation-name: spin; + animation-duration: 4000ms; + animation-iteration-count: infinite; + animation-timing-function: linear; + @keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + } + } } diff --git a/src/scss/index.scss b/src/scss/index.scss index 5be9f9d..1e67600 100644 --- a/src/scss/index.scss +++ b/src/scss/index.scss @@ -6,6 +6,7 @@ html { font-size: 16px; font-family: 'Assistant', sans-serif; } + #main-width { width: 80%; min-width: 800px; @@ -15,4 +16,5 @@ html { @import "header"; @import "logs"; -@import "footer"; \ No newline at end of file +@import "footer"; +@import "notifications"; \ No newline at end of file diff --git a/src/scss/notifications.scss b/src/scss/notifications.scss new file mode 100644 index 0000000..4d77af6 --- /dev/null +++ b/src/scss/notifications.scss @@ -0,0 +1,59 @@ +@import "colors"; + +#notifications { + z-index: 10; + position: absolute; + bottom: 20px; + right: 20px; + width: 150px; +} + +.notification { + border-left-width: 2px; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + + svg.close { + float: right; + color: $oc-gray-5; + } + + h4 { + svg { + width: 15px; + height: 15px; + margin-right: 15px; + } + } + + p { + margin-left: 30px; + } + + &.error { + border-color: $oc-red-7; + background: $oc-red-0; + + h4 svg { + color: $oc-red-5; + } + } + + &.success { + border-color: $oc-green-5; + background: $oc-green-1; + + h4 svg { + color: $oc-green-6; + } + } + + &.info { + border-color: $oc-blue-8; + background: $oc-blue-2; + + h4 svg { + color: $oc-blue-6; + } + } +} \ No newline at end of file diff --git a/src/ts/app.ts b/src/ts/app.ts index 2663ca1..4062580 100644 --- a/src/ts/app.ts +++ b/src/ts/app.ts @@ -1,5 +1,5 @@ import {fetchIgBuildLogs, IgBuildLog} from "./api"; -import {rebuildLogsInDom} from "./dom"; +import {domNodeDataRefresh, rebuildLogsInDom} from "./dom"; import {notifyError} from "./notification"; let allIgBuildLogs: Array = []; @@ -7,7 +7,11 @@ let fetchingData: boolean = false; const setFetchingData = (value: boolean) => { fetchingData = value; - // TODO: update DOM to show/hide loading spinner + if (value) { + domNodeDataRefresh.classList.add('active'); + } else { + domNodeDataRefresh.classList.remove('active'); + } } const refreshLogs: () => Promise = async () => { @@ -23,10 +27,11 @@ const refreshLogs: () => Promise = async () => { } catch (e: unknown) { setFetchingData(false); if (e instanceof Error) { - notifyError('Failed to fetch logs', e).then(() => {}); + notifyError('Failed to fetch logs', e); } console.error(e); } } -refreshLogs().then(() => {}); \ No newline at end of file +refreshLogs().then(() => { +}); \ No newline at end of file diff --git a/src/ts/countries.ts b/src/ts/countries.ts index a5cb216..6a2cc91 100644 --- a/src/ts/countries.ts +++ b/src/ts/countries.ts @@ -1,10 +1,67 @@ // The list of supported countries. The string value is the name of the flag icon. export enum Country { + Australia = 'au', + Austria = 'at', + Belgium = 'be', + Chile = 'cl', + Czechia = 'cz', + Denmark = 'dk', + Estonia = 'ee', + Finland = 'fi', + France = 'fr', + Italy = 'it', + Netherlands = 'nl', + NewZealand = 'nz', + Portugal = 'pt', + SriLanka = 'lk', + Sweden = 'se', Switzerland = 'ch', + Vietnam = 'vn', } // The association map between repository owners and their respective countries. export const repoOwnersToCountries: { [key: string]: Country } = { + 'hl7au': Country.Australia, + 'HealthIntersections': Country.Australia, + 'AuDigitalHealth': Country.Australia, + 'aehrc': Country.Australia, + + 'HL7Austria': Country.Austria, + + 'hl7-be': Country.Belgium, + + 'Minsal-CL': Country.Chile, + 'HL7Chile': Country.Chile, + + 'HL7-cz': Country.Czechia, + + 'medcomdk': Country.Denmark, + 'fut-infrastructure': Country.Denmark, + 'hl7dk': Country.Denmark, + + 'TEHIK-EE': Country.Estonia, + + 'fhir-fi': Country.Finland, + + 'ansforge': Country.France, + 'Interop-Sante': Country.France, + + 'hl7-it': Country.Italy, + + 'RIVO-Noord': Country.Netherlands, + 'SanteonNL': Country.Netherlands, + + 'HL7NZ': Country.NewZealand, + 'tewhatuora': Country.NewZealand, + + 'hl7-pt': Country.Portugal, + + 'lk-gov-health-hiu': Country.SriLanka, + + 'HL7Sweden': Country.Sweden, + 'hl7ch': Country.Switzerland, 'ehealthsuisse': Country.Switzerland, + + 'hl7vn': Country.Vietnam, } diff --git a/src/ts/dom.ts b/src/ts/dom.ts index 9df2036..1900014 100644 --- a/src/ts/dom.ts +++ b/src/ts/dom.ts @@ -5,6 +5,10 @@ import {dayNameFormatter, mediumDateFormatter, timeFormatter} from "./browser"; const domNodeLogWrapper: HTMLElement | null = document.getElementById("log-wrapper"); const domTemplateLogDay: HTMLElement | null = document.getElementById("log-day-template"); const domTemplateLog: HTMLElement | null = document.getElementById("log-template"); +export const domNodeDataRefresh: HTMLElement = document.getElementById("refresh-data")!; +export const domTemplateNotification: HTMLTemplateElement = document.getElementById("notification-template") as HTMLTemplateElement; +export const domNodeNotifications: HTMLElement = document.getElementById("notifications")!; + if (!domNodeLogWrapper) { throw new Error("Could not find the log wrapper element"); } @@ -22,7 +26,8 @@ const requestRebuild = (targetLog: HTMLElement): void => { if (!dataset["repoOwner"] || !dataset["repoName"] || !dataset["repoBranch"]) { throw new Error("Missing repository information"); } - requestIgBuild(dataset["repoOwner"], dataset["repoName"], dataset["repoBranch"]).then(() => {}); + requestIgBuild(dataset["repoOwner"], dataset["repoName"], dataset["repoBranch"]).then(() => { + }); } domNodeLogWrapper.addEventListener('click', (event: MouseEvent) => { @@ -90,7 +95,8 @@ export const rebuildLogsInDom = (logs: Array) => { if (log.country) { const img = document.createElement('img'); img.src = `images/flags/${log.country}.svg`; - img.alt = `Country: log.country`; + img.alt = `Country: ${log.country}`; + img.title = img.alt; template.content.querySelector('.country')!.appendChild(img); } diff --git a/src/ts/notification.ts b/src/ts/notification.ts index ea712b2..70450f1 100644 --- a/src/ts/notification.ts +++ b/src/ts/notification.ts @@ -1,4 +1,34 @@ -export async function notifyError(title: string, error: Error): Promise { - title; - error; +import {domNodeNotifications, domTemplateNotification} from "./dom"; + +export function notifyError(title: string, error: Error): void { + console.log(title); + console.error(error); + buildNotification(title, error.message, 'error'); +} + +function buildNotification(title: string, message: string | undefined, type: string): void { + const template = domTemplateNotification.cloneNode(true) as HTMLTemplateElement; + template.content.querySelector('.title')!.textContent += title; + if (message) { + template.content.querySelector('.message')!.textContent = message; + } else { + template.content.querySelector('.message')!.remove(); + } + template.content.querySelector('.notification')!.classList.add(type); + template.content.querySelectorAll(`h4 svg:not(.${type})`).forEach(svg => svg.remove()); + + const delayedClose = window.setTimeout(() => { + template.content.querySelector('.notification')?.remove(); + }, 10000); + + (template.content.querySelector('svg.close') as HTMLElement).addEventListener('click', (event: MouseEvent) => { + if (event.target instanceof HTMLElement) { + event.target?.closest('.notification')?.remove(); + } + if (delayedClose) { + window.clearTimeout(delayedClose); + } + }); + + domNodeNotifications.appendChild(template.content); } \ No newline at end of file diff --git a/static/index.html b/static/index.html index b2d38a4..a210c8b 100644 --- a/static/index.html +++ b/static/index.html @@ -13,12 +13,12 @@ - - - - - - + + + + + + @@ -43,6 +43,8 @@

Shoebill Watcher

The sources, license are available in the GitHub repository, you can also report issues or propose new features there. + +
+ + \ No newline at end of file