diff --git a/build-scripts/gulp/gallery.js b/build-scripts/gulp/gallery.js index 16848ec723..6e45f045cb 100644 --- a/build-scripts/gulp/gallery.js +++ b/build-scripts/gulp/gallery.js @@ -17,25 +17,33 @@ require("./entry-html.js"); require("./rollup.js"); gulp.task("gather-gallery-demos", async function gatherDemos() { - const files = await fs.promises.readdir( - path.resolve(paths.gallery_dir, "src/demos") - ); + const demoDir = path.resolve(paths.gallery_dir, "src/demos"); + const files = await fs.promises.readdir(demoDir); const galleryBuild = path.resolve(paths.gallery_dir, "build"); fs.mkdirSync(galleryBuild, { recursive: true }); let content = "export const DEMOS = {\n"; + const processed = new Set(); + for (const file of files) { - if (!file.endsWith(".ts")) { + let demoId = path.basename( + file, + file.endsWith(".ts") ? ".ts" : ".markdown" + ); + + // Can be processed if we saw demo or description before. + if (processed.has(demoId)) { continue; } - const demoId = path.basename(file, ".ts"); - const descriptionFile = path.resolve( - paths.gallery_dir, - "src/demos", - `${demoId}.markdown` - ); + + processed.add(demoId); + + const demoFile = path.resolve(demoDir, `${demoId}.ts`); + + const descriptionFile = path.resolve(demoDir, `${demoId}.markdown`); + const hasDemo = fs.existsSync(demoFile); const hasDescription = fs.existsSync(descriptionFile); if (hasDescription) { const descriptionContent = fs.readFileSync(descriptionFile, "utf-8"); @@ -55,7 +63,8 @@ gulp.task("gather-gallery-demos", async function gatherDemos() { ? `description: () => import("${descriptionPath}").then(m => m.default),` : "" } - load: () => import("${demoPath}") + ${hasDemo ? `load: () => import("${demoPath}")` : ""} + },\n`; } @@ -84,7 +93,17 @@ gulp.task( ), "copy-static-gallery", "gen-index-gallery-dev", - env.useRollup() ? "rollup-dev-server-gallery" : "webpack-dev-server-gallery" + gulp.parallel( + env.useRollup() + ? "rollup-dev-server-gallery" + : "webpack-dev-server-gallery", + async function watchMarkdownFiles() { + gulp.watch( + path.resolve(paths.gallery_dir, "src/demos/*.markdown"), + gulp.series("gather-gallery-demos") + ); + } + ) ) ); diff --git a/gallery/src/demos/demo-introduction.markdown b/gallery/src/demos/demo-introduction.markdown new file mode 100644 index 0000000000..d80033b05e --- /dev/null +++ b/gallery/src/demos/demo-introduction.markdown @@ -0,0 +1,8 @@ +Lovelace has many different cards. Each card allows the user to tell +a different story about what is going on in their house. These cards +are very customizable, as no household is the same. + +This gallery helps our developers and designers to see all the +different states that each card can be in. + +Check [the Lovelace documentation](https://www.home-assistant.io/lovelace) for instructions on how to get started with Lovelace. diff --git a/gallery/src/demos/demo-introduction.ts b/gallery/src/demos/demo-introduction.ts deleted file mode 100644 index 29e306bbff..0000000000 --- a/gallery/src/demos/demo-introduction.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { css, html, LitElement, TemplateResult } from "lit"; -import { customElement } from "lit/decorators"; -import "../../../src/components/ha-card"; -import "../../../src/components/ha-markdown"; - -@customElement("demo-introduction") -export class DemoIntroduction extends LitElement { - protected render(): TemplateResult { - return html` - -
- -
-
- `; - } - - static get styles() { - return css` - ha-card { - max-width: 600px; - margin: 24px auto; - } - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - "demo-introduction": DemoIntroduction; - } -} diff --git a/gallery/src/ha-gallery.ts b/gallery/src/ha-gallery.ts index 4f044096cb..2aa583ca12 100644 --- a/gallery/src/ha-gallery.ts +++ b/gallery/src/ha-gallery.ts @@ -4,55 +4,13 @@ import "@material/mwc-top-app-bar-fixed"; import { html, css, LitElement, PropertyValues } from "lit"; import { customElement, property, query } from "lit/decorators"; import { until } from "lit/directives/until"; +import "../../src/components/ha-card"; import "../../src/components/ha-icon-button"; import "../../src/managers/notification-manager"; import { haStyle } from "../../src/resources/styles"; -// eslint-disable-next-line import/extensions import { DEMOS } from "../build/import-demos"; import { dynamicElement } from "../../src/common/dom/dynamic-element-directive"; - -const DEMOS_GROUPED: { - header?: string; - demos?: string[]; - demoStart?: string; -}[] = [ - { - demos: ["introduction"], - }, - { - header: "Lovelace", - demoStart: "hui-", - }, - { - header: "Automation", - demoStart: "automation-", - }, - { - header: "Rest", - demoStart: "", - }, -]; - -const demosToProcess = new Set(Object.keys(DEMOS)); - -for (const group of Object.values(DEMOS_GROUPED)) { - if (group.demos) { - for (const demo of group.demos) { - demosToProcess.delete(demo); - } - } - if (!group.demos) { - group.demos = []; - } - if (group.demoStart !== undefined) { - for (const demo of demosToProcess) { - if (demo.startsWith(group.demoStart)) { - group.demos.push(demo); - demosToProcess.delete(demo); - } - } - } -} +import { SIDEBAR } from "./sidebar"; const FAKE_HASS = { // Just enough for computeRTL for notification-manager @@ -65,7 +23,7 @@ const FAKE_HASS = { @customElement("ha-gallery") class HaGallery extends LitElement { @property() private _demo = - document.location.hash.substring(1) || DEMOS_GROUPED[0].demos![0]; + document.location.hash.substring(1) || SIDEBAR[0].demos![0]; @query("notification-manager") private _notifications!: HTMLElementTagNameMap["notification-manager"]; @@ -73,23 +31,51 @@ class HaGallery extends LitElement { @query("mwc-drawer") private _drawer!: HTMLElementTagNameMap["mwc-drawer"]; + private _narrow = window.matchMedia("(max-width: 600px)").matches; + render() { + const sidebar: unknown[] = []; + + for (const group of SIDEBAR) { + let sectionOpen = false; + const links: unknown[] = []; + + for (const demo of group.demos!) { + const active = this._demo === demo; + if (active) { + sectionOpen = true; + } + + links.push(html` + ${group.demoStart === undefined + ? demo + : demo.substring(group.demoStart.length)} + `); + } + + sidebar.push( + group.header + ? html` +
+ ${group.header} + ${links} +
+ ` + : links + ); + } + return html` - + Home Assistant Design - +
html` - +
${content}
` @@ -126,21 +112,12 @@ class HaGallery extends LitElement { `; } - private _renderDemo(demo: string, demoStart?: string) { - return html` - ${demoStart === undefined ? demo : demo.substring(demoStart.length)} - `; - } - firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); this.addEventListener("show-notification", (ev) => this._notifications.showDialog({ message: ev.detail.message }) ); - this.addEventListener("alert-dismissed-clicked", () => this._notifications.showDialog({ message: "Alert dismissed clicked" }) ); @@ -156,12 +133,15 @@ class HaGallery extends LitElement { window.addEventListener("hashchange", () => { this._demo = document.location.hash.substring(1); + if (this._narrow) { + this._drawer.open = false; + } }); } updated(changedProps: PropertyValues) { super.updated(changedProps); - if (changedProps.has("_demo") && this._demo) { + if (changedProps.has("_demo") && DEMOS[this._demo].load) { DEMOS[this._demo].load(); } } @@ -179,16 +159,18 @@ class HaGallery extends LitElement { -moz-user-select: initial; } - .section { - font-weight: bold; - } - .sidebar { padding: 4px; } - .sidebar p { - margin: 1em 12px; + .sidebar details { + margin-top: 1em; + } + + .sidebar summary { + cursor: pointer; + font-weight: bold; + margin-bottom: 8px; } .sidebar a { diff --git a/gallery/src/sidebar.ts b/gallery/src/sidebar.ts new file mode 100644 index 0000000000..71222f59a0 --- /dev/null +++ b/gallery/src/sidebar.ts @@ -0,0 +1,69 @@ +import { DEMOS } from "../build/import-demos"; + +export const SIDEBAR: SidebarSection[] = [ + { + demos: ["introduction"], + }, + + { + // Each section has a header + header: "Lovelace", + // Specify demos to make sure they are put on top. + demos: [], + // Add a demoStart to automatically gather demos based on their name + demoStart: "hui-", + }, + { + header: "Automation", + demoStart: "automation-", + }, + { + header: "Components", + demos: [ + "ha-alert", + "ha-bar", + "ha-chips", + "ha-faded", + "ha-form", + "ha-label-badge", + "ha-selector", + ], + }, + { + header: "More Info", + demoStart: "more-info-", + }, + { + header: "Rest", + demoStart: "", // empty string matches all. + }, +]; + +interface SidebarSection { + header?: string; + demos?: string[]; + demoStart?: string; +} + +const demosToProcess = new Set(Object.keys(DEMOS)); + +for (const group of Object.values(SIDEBAR)) { + // Any pre-defined groups will not be sorted. + if (group.demos) { + for (const demo of group.demos) { + demosToProcess.delete(demo); + } + } else { + group.demos = []; + } +} +for (const group of Object.values(SIDEBAR)) { + if (group.demoStart !== undefined) { + for (const demo of demosToProcess) { + if (demo.startsWith(group.demoStart)) { + group.demos!.push(demo); + demosToProcess.delete(demo); + } + } + } +}