feat add contextual toolbar

- add conventional breadcrumbs to every page through a shared toolbar - move graph controls and the homepage status summary into page-specific toolbar items - align pill-shaped toolbar controls across desktop and mobile layouts and expand page coverage

Commit
85695906828045b9996264e89dbde849cd0651d6
Author
Codex <codex@openai.com>
Author date
Committer
Codex <codex@openai.com>
Committer date
Changed files
roles/dashboard/lib/FAPG/DAQ/Dashboard/Controller/Dashboard.pm
index c3039092..6b775daa 100644..100644
@@ -7,22 +7,29 @@
7 7
8 8 sub index ($self) {
9 9 $self->render(
10 Removed: template => 'dashboard/index',
11 Removed: probes => $self->probes,
10 Added: template => 'dashboard/index',
11 Added: probes => $self->probes,
12 Added: breadcrumbs => [ { label => 'Home' } ],
13 Added: show_overview_status => 1,
12 14 );
13 15 }
14 16
15 17 sub status ($self) {
16 18 $self->render(
17 Removed: template => 'dashboard/status',
18 Removed: items => $self->status_items,
19 Added: template => 'dashboard/status',
20 Added: items => $self->status_items,
21 Added: breadcrumbs =>
22 Added: [ { label => 'Home', href => '/' }, { label => 'Status' }, ],
19 23 );
20 24 }
21 25
22 26 sub probe_list ($self) {
23 27 $self->render(
24 Removed: template => 'dashboard/probes',
25 Removed: probes => $self->probes,
28 Added: template => 'dashboard/probes',
29 Added: probes => $self->probes,
30 Added: breadcrumbs =>
31 Added: [ { label => 'Home', href => '/' }, { label => 'Probes' }, ],
32 Added: graph_control_aria_label => 'All probe chart timeframes',
26 33 );
27 34 }
28 35
@@ -49,12 +56,14 @@
49 56 $oldest =~ s/T.*\z//;
50 57
51 58 $self->render(
52 Removed: template => 'dashboard/downloads',
53 Removed: from => $from,
54 Removed: month_from => $month_from,
55 Removed: to => $to,
56 Removed: oldest => $oldest,
57 Removed: probes => $self->probes,
59 Added: template => 'dashboard/downloads',
60 Added: from => $from,
61 Added: month_from => $month_from,
62 Added: to => $to,
63 Added: oldest => $oldest,
64 Added: probes => $self->probes,
65 Added: breadcrumbs =>
66 Added: [ { label => 'Home', href => '/' }, { label => 'Downloads' }, ],
58 67 );
59 68 }
60 69
@@ -65,8 +74,14 @@
65 74 return $self->reply->not_found unless defined $probe;
66 75
67 76 $self->render(
68 Removed: template => 'dashboard/graph',
69 Removed: probe => $probe,
77 Added: template => 'dashboard/graph',
78 Added: probe => $probe,
79 Added: breadcrumbs => [
80 Added: { label => 'Home', href => '/' },
81 Added: { label => 'Probes', href => '/probes' },
82 Added: { label => $probe->{label} },
83 Added: ],
84 Added: graph_control_aria_label => "$probe->{label} chart timeframe",
70 85 );
71 86 }
72 87
roles/dashboard/public/css/app.css
index 3cf2e84c..021b12cb 100644..100644
@@ -57,7 +57,7 @@
57 57 justify-content: flex-start;
58 58 gap: 1rem;
59 59 width: 100%;
60 Removed: margin: 0 0 1.5rem;
60 Added: margin: 0 0 0.75rem;
61 61 border: 1px solid var(--border);
62 62 border-radius: 0 0 0.5rem 0.5rem;
63 63 padding: 0.7rem 0.85rem;
@@ -65,6 +65,92 @@
65 65 box-shadow: 0 8px 24px rgba(23, 33, 27, 0.08);
66 66 }
67 67
68 Added: .toolbar {
69 Added: --toolbar-item-height: 2.75rem;
70 Added:
71 Added: display: flex;
72 Added: align-items: center;
73 Added: justify-content: space-between;
74 Added: gap: 1rem;
75 Added: width: min(1100px, calc(100% - 2rem));
76 Added: margin: 0 auto 1rem;
77 Added: }
78 Added:
79 Added: .breadcrumbs {
80 Added: display: inline-flex;
81 Added: align-self: flex-start;
82 Added: align-items: center;
83 Added: max-width: 100%;
84 Added: min-height: var(--toolbar-item-height);
85 Added: min-width: 0;
86 Added: border: 1px solid var(--border);
87 Added: border-radius: 999px;
88 Added: padding: 0.45rem 0.8rem;
89 Added: background: var(--card);
90 Added: color: var(--muted);
91 Added: font-size: 0.88rem;
92 Added: }
93 Added:
94 Added: .breadcrumbs ol {
95 Added: display: flex;
96 Added: flex-wrap: wrap;
97 Added: gap: 0.4rem;
98 Added: align-items: center;
99 Added: margin: 0;
100 Added: padding: 0;
101 Added: list-style: none;
102 Added: }
103 Added:
104 Added: .breadcrumbs li {
105 Added: display: inline-flex;
106 Added: gap: 0.4rem;
107 Added: align-items: center;
108 Added: }
109 Added:
110 Added: .breadcrumbs li + li::before {
111 Added: color: var(--muted);
112 Added: content: "/";
113 Added: font-weight: 700;
114 Added: }
115 Added:
116 Added: .breadcrumbs a {
117 Added: color: var(--accent);
118 Added: font-weight: 700;
119 Added: text-decoration: none;
120 Added: }
121 Added:
122 Added: .breadcrumbs a:hover {
123 Added: text-decoration: underline;
124 Added: }
125 Added:
126 Added: .breadcrumbs [aria-current="page"] {
127 Added: color: var(--text);
128 Added: font-weight: 700;
129 Added: }
130 Added:
131 Added: .toolbar-items {
132 Added: display: flex;
133 Added: flex: 0 1 auto;
134 Added: align-items: center;
135 Added: justify-content: flex-end;
136 Added: min-height: var(--toolbar-item-height);
137 Added: min-width: 0;
138 Added: margin-left: auto;
139 Added: }
140 Added:
141 Added: .toolbar .graph-control,
142 Added: .toolbar .status-pill,
143 Added: .toolbar .timeframe-control,
144 Added: .toolbar .smoothing-control {
145 Added: min-height: var(--toolbar-item-height);
146 Added: }
147 Added:
148 Added: .toolbar .status-pill,
149 Added: .toolbar .smoothing-control {
150 Added: align-items: center;
151 Added: justify-content: center;
152 Added: }
153 Added:
68 154 .page-brand {
69 155 color: var(--accent);
70 156 font-size: 1.15rem;
@@ -578,10 +664,10 @@
578 664 .graph-control {
579 665 display: flex;
580 666 flex-wrap: wrap;
581 Removed: justify-content: center;
667 Added: justify-content: flex-end;
582 668 align-items: center;
583 669 gap: 0.75rem;
584 Removed: margin-bottom: 1rem;
670 Added: margin: 0;
585 671 }
586 672
587 673 .graph-control .timeframe-option {
@@ -851,10 +937,27 @@
851 937 margin-bottom: 1rem;
852 938 }
853 939
940 Added: .toolbar {
941 Added: flex-direction: column;
942 Added: align-items: stretch;
943 Added: width: min(100% - 1rem, 1100px);
944 Added: margin-bottom: 1rem;
945 Added: }
946 Added:
947 Added: .toolbar-items {
948 Added: width: 100%;
949 Added: margin-left: 0;
950 Added: }
951 Added:
952 Added: .toolbar-items .graph-control {
953 Added: width: 100%;
954 Added: justify-content: flex-start;
955 Added: }
956 Added:
854 957 .page-header {
855 958 align-items: center;
856 959 width: 100%;
857 Removed: margin: 0 0 1rem;
960 Added: margin: 0 0 0.75rem;
858 961 border-right: 0;
859 962 border-left: 0;
860 963 border-radius: 0 0 0.5rem 0.5rem;
roles/dashboard/t/00-homepage.t
index 242b45ad..0bcdfe42 100644..100644
@@ -14,11 +14,21 @@
14 14 $t->get_ok('/')
15 15 ->status_is(200)
16 16 ->element_exists('body > header.page-header')
17 Added: ->element_exists(
18 Added: 'body > header.page-header + div.toolbar + main.site-main')
17 19 ->element_exists_not('main.site-main > header.page-header')
20 Added: ->text_is( 'nav.breadcrumbs [aria-current="page"]', 'Home' )
21 Added: ->element_exists_not('nav.breadcrumbs a[href="/"]')
18 22 ->element_exists('input.nav-toggle[type="checkbox"]')
19 23 ->text_is( 'a.page-brand[href="/"]', 'FAPG' )
20 Removed: ->element_exists('a.header-status-link[href="/status"][data-header-status]')
21 Removed: ->element_exists('a.status-summary[href="/status"][data-overview-status]')
24 Added: ->element_exists(
25 Added: 'a.header-status-link[href="/status"][data-header-status]')
26 Added: ->element_exists(
27 Added: 'div.toolbar-items a.status-summary[href="/status"][data-overview-status]'
28 Added: )
29 Added: ->element_exists_not(
30 Added: 'main.site-main a.status-summary[data-overview-status]')
31 Added: ->element_exists_not('div.toolbar div.graph-control')
22 32 ->element_exists('a.header-status-link span.header-status-led')
23 33 ->element_exists('[data-header-status-item="hub"]')
24 34 ->element_exists('[data-header-status-item="ph"]')
roles/dashboard/t/01-graph-page.t
index a3ccaa81..d788d506 100644..100644
@@ -13,8 +13,13 @@
13 13
14 14 $t->get_ok('/probes/ph')
15 15 ->status_is(200)
16 Removed: ->text_is( 'a.page-brand[href="/"]', 'FAPG' )
17 16 ->element_exists(
17 Added: 'body > header.page-header + div.toolbar + main.site-main')
18 Added: ->text_is( 'nav.breadcrumbs a[href="/"]', 'Home' )
19 Added: ->text_is( 'nav.breadcrumbs a[href="/probes"]', 'Probes' )
20 Added: ->text_is( 'nav.breadcrumbs [aria-current="page"]', 'pH' )
21 Added: ->text_is( 'a.page-brand[href="/"]', 'FAPG' )
22 Added: ->element_exists(
18 23 'a.header-status-link[href="/status"][data-header-status]')
19 24 ->element_exists('a.header-status-link span.header-status-led')
20 25 ->element_exists('[data-header-status-item="hub"]')
@@ -43,7 +48,9 @@
43 48 ->content_like(qr/Weekly/)
44 49 ->content_like(qr/Monthly/)
45 50 ->content_like(qr/Yearly/)
46 Removed: ->element_exists('div.graph-control > div.timeframe-control')
51 Added: ->element_exists(
52 Added: 'div.toolbar-items div.graph-control > div.timeframe-control')
53 Added: ->element_exists_not('main.site-main div.graph-control')
47 54 ->element_exists('[data-timeframe-option="week"]')
48 55 ->element_exists('[data-timeframe-option="month"]')
49 56 ->element_exists(
@@ -61,6 +68,9 @@
61 68
62 69 $t->get_ok('/probes')
63 70 ->status_is(200)
71 Added: ->text_is( 'nav.breadcrumbs a[href="/"]', 'Home' )
72 Added: ->text_is( 'nav.breadcrumbs [aria-current="page"]', 'Probes' )
73 Added: ->element_exists_not('nav.breadcrumbs a[href="/probes"]')
64 74 ->element_exists('div.card > div.card-heading')
65 75 ->element_exists('div.card > div.card-body')
66 76 ->text_is( 'h1 a[href="/probes/ph"]', 'pH' )
@@ -80,7 +90,8 @@
80 90 ->element_exists('a[data-raw-download][data-probe="orp"]')
81 91 ->element_exists('a[data-raw-download][data-probe="ec"]')
82 92 ->element_exists('div.probes-grid')
83 Removed: ->element_exists('div.graph-control > div.timeframe-control')
93 Added: ->element_exists(
94 Added: 'div.toolbar-items div.graph-control > div.timeframe-control')
84 95 ->element_exists_not('div.card div.timeframe-control')
85 96 ->element_exists('[data-timeframe-option="week"]')
86 97 ->element_exists(
roles/dashboard/t/05-status-page.t
index 36f8f4ed..032b6360 100644..100644
@@ -13,10 +13,16 @@
13 13
14 14 $t->get_ok('/status')
15 15 ->status_is(200)
16 Removed: ->text_is('h1', 'DAQ Status')
16 Added: ->element_exists(
17 Added: 'body > header.page-header + div.toolbar + main.site-main')
18 Added: ->text_is( 'nav.breadcrumbs a[href="/"]', 'Home' )
19 Added: ->text_is( 'nav.breadcrumbs [aria-current="page"]', 'Status' )
20 Added: ->element_exists_not('div.toolbar-items')
21 Added: ->text_is( 'h1', 'DAQ Status' )
17 22 ->element_exists('div.card.status-page-card > div.card-heading')
18 23 ->element_exists('div.card.status-page-card > div.card-body')
19 Removed: ->element_exists('a.header-status-link[href="/status"][data-header-status]')
24 Added: ->element_exists(
25 Added: 'a.header-status-link[href="/status"][data-header-status]')
20 26 ->element_exists('table.status-table')
21 27 ->element_exists('tr[data-status-page-item="hub"]')
22 28 ->element_exists('tr[data-status-page-item="ph"]')
roles/dashboard/t/06-downloads.t
index f36f463c..376e48cd 100644..100644
@@ -14,6 +14,11 @@
14 14
15 15 $t->get_ok('/downloads')
16 16 ->status_is(200)
17 Added: ->element_exists(
18 Added: 'body > header.page-header + div.toolbar + main.site-main')
19 Added: ->text_is( 'nav.breadcrumbs a[href="/"]', 'Home' )
20 Added: ->text_is( 'nav.breadcrumbs [aria-current="page"]', 'Downloads' )
21 Added: ->element_exists_not('div.toolbar-items')
17 22 ->text_is( 'div.nav-links a[href="/probes"]', 'Probes' )
18 23 ->text_is( 'div.nav-links a[href="/downloads"]', 'Downloads' )
19 24 ->element_exists(
roles/dashboard/templates/dashboard/graph.html.ep
index b4c9b3bf..ffd78005 100644..100644
@@ -3,8 +3,6 @@
3 3 % layout 'default';
4 4 % title "FAPG DAQ $probe->{label}";
5 5
6 Removed: %= include 'dashboard/graph-control', aria_label => "$probe->{label} chart timeframe"
7 Removed:
8 6 %= include 'dashboard/probe-card', probe => $probe
9 7
10 8 <section class="card message-log" aria-label="Last ten reading messages for <%= $probe->{label} %>">
roles/dashboard/templates/dashboard/index.html.ep
index e931b4bf..ef9425ad 100644..100644
@@ -5,10 +5,6 @@
5 5
6 6 <section class="status-band" aria-label="DAQ status overview">
7 7 <div class="status-overview">
8 Removed: <a class="status-pill status-summary is-unknown" href="/status" data-overview-status>
9 Removed: <span data-status-text>Checking sensors</span>
10 Removed: </a>
11 Removed:
12 8 % for my $probe ($probes->@*) {
13 9 <span
14 10 hidden
roles/dashboard/templates/dashboard/probes.html.ep
index cd4f27eb..fb7ec443 100644..100644
@@ -3,8 +3,6 @@
3 3 % layout 'default';
4 4 % title 'FAPG DAQ Probes';
5 5
6 Removed: %= include 'dashboard/graph-control', aria_label => 'All probe chart timeframes'
7 Removed:
8 6 <div class="probes-grid">
9 7 % for my $probe ($probes->@*) {
10 8 %= include 'dashboard/probe-card', probe => $probe
roles/dashboard/templates/dashboard/toolbar.html.ep
index 00000000..2ac69c41 000000..100644
@@ -0,0 +1,30 @@
1 Added: <!-- -*- mode: web; -*- -->
2 Added:
3 Added: <div class="toolbar">
4 Added: <nav class="breadcrumbs" aria-label="Breadcrumb">
5 Added: <ol>
6 Added: % for my $index (0 .. $breadcrumbs->$#*) {
7 Added: % my $breadcrumb = $breadcrumbs->[$index];
8 Added: <li>
9 Added: % if (defined $breadcrumb->{href}) {
10 Added: <a href="<%= $breadcrumb->{href} %>"><%= $breadcrumb->{label} %></a>
11 Added: % } else {
12 Added: <span aria-current="page"><%= $breadcrumb->{label} %></span>
13 Added: % }
14 Added: </li>
15 Added: % }
16 Added: </ol>
17 Added: </nav>
18 Added: % if (defined $graph_control_aria_label || $show_overview_status) {
19 Added: <div class="toolbar-items">
20 Added: % if (defined $graph_control_aria_label) {
21 Added: %= include 'dashboard/graph-control', aria_label => $graph_control_aria_label
22 Added: % }
23 Added: % if ($show_overview_status) {
24 Added: <a class="status-pill status-summary is-unknown" href="/status" data-overview-status>
25 Added: <span data-status-text>Checking sensors</span>
26 Added: </a>
27 Added: % }
28 Added: </div>
29 Added: % }
30 Added: </div>
roles/dashboard/templates/layouts/default.html.ep
index 8f1fbe60..e1dbf916 100644..100644
@@ -39,6 +39,7 @@
39 39 data-header-status-label="<%= $item->{label} %>"></span>
40 40 % }
41 41 </header>
42 Added: %= include 'dashboard/toolbar', breadcrumbs => (stash('breadcrumbs') // []), graph_control_aria_label => stash('graph_control_aria_label'), show_overview_status => stash('show_overview_status')
42 43 <main class="site-main">
43 44 <%= content %>
44 45 </main>