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
This commit is contained in:
James Read 2024-04-02 22:58:23 +01:00 committed by GitHub
parent ce670cf58c
commit 7dd1d0a7fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 192 additions and 167 deletions

View File

@ -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

View File

@ -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
})

View File

@ -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>
<input type = "checkbox" id = "hide-sidebar-checkbox" hidden checked />
<nav hidden>
<h1 id = "page-title">OliveTin</h1>
<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>"
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>'
}
bigErrorDialog.showModal()
console.error('Error ' + type + ': ', message)
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>"
document.body.prepend(domErr)
}
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>

View File

@ -99,33 +99,53 @@ 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)
}
export function setupSectionNavigation (style) {
function setSectionNavigationVisible (visible) {
const nav = document.querySelector('nav')
const btn = document.getElementById('sidebar-toggler-button')
if (style === 'sidebar') {
nav.classList += 'sidebar'
document.body.classList += 'has-sidebar'
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 += 'topbar'
document.body.classList += 'has-topbar'
nav.classList.remove('shown')
btn.setAttribute('aria-pressed', true)
btn.setAttribute('aria-label', 'Close sidebar navigation')
btn.innerHTML = '&#9776;'
}
} else {
btn.disabled = true
}
}
nav.hidden = false
export function setupSectionNavigation (style) {
const nav = document.querySelector('nav')
const btn = document.getElementById('sidebar-toggler-button')
if (style === 'sidebar') {
nav.classList.add('sidebar')
document.body.classList.add('has-sidebar')
btn.onclick = () => {
if (nav.classList.contains('shown')) {
setSectionNavigationVisible(false)
} else {
setSectionNavigationVisible(true)
}
}
} else {
nav.classList.add('topbar')
document.body.classList.add('has-topbar')
}
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)
}

View File

@ -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()

View File

@ -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,
footer a {
footer span {
display: inline-block;
}
a {
color: black;
}
#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;
nav ul li a {
display: block;
padding: 0.5em 1em;
user-select: none;
}
#sidebar-toggler-button:hover,
#sidebar-toggler-button:focus {
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 {
height: 2em;
width: 2em;
text-align: center;
display: inline-grid;
place-items: center;
padding: 0;
}
nav {
background-color: white;
display: none;
left: -250px;
}
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;
display: flex;
flex-direction: column;
}
#navigation-links {
nav.shown {
display: flex;
z-index: -1;
flex-direction: column;
left: 0;
}
.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 ul li a:hover {
color: black;
background-color: #efefef;
cursor: pointer;
}
nav.topbar {
background-color: white;
nav.topbar ul {
display: inline-block;
}
nav.topbar ul li {
display: inline-block;
font-size: small;
}
nav.topbar ul li a {
border-radius: .5em;
}
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;
}
h1 a, h1 a:visited {
color: white;
a, a:visited {
color: #bbb;
}
nav {
a:focus {
background-color: #72B7F4;
color: black;
outline: 1px solid #72B7F4;
}
nav.sidebar {
background-color: #111;
color: white;
}