Parcourir la source

Markup optimisations (#258)

* feature: Cleanup markup for accessibility

* feature: Cleanup markup for accessibility

* feature: Cleanup markup for accessibility

* feature: Cleanup markup for accessibility

* bugfix: Broken test

* feature: Cleanup markup for accessibility
James Read il y a 2 ans
Parent
commit
7dd1d0a7fc
6 fichiers modifiés avec 183 ajouts et 158 suppressions
  1. 1 1
      .air.toml
  2. 1 1
      integration-tests/test/hiddenNav.mjs
  3. 35 43
      webui.dev/index.html
  4. 36 15
      webui.dev/js/marshaller.js
  5. 18 7
      webui.dev/main.js
  6. 92 91
      webui.dev/style.css

+ 1 - 1
.air.toml

@@ -7,7 +7,7 @@ tmp_dir = "tmp"
   bin = "./OliveTin"
   cmd = "go build -o OliveTin github.com/OliveTin/OliveTin/cmd/OliveTin"
   delay = 1
-  exclude_dir = ["assets", "tmp", "vendor", "testdata"]
+  exclude_dir = ["assets", "tmp", "vendor", "testdata", "webui.dev", "webui"]
   exclude_file = []
   exclude_regex = ["_test.go"]
   exclude_unchanged = false

+ 1 - 1
integration-tests/test/hiddenNav.mjs

@@ -13,7 +13,7 @@ describe('config: hiddenNav', function () {
   it('nav is hidden', async () => {
     await webdriver.get(runner.baseUrl())
 
-    const toggler = await webdriver.findElement(By.id('sidebar-toggle-wrapper'))
+    const toggler = await webdriver.findElement(By.tagName('header'))
 
     expect(await toggler.isDisplayed()).to.be.false
   })

+ 35 - 43
webui.dev/index.html

@@ -16,25 +16,27 @@
 	</head>
 
 	<body>
-		<main title = "main content">
-			<div id = "perma-widget">
-				<div id = "sidebar-toggle-wrapper">
-					<label for = "hide-sidebar-checkbox" id = "sidebar-toggler-button" title = "Toggle sidebar" tabindex = "0">&#9776;</label>
-				</div>
-				<h1 id = "page-title">OliveTin</h1>
-			</div>
+		<header>
+			<button id = "sidebar-toggler-button" aria-label = "Open sidebar navigation" aria-pressed = "false" aria-haspopup = "menu">&#9776;</button>
+
+			<h1 id = "page-title">OliveTin</h1>
 
-			<input type = "checkbox" id = "hide-sidebar-checkbox" hidden checked />
-			<nav hidden>
+			<nav>
 				<ul id = "navigation-links">
-					<li title = "Actions"><a id = "showActions">Actions</a></li>
+					<li title = "Actions">
+						<a id = "showActions" href = "#actions">Actions</a>
+					</li>
 				</ul>
 
 				<ul id = "supplemental-links">
-					<li title = "Logs"><a id = "showLogs">Logs</a></li>
+					<li title = "Logs">
+						<a id = "showLogs" href = "#logs">Logs</a>
+					</li>
 				</ul>
 			</nav>
+		</header>
 
+		<main title = "main content">
 			<section id = "contentLogs" title = "Logs" hidden>
 				<div class = "toolbar">
 					<input placeholder = "Search for action name" id = "logSearchBox" />
@@ -79,6 +81,10 @@
 			</p>
 		</footer>
 
+		<dialog title = "Big Error Message" id = "big-error" class = "error">
+
+		</dialog>
+
 		<dialog title = "Execution Results" id = "execution-results-popup">
 			<div class = "action-header">
 				<span id = "execution-dialog-icon" class = "icon" role = "img"></span>
@@ -107,16 +113,12 @@
 			<div id = "execution-dialog-output">
 				<details>
 					<summary>Standard Output</summary>
-					<pre id = "execution-dialog-stdout">
-						?
-					</pre>
+					<pre id = "execution-dialog-stdout">?</pre>
 				</details>
 
 				<details>
 					<summary>Standard Error</summary>
-					<pre id = "execution-dialog-stderr">
-						?
-					</pre>
+					<pre id = "execution-dialog-stderr">?</pre>
 				</details>
 			</div>
 
@@ -150,7 +152,7 @@
 		<template id = "tplActionButton">
 			<button>
 				<span title = "action button icon" class = "icon">&#x1f4a9;</span>
-				<span class = "title">Untitled Button</span>
+				<span class = "title" aria-live = "polite">Untitled Button</span>
 			</button>
 
 			<div class = "action-button-footer" hidden></div>
@@ -165,16 +167,12 @@
 
 					<details>
 						<summary>stdout</summary>
-						<pre class = "stdout">
-							?
-						</pre>
+						<pre class = "stdout">?</pre>
 					</details>
 
 					<details>
 						<summary>stderr</summary>
-						<pre class = "stderr">
-							?
-						</pre>
+						<pre class = "stderr">?</pre>
 					</details>
 
 					<div class = "tags"></div>
@@ -184,34 +182,28 @@
 		</template>
 
 		<script type = "text/javascript">
+			const bigErrorDialog = document.getElementById('big-error')
+
 			/**
 			This is the bootstrap code, which relies on very simple, old javascript
 		  	to at least display a helpful error message if we can't use OliveTin.
 			*/
-			window.showBigError = function (type, friendlyType, message) {
-			  clearBigErrors(type)
+			window.showBigError = function (type, friendlyType, message, isFatal) {
+			  bigErrorDialog.innerHTML = '<h1>Error ' + friendlyType + '</h1><p>' + message + "</p><p><a href = 'http://docs.olivetin.app/err-" + type + ".html' target = 'blank'/>" + type + " error in OliveTin Documentation</a></p>"
 
-			  console.error('Error ' + type + ': ', message)
+			  if (isFatal) {
+				  bigErrorDialog.innerHTML += '<p>You will need to refresh your browser to clear this message.</p>'
+			  } else {
+				  bigErrorDialog.innerHTML += '<p>This error message will go away automatically if the problem is solved.</p>'
+			  }
 
-			  const domErr = document.createElement('div')
-			  domErr.classList.add('error')
-			  domErr.classList.add(type)
-			  domErr.innerHTML = '<h1>Error ' + friendlyType + '</h1><p>' + message + "</p><p><a href = 'http://docs.olivetin.app/err-" + type + ".html' target = 'blank'/>" + type + " error in OliveTin Documentation</a></p>"
+			  bigErrorDialog.showModal()
 
-			  document.body.prepend(domErr)
+			  console.error('Error ' + type + ': ', message)
 			}
 
-			window.clearBigErrors = function (additionalClass) {
-			  let selector = 'div.error'
-
-		      if (additionalClass != null) {
-				  selector += '.' + additionalClass
-			  }
-
-			  for (const oldError of document.querySelectorAll(selector).values()) {
-				window.old = oldError;
-				oldError.remove();
-			  }
+			window.clearBigErrors = function () {
+			  bigErrorDialog.close()
 			}
 		</script>
 

+ 36 - 15
webui.dev/js/marshaller.js

@@ -99,34 +99,54 @@ function showSection (title) {
     }
   }
 
-  for (const otherName of ['Actions', 'Logs']) {
-    document.getElementById('show' + otherName).classList.remove('activeSection')
-    document.getElementById('content' + otherName).hidden = true
-  }
-
-  //  document.getElementById('show' + name).classList.add('activeSection')
-  // document.getElementById('content' + name).hidden = false
-
-  document.getElementById('hide-sidebar-checkbox').checked = true
+  setSectionNavigationVisible(false)
 
   changeDirectory(null)
 }
 
+function setSectionNavigationVisible (visible) {
+  const nav = document.querySelector('nav')
+  const btn = document.getElementById('sidebar-toggler-button')
+
+  if (document.body.classList.contains('has-sidebar')) {
+    if (visible) {
+      nav.classList.add('shown')
+      btn.setAttribute('aria-pressed', false)
+      btn.setAttribute('aria-label', 'Open sidebar navigation')
+      btn.innerHTML = '&laquo;'
+    } else {
+      nav.classList.remove('shown')
+      btn.setAttribute('aria-pressed', true)
+      btn.setAttribute('aria-label', 'Close sidebar navigation')
+      btn.innerHTML = '&#9776;'
+    }
+  } else {
+    btn.disabled = true
+  }
+}
+
 export function setupSectionNavigation (style) {
   const nav = document.querySelector('nav')
+  const btn = document.getElementById('sidebar-toggler-button')
 
   if (style === 'sidebar') {
-    nav.classList += 'sidebar'
+    nav.classList.add('sidebar')
 
-    document.body.classList += 'has-sidebar'
+    document.body.classList.add('has-sidebar')
+
+    btn.onclick = () => {
+      if (nav.classList.contains('shown')) {
+        setSectionNavigationVisible(false)
+      } else {
+        setSectionNavigationVisible(true)
+      }
+    }
   } else {
-    nav.classList += 'topbar'
+    nav.classList.add('topbar')
 
-    document.body.classList += 'has-topbar'
+    document.body.classList.add('has-topbar')
   }
 
-  nav.hidden = false
-
   document.getElementById('showActions').onclick = () => { showSection('Actions') }
   document.getElementById('showLogs').onclick = () => { showSection('Logs') }
 }
@@ -159,6 +179,7 @@ function marshalDashboardStructureToHtml (json) {
     const navigationA = document.createElement('a')
     navigationA.title = dashboard.title
     navigationA.innerText = dashboard.title
+    navigationA.setAttribute('href', '#' + dashboard.title)
     navigationA.onclick = () => {
       showSection(dashboard.title)
     }

+ 18 - 7
webui.dev/main.js

@@ -1,6 +1,11 @@
 'use strict'
 
-import { initMarshaller, setupSectionNavigation, marshalDashboardComponentsJsonToHtml, marshalLogsJsonToHtml } from './js/marshaller.js'
+import {
+  initMarshaller,
+  setupSectionNavigation,
+  marshalDashboardComponentsJsonToHtml,
+  marshalLogsJsonToHtml
+} from './js/marshaller.js'
 import { checkWebsocketConnection } from './js/websocket.js'
 
 function searchLogs (e) {
@@ -68,7 +73,7 @@ function fetchGetDashboardComponents () {
     return res.json()
   }).then(res => {
     if (!window.restAvailable) {
-      window.clearBigErrors('fetch-buttons')
+      window.clearBigErrors()
     }
 
     window.restAvailable = true
@@ -77,7 +82,7 @@ function fetchGetDashboardComponents () {
     refreshServerConnectionLabel() // in-case it changed, update the label quicker
   }).catch((err) => { // err is 1st arg
     window.restAvailable = false
-    window.showBigError('fetch-buttons', 'getting buttons', err, 'blat')
+    window.showBigError('fetch-buttons', 'getting buttons', err, false)
   })
 }
 
@@ -89,11 +94,13 @@ function fetchGetLogs () {
   }).then(res => {
     marshalLogsJsonToHtml(res)
   }).catch(err => {
-    window.showBigError('fetch-buttons', 'getting buttons', err, 'blat')
+    window.showBigError('fetch-buttons', 'getting buttons', err, false)
   })
 }
 
 function processWebuiSettingsJson (settings) {
+  setupSectionNavigation(settings.SectionNavigationStyle)
+
   window.restBaseUrl = settings.Rest
 
   if (settings.ThemeName) {
@@ -112,8 +119,13 @@ function processWebuiSettingsJson (settings) {
     document.querySelector('#available-version').hidden = false
   }
 
-  document.querySelector('#perma-widget').hidden = !settings.ShowNavigation
-  document.querySelector('footer[title="footer"]').hidden = !settings.ShowFooter
+  if (!settings.ShowNavigation) {
+    document.querySelector('header').style.display = 'none'
+  }
+
+  if (!settings.ShowFooter) {
+    document.querySelector('footer[title="footer"]').style.display = 'none'
+  }
 
   window.pageTitle = 'OliveTin'
 
@@ -130,7 +142,6 @@ function processWebuiSettingsJson (settings) {
 function main () {
   initMarshaller()
   setupLogSearchBox()
-  setupSectionNavigation('sidebar')
 
   window.fetch('webUiSettings.json').then(res => {
     return res.json()

+ 92 - 91
webui.dev/style.css

@@ -5,6 +5,8 @@ body {
   margin: 0;
   padding: 0;
   text-align: center;
+  display: flex;
+  flex-direction: column;
 }
 
 dialog {
@@ -33,53 +35,78 @@ fieldset {
   border: 0;
 }
 
-#sidebar-toggle-wrapper {
-  display: inline;
+footer {
+  font-size: smaller;
+}
+
+footer span {
+  display: inline-block;
+}
+
+a {
+  color: black;
+}
+
+nav ul li a {
+  display: block;
+  padding: 0.5em 1em;
+  user-select: none;
 }
 
-footer,
-footer a {
+a:focus {
+  background-color: black;
+  color: white;
+  outline: 1px solid black;
+}
+
+nav ul li a:hover {
+  background-color: #efefef;
   color: black;
+  cursor: pointer;
 }
 
 #sidebar-toggler-button {
-  top: 1em;
-  left: 0.5em;
   height: 2em;
   width: 2em;
-  background-color: white;
   text-align: center;
   display: inline-grid;
   place-items: center;
-  padding: .2em;
-  box-shadow: 0 0 5px 0 #444;
-  color: #666;
+  padding: 0;
 }
 
-#sidebar-toggler-button:hover,
-#sidebar-toggler-button:focus {
-  color: black;
-  cursor: pointer;
+nav {
+  display: none;
+  left: -250px;
 }
 
-nav {
-  background-color: white;
+nav.topbar {
+  display: flex;
+  flex-direction: row;
+  place-content: end;
+  border-radius: .5em;
+  padding-right: 1em;
+  padding-left: 1em;
+  flex-grow: 1;
 }
 
 nav.sidebar {
+  background-color: white;
   position: fixed;
   width: 180px;
   height: 100dvh;
-  left: 0;
   top: 0;
   transition: left 0.5s ease;
   box-shadow: 0 0 10px 0 #444;
-  z-index: 3;
+}
+
+nav.shown {
   display: flex;
+  z-index: -1;
   flex-direction: column;
+  left: 0;
 }
 
-#navigation-links {
+.sidebar #navigation-links {
   padding-top: 4em;
   flex-grow: 1;
 }
@@ -88,55 +115,34 @@ nav.sidebar {
   flex-grow: 0;
 }
 
-input:checked ~ nav.sidebar {
-  left: -250px;
-}
-
 h1 {
   display: inline;
   font-size: small;
   padding-left: .5em;
 }
 
-h1 a {
-  color: black;
-}
-
 nav ul {
   margin: 0;
   padding: 0;
 }
 
-nav ul li {
-  list-style: none;
-  text-align: left;
-  border-bottom: 1px inset black;
-}
-
-nav ul li a {
-  display: block;
-  padding-left: 1em;
-  padding-top: 0.5em;
-  padding-bottom: 0.5em;
-  user-select: none;
-}
-
-h1 a:visited {
-  color: black;
+nav.topbar ul {
+  display: inline-block;
 }
 
-nav ul li a:hover {
-  color: black;
-  background-color: #efefef;
-  cursor: pointer;
+nav.topbar ul li {
+  display: inline-block;
+  font-size: small;
 }
 
-nav.topbar {
-  background-color: white;
+nav.topbar ul li a {
+  border-radius: .5em;
 }
 
-nav.topbar ul li {
-  display: inline-block;
+nav.sidebar ul li {
+  list-style: none;
+  text-align: left;
+  border-bottom: 1px inset black;
 }
 
 table {
@@ -190,10 +196,6 @@ tr.log-row span.icon {
   background-color: salmon;
 }
 
-div.error {
-  padding: 1em;
-}
-
 .title.temporary-status-message {
   color: gray;
 }
@@ -240,6 +242,7 @@ input[type="submit"]
   background-color: white;
   box-shadow: 0 0 6px 0 #aaa;
   user-select: none;
+  transition: background-color 1s ease, color 1s ease;
 }
 
 /* Action Buttons */
@@ -251,10 +254,14 @@ action-button {
 action-button button {
   flex-grow: 1;
   width: 100%;
-  z-index: 2;
+  z-index: 1;
   border-radius: .7em;
 }
 
+span.title {
+  display: inline;
+}
+
 action-button details {
   flex-grow: 1;
 }
@@ -304,37 +311,29 @@ input[type="submit"]:focus {
 
 button:disabled,
 input[type="submit"]:disabled {
-  color: gray;
-  background-color: #333;
+  color: #3c3c3c;
+  background-color: #cecece;
   cursor: not-allowed;
 }
 
 
-/* Button animations */
-
 .action-failed {
-  transition: 1s;
   background-color: red;
 }
 
 .action-success {
-  transition: 1s;
   background-color: limegreen;
 }
 
 .action-nonzero-exit {
-  animation: kf-action-nonzero-exit 1s;
-  transition: 1s;
   background-color: orange;
 }
 
 .action-timeout {
-  transition: 1s;
   background-color: cyan;
 }
 
 .action-blocked {
-  transition: 1s;
   background-color: purple;
   color: white;
 }
@@ -347,10 +346,7 @@ img.logo {
 
 main {
   padding: 1em;
-}
-
-body.has-sidebar main {
-  padding-top: 3em;
+  padding-top: 3.5em;
 }
 
 summary {
@@ -369,15 +365,18 @@ label {
   padding-right: 1em;
 }
 
-#perma-widget {
-  position: absolute;
-  top: 1em;
-  left: 1em;
-  z-index: 9;
-}
-
-#perma-widget label {
-  display: inline;
+header {
+  text-align: left;
+  display: flex;
+  flex-direction: row;
+  z-index: 3;
+  align-items: center;
+  padding-left: 1em;
+  padding-bottom: .2em;
+  padding-top: 1em;
+  position: fixed;
+  background-color: #dee3e7;
+  width: 100vw;
 }
 
 input {
@@ -505,31 +504,33 @@ div.toolbar * {
   }
 
   button,
-  input[type="submit"],
-  #sidebar-toggler-button {
+  input[type="submit"] {
     border: 1px solid #666;
     background-color: #222;
     box-shadow: 0 0 6px 0 #444;
     color: white;
   }
 
-  #sidebar-toggler-button:focus,
-  #sidebar-toggler-button:hover
-  {
-    color: white;
+  button:focus,
+  input[type="submit"]:focus {
+    outline: 2px solid #72B7F4;
   }
 
-  footer,
-  footer a {
-    color: gray;
+  footer {
+    color: #bbb;
   }
 
+  a, a:visited {
+    color: #bbb;
+  }
 
-  h1 a, h1 a:visited {
-    color: white;
+  a:focus {
+    background-color: #72B7F4;
+    color: black;
+    outline: 1px solid #72B7F4;
   }
 
-  nav {
+  nav.sidebar {
     background-color: #111;
     color: white;
   }