Skip to content

Commit e5ea340

Browse files
committed
Add detail page for ZWA-2 firmware install
1 parent 8fbd8da commit e5ea340

File tree

6 files changed

+233
-70
lines changed

6 files changed

+233
-70
lines changed

public/svgs/chevron-right.svg

Lines changed: 1 addition & 0 deletions
Loading

src/components/details.ts

Lines changed: 116 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
import '@awesome.me/webawesome/dist/components/button/button.js';
12
import '@awesome.me/webawesome/dist/components/icon/icon.js';
23
import { Router } from '@vaadin/router';
34
import { LitElement, css, html, nothing } from 'lit';
45
import { customElement, property, state } from 'lit/decorators.js';
56
export interface DetailsAction {
6-
variant?: 'primary' | 'default' | 'secondary';
77
title: string;
88
description: string;
99
href: string;
1010
label: string;
1111
icon: string;
12+
experimental?: boolean;
13+
trailingIcon?: string;
1214
}
1315

1416
export interface DetailsConfig {
@@ -19,11 +21,15 @@ export interface DetailsConfig {
1921
secondaryDescription?: string;
2022
};
2123
actions: DetailsAction[];
24+
heroCtaLabel?: string;
25+
heroCta?: unknown;
2226
}
2327

2428
@customElement('details-page')
2529
export class DetailsPage extends LitElement {
2630
@property({ type: Object }) config!: DetailsConfig;
31+
@property({ type: Boolean, attribute: 'history-back' }) historyBack = false;
32+
@property({ type: String, attribute: 'back-href' }) backHref = '/';
2733
@state() private isDescriptionExpanded = false;
2834

2935
static styles = css`
@@ -64,6 +70,10 @@ export class DetailsPage extends LitElement {
6470
margin-bottom: 30px;
6571
}
6672
73+
.layout.single {
74+
grid-template-columns: 1fr;
75+
}
76+
6777
.hero {
6878
background: white;
6979
border-radius: 16px;
@@ -127,6 +137,26 @@ export class DetailsPage extends LitElement {
127137
display: none;
128138
}
129139
140+
.hero-cta {
141+
margin: 16px 0 24px 0;
142+
display: inline-flex;
143+
}
144+
145+
.hero-cta button.install {
146+
color: #03a9f4;
147+
background: #fff;
148+
border: 1px solid #03a9f4;
149+
border-radius: 8px;
150+
padding: 10px 16px;
151+
font-weight: 600;
152+
cursor: pointer;
153+
}
154+
155+
.hero-cta button.install:hover {
156+
background: #03a9f4;
157+
color: #fff;
158+
}
159+
130160
.actions-list {
131161
background: white;
132162
border-radius: 16px;
@@ -188,12 +218,30 @@ export class DetailsPage extends LitElement {
188218
line-height: 1.45;
189219
}
190220
221+
.action-subtitle {
222+
display: flex;
223+
align-items: center;
224+
gap: 4px;
225+
}
226+
227+
.experimental {
228+
color: #e78e21;
229+
font-size: 1rem;
230+
line-height: 1.45;
231+
}
232+
191233
.action-trailing svg {
192234
width: 16px;
193235
height: 16px;
194236
fill: #9e9e9e;
195237
}
196238
239+
.action-trailing img {
240+
width: 16px;
241+
height: 16px;
242+
display: block;
243+
}
244+
197245
@media (max-width: 768px) {
198246
.hero[data-expanded='false'] .secondary-description {
199247
display: none;
@@ -226,8 +274,12 @@ export class DetailsPage extends LitElement {
226274
}
227275
`;
228276

229-
private _goHome() {
230-
Router.go('/');
277+
private _goBack() {
278+
if (this.historyBack && window.history.length > 1) {
279+
window.history.back();
280+
return;
281+
}
282+
Router.go(this.backHref || '/');
231283
}
232284

233285
private _handleActionClick(href: string) {
@@ -240,8 +292,9 @@ export class DetailsPage extends LitElement {
240292
}
241293

242294
render() {
243-
const { hero, actions } = this.config ?? ({} as DetailsConfig);
244-
if (!hero || !actions) {
295+
const { hero, actions, heroCtaLabel, heroCta } =
296+
this.config ?? ({} as DetailsConfig);
297+
if (!hero) {
245298
return null;
246299
}
247300

@@ -252,18 +305,18 @@ export class DetailsPage extends LitElement {
252305
class="back-button"
253306
@click=${(e: Event) => {
254307
e.preventDefault();
255-
this._goHome();
308+
this._goBack();
256309
}}
257310
>
258311
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
259312
<path
260313
d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z"
261314
/>
262315
</svg>
263-
Back to Home
316+
${this.historyBack ? 'Back' : 'Back to Home'}
264317
</a>
265318
266-
<div class="layout">
319+
<div class="layout ${actions && actions.length ? '' : 'single'}">
267320
<div
268321
class="hero"
269322
data-expanded="${this.isDescriptionExpanded ? 'true' : 'false'}"
@@ -277,11 +330,17 @@ export class DetailsPage extends LitElement {
277330
>
278331
${hero.description}
279332
</p>
280-
${hero.secondaryDescription
281-
? html`<p class="secondary-description">
282-
${hero.secondaryDescription}
283-
</p>`
284-
: nothing}
333+
${heroCta
334+
? html`<div class="hero-cta">${heroCta}</div>`
335+
: heroCtaLabel
336+
? html`<div class="hero-cta">
337+
<wa-button variant="primary">${heroCtaLabel}</wa-button>
338+
</div>`
339+
: hero.secondaryDescription
340+
? html`<p class="secondary-description">
341+
${hero.secondaryDescription}
342+
</p>`
343+
: nothing}
285344
${html`<button
286345
class="read-more"
287346
@click=${() =>
@@ -291,37 +350,52 @@ export class DetailsPage extends LitElement {
291350
</button>`}
292351
</div>
293352
294-
<div class="actions-list">
295-
${actions.map(
296-
a => html`
297-
<div
298-
class="action-item"
299-
@click=${() => this._handleActionClick(a.href)}
300-
>
301-
<div class="action-icon">
353+
${actions && actions.length
354+
? html`<div class="actions-list">
355+
${actions.map(
356+
a => html`
302357
<div
303-
class="icon-mask"
304-
style="--icon-url: url(${a.icon})"
305-
></div>
306-
</div>
307-
<div class="action-content">
308-
<h3>${a.title}</h3>
309-
<p>${a.description}</p>
310-
</div>
311-
<div class="action-trailing">
312-
<svg
313-
xmlns="http://www.w3.org/2000/svg"
314-
viewBox="0 0 448 512"
358+
class="action-item"
359+
@click=${() => this._handleActionClick(a.href)}
315360
>
316-
<path
317-
d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l82.7 0-201.4 201.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3 448 192c0 17.7 14.3 32 32 32s32-14.3 32-32l0-160c0-17.7-14.3-32-32-32L320 0zM80 96C35.8 96 0 131.8 0 176L0 432c0 44.2 35.8 80 80 80l256 0c44.2 0 80-35.8 80-80l0-80c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 80c0 8.8-7.2 16-16 16L80 448c-8.8 0-16-7.2-16-16l0-256c0-8.8 7.2-16 16-16l80 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L80 96z"
318-
/>
319-
</svg>
320-
</div>
321-
</div>
322-
`
323-
)}
324-
</div>
361+
<div class="action-icon">
362+
<div
363+
class="icon-mask"
364+
style="--icon-url: url(${a.icon})"
365+
></div>
366+
</div>
367+
<div class="action-content">
368+
<h3>${a.title}</h3>
369+
<p>
370+
${a.experimental
371+
? html`<span class="experimental"
372+
>Experimental.</span
373+
>`
374+
: nothing}
375+
<span>${a.description}</span>
376+
</p>
377+
</div>
378+
<div class="action-trailing">
379+
${a.trailingIcon
380+
? html`<img
381+
src="${a.trailingIcon}"
382+
alt=""
383+
style="width: 20px; height: 20px;"
384+
/>`
385+
: html`<svg
386+
xmlns="http://www.w3.org/2000/svg"
387+
viewBox="0 0 448 512"
388+
>
389+
<path
390+
d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l82.7 0-201.4 201.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3 448 192c0 17.7 14.3 32 32 32s32-14.3 32-32l0-160c0-17.7-14.3-32-32-32L320 0zM80 96C35.8 96 0 131.8 0 176L0 432c0 44.2 35.8 80 80 80l256 0c44.2 0 80-35.8 80-80l0-80c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 80c0 8.8-7.2 16-16 16L80 448c-8.8 0-16-7.2-16-16l0-256c0-8.8 7.2-16 16-16l80 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L80 96z"
391+
/>
392+
</svg>`}
393+
</div>
394+
</div>
395+
`
396+
)}
397+
</div>`
398+
: nothing}
325399
</div>
326400
</div>
327401
`;

src/main-app.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import './pages/media-player-details';
1313
import './pages/vpe-details';
1414
import './pages/zbt1-details';
1515
import './pages/zwa2-details';
16+
import './pages/zwa2-install-original';
17+
import './pages/zwa2-install-portable';
1618

1719
// Using Vaadin Router for client-side routing
1820

@@ -39,6 +41,8 @@ export class MainApp extends LitElement {
3941
{ path: '/', component: 'home-page' },
4042
{ path: '/vpe', component: 'vpe-details' },
4143
{ path: '/zwa2', component: 'zwa2-details' },
44+
{ path: '/zwa2/install', component: 'zwa2-install-original' },
45+
{ path: '/zwa2/install-portable', component: 'zwa2-install-portable' },
4246
{ path: '/zbt1', component: 'zbt1-details' },
4347
{ path: '/esphome', component: 'esphome-details' },
4448
{ path: '/media-player', component: 'media-player-details' },

src/pages/zwa2-install-original.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { LitElement, css, html } from 'lit';
2+
import { customElement } from 'lit/decorators.js';
3+
4+
import '../components/details.js';
5+
6+
@customElement('zwa2-install-original')
7+
export class Zwa2InstallOriginalPage extends LitElement {
8+
static styles = css`
9+
:host {
10+
display: block;
11+
padding: 20px;
12+
}
13+
`;
14+
15+
handleInstall() {
16+
window.open(
17+
'https://home-assistant.github.io/zwa2-toolbox/',
18+
'_blank',
19+
'noopener,noreferrer'
20+
);
21+
}
22+
23+
render() {
24+
const config = {
25+
hero: {
26+
title: 'Install original firmware',
27+
subtitle: 'Connect ZWA-2 directly to Home Assistant via USB',
28+
description:
29+
'This installation will flash the original firmware to your ZWA-2 so it can be used directly over USB with your Home Assistant hub.',
30+
},
31+
actions: [],
32+
heroCta: html`<button class="install" @click=${this.handleInstall}>
33+
Install
34+
</button>`,
35+
} as const;
36+
37+
return html`<details-page
38+
.config=${config}
39+
history-back
40+
back-href="/zwa2"
41+
></details-page>`;
42+
}
43+
}
44+
45+
declare global {
46+
interface HTMLElementTagNameMap {
47+
'zwa2-install-original': Zwa2InstallOriginalPage;
48+
}
49+
}

src/pages/zwa2-install-portable.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { LitElement, css, html } from 'lit';
2+
import { customElement } from 'lit/decorators.js';
3+
4+
import '../components/details.js';
5+
6+
@customElement('zwa2-install-portable')
7+
export class Zwa2InstallPortablePage extends LitElement {
8+
static styles = css`
9+
:host {
10+
display: block;
11+
padding: 20px;
12+
}
13+
`;
14+
15+
handleInstall() {
16+
window.open(
17+
'https://home-assistant.github.io/zwa2-toolbox/',
18+
'_blank',
19+
'noopener,noreferrer'
20+
);
21+
}
22+
23+
render() {
24+
const config = {
25+
hero: {
26+
title: 'Install portable Z-Wave firmware',
27+
subtitle: 'Place ZWA-2 optimally and connect via Wi‑Fi',
28+
description:
29+
'This installation will flash the portable Z-Wave firmware to your ZWA-2, enabling Wi‑Fi connectivity so you can place it in the best location for coverage.',
30+
},
31+
actions: [],
32+
heroCta: html`<button class="install" @click=${this.handleInstall}>
33+
Install
34+
</button>`,
35+
} as const;
36+
37+
return html`<details-page
38+
.config=${config}
39+
history-back
40+
back-href="/zwa2"
41+
></details-page>`;
42+
}
43+
}
44+
45+
declare global {
46+
interface HTMLElementTagNameMap {
47+
'zwa2-install-portable': Zwa2InstallPortablePage;
48+
}
49+
}

0 commit comments

Comments
 (0)