Use page instead of demo (#11118)

* Use page instead of demo

* Update netlify script

* Update ci.yml

* Rename demo -> page
This commit is contained in:
Paulus Schoutsen 2022-01-06 22:32:10 -08:00 committed by GitHub
parent 2c0d330f1f
commit 3133f9b01f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 242 additions and 144 deletions

View File

@ -30,7 +30,7 @@ jobs:
env: env:
CI: true CI: true
- name: Build resources - name: Build resources
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-demos run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
- name: Run eslint - name: Run eslint
run: yarn run lint:eslint run: yarn run lint:eslint
- name: Run tsc - name: Run tsc

View File

@ -19,14 +19,14 @@ require("./service-worker.js");
require("./entry-html.js"); require("./entry-html.js");
require("./rollup.js"); require("./rollup.js");
gulp.task("gather-gallery-demos", async function gatherDemos() { gulp.task("gather-gallery-pages", async function gatherPages() {
const demoDir = path.resolve(paths.gallery_dir, "src/demos"); const pageDir = path.resolve(paths.gallery_dir, "src/pages");
const files = glob.sync(path.resolve(demoDir, "**/*")); const files = glob.sync(path.resolve(pageDir, "**/*"));
const galleryBuild = path.resolve(paths.gallery_dir, "build"); const galleryBuild = path.resolve(paths.gallery_dir, "build");
fs.mkdirSync(galleryBuild, { recursive: true }); fs.mkdirSync(galleryBuild, { recursive: true });
let content = "export const DEMOS = {\n"; let content = "export const PAGES = {\n";
const processed = new Set(); const processed = new Set();
@ -34,17 +34,17 @@ gulp.task("gather-gallery-demos", async function gatherDemos() {
if (fs.lstatSync(file).isDirectory()) { if (fs.lstatSync(file).isDirectory()) {
continue; continue;
} }
demoId = file.substring(demoDir.length + 1, file.lastIndexOf(".")); const pageId = file.substring(pageDir.length + 1, file.lastIndexOf("."));
if (processed.has(demoId)) { if (processed.has(pageId)) {
continue; continue;
} }
processed.add(demoId); processed.add(pageId);
const [category, name] = demoId.split("/", 2); const [category, name] = pageId.split("/", 2);
const demoFile = path.resolve(demoDir, `${demoId}.ts`); const demoFile = path.resolve(pageDir, `${pageId}.ts`);
const descriptionFile = path.resolve(demoDir, `${demoId}.markdown`); const descriptionFile = path.resolve(pageDir, `${pageId}.markdown`);
const hasDemo = fs.existsSync(demoFile); const hasDemo = fs.existsSync(demoFile);
let hasDescription = fs.existsSync(descriptionFile); let hasDescription = fs.existsSync(descriptionFile);
let metadata = {}; let metadata = {};
@ -63,24 +63,25 @@ gulp.task("gather-gallery-demos", async function gatherDemos() {
if (descriptionContent === "") { if (descriptionContent === "") {
hasDescription = false; hasDescription = false;
} else { } else {
descriptionContent = marked(descriptionContent).replace(/`/g, "\\`");
fs.mkdirSync(path.resolve(galleryBuild, category), { recursive: true }); fs.mkdirSync(path.resolve(galleryBuild, category), { recursive: true });
fs.writeFileSync( fs.writeFileSync(
path.resolve(galleryBuild, `${demoId}-description.ts`), path.resolve(galleryBuild, `${pageId}-description.ts`),
` `
import {html} from "lit"; import {html} from "lit";
export default html\`${marked(descriptionContent)}\` export default html\`${descriptionContent}\`
` `
); );
} }
} }
content += ` "${demoId}": { content += ` "${pageId}": {
metadata: ${JSON.stringify(metadata)}, metadata: ${JSON.stringify(metadata)},
${ ${
hasDescription hasDescription
? `description: () => import("./${demoId}-description").then(m => m.default),` ? `description: () => import("./${pageId}-description").then(m => m.default),`
: "" : ""
} }
${hasDemo ? `load: () => import("../src/demos/${demoId}")` : ""} ${hasDemo ? `demo: () => import("../src/pages/${pageId}")` : ""}
},\n`; },\n`;
} }
@ -93,51 +94,54 @@ gulp.task("gather-gallery-demos", async function gatherDemos() {
delete require.cache[sidebarPath]; delete require.cache[sidebarPath];
const sidebar = require(sidebarPath); const sidebar = require(sidebarPath);
const demosToProcess = {}; const pagesToProcess = {};
for (const key of processed) { for (const key of processed) {
const [category, demo] = key.split("/", 2); const [category, page] = key.split("/", 2);
if (!(category in demosToProcess)) { if (!(category in pagesToProcess)) {
demosToProcess[category] = new Set(); pagesToProcess[category] = new Set();
} }
demosToProcess[category].add(demo); pagesToProcess[category].add(page);
} }
for (const group of Object.values(sidebar)) { for (const group of Object.values(sidebar)) {
const toProcess = demosToProcess[group.category]; const toProcess = pagesToProcess[group.category];
delete demosToProcess[group.category]; delete pagesToProcess[group.category];
if (!toProcess) { if (!toProcess) {
console.error("Unknown category", group.category); console.error("Unknown category", group.category);
if (!group.pages) {
group.pages = [];
}
continue; continue;
} }
// Any pre-defined groups will not be sorted. // Any pre-defined groups will not be sorted.
if (group.demos) { if (group.pages) {
for (const demo of group.demos) { for (const page of group.pages) {
if (!toProcess.delete(demo)) { if (!toProcess.delete(page)) {
console.error("Found unreferenced demo", demo); console.error("Found unreferenced demo", page);
} }
} }
} else { } else {
group.demos = []; group.pages = [];
} }
for (const demo of Array.from(toProcess).sort()) { for (const page of Array.from(toProcess).sort()) {
group.demos.push(demo); group.pages.push(page);
} }
} }
for (const [category, demos] of Object.entries(demosToProcess)) { for (const [category, pages] of Object.entries(pagesToProcess)) {
sidebar.push({ sidebar.push({
category, category,
header: category, header: category,
demos: Array.from(demos), pages: Array.from(pages).sort(),
}); });
} }
content += `export const SIDEBAR = ${JSON.stringify(sidebar, null, 2)};\n`; content += `export const SIDEBAR = ${JSON.stringify(sidebar, null, 2)};\n`;
fs.writeFileSync( fs.writeFileSync(
path.resolve(galleryBuild, "import-demos.ts"), path.resolve(galleryBuild, "import-pages.ts"),
content, content,
"utf-8" "utf-8"
); );
@ -155,7 +159,7 @@ gulp.task(
"gen-icons-json", "gen-icons-json",
"build-translations", "build-translations",
"build-locale-data", "build-locale-data",
"gather-gallery-demos" "gather-gallery-pages"
), ),
"copy-static-gallery", "copy-static-gallery",
"gen-index-gallery-dev", "gen-index-gallery-dev",
@ -166,10 +170,10 @@ gulp.task(
async function watchMarkdownFiles() { async function watchMarkdownFiles() {
gulp.watch( gulp.watch(
[ [
path.resolve(paths.gallery_dir, "src/demos/**/*.markdown"), path.resolve(paths.gallery_dir, "src/pages/**/*.markdown"),
path.resolve(paths.gallery_dir, "sidebar.js"), path.resolve(paths.gallery_dir, "sidebar.js"),
], ],
gulp.series("gather-gallery-demos") gulp.series("gather-gallery-pages")
); );
} }
) )
@ -188,7 +192,7 @@ gulp.task(
"gen-icons-json", "gen-icons-json",
"build-translations", "build-translations",
"build-locale-data", "build-locale-data",
"gather-gallery-demos" "gather-gallery-pages"
), ),
"copy-static-gallery", "copy-static-gallery",
env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery", env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery",

View File

@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
TARGET_LABEL="Needs gallery preview" TARGET_LABEL="Needs design preview"
if [[ "$NETLIFY" != "true" ]]; then if [[ "$NETLIFY" != "true" ]]; then
echo "This script can only be run on Netlify" echo "This script can only be run on Netlify"
@ -13,7 +13,7 @@ function createStatus() {
target_url="$3" target_url="$3"
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $GITHUB_TOKEN" \ curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/home-assistant/frontend/statuses/$COMMIT_REF" \ "https://api.github.com/repos/home-assistant/frontend/statuses/$COMMIT_REF" \
-d '{"state": "'"${state}"'", "context": "Netlify/Gallery Preview Build", "description": "'"$description"'", "target_url": "'"$target_url"'"}' -d '{"state": "'"${state}"'", "context": "Netlify/Design Preview Build", "description": "'"$description"'", "target_url": "'"$target_url"'"}'
} }
@ -22,7 +22,7 @@ if [[ "${PULL_REQUEST}" == "false" ]]; then
else else
if [[ "$(curl -sSLf -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $GITHUB_TOKEN" \ if [[ "$(curl -sSLf -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/home-assistant/frontend/pulls/${REVIEW_ID}" | jq '.labels[].name' -r)" =~ "$TARGET_LABEL" ]]; then "https://api.github.com/repos/home-assistant/frontend/pulls/${REVIEW_ID}" | jq '.labels[].name' -r)" =~ "$TARGET_LABEL" ]]; then
createStatus "pending" "Building gallery preview" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID" createStatus "pending" "Building design preview" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
gulp build-gallery gulp build-gallery
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
createStatus "success" "Build complete" "$DEPLOY_URL" createStatus "success" "Build complete" "$DEPLOY_URL"

View File

@ -1,16 +1,17 @@
module.exports = [ module.exports = [
{ {
category: "introduction", // This section has no header and so all page links are shown directly in the sidebar
demos: ["introduction"], category: "concepts",
pages: ["home"],
}, },
{ {
category: "lovelace", category: "lovelace",
// Each section has a header // Label for in the sidebar
header: "Lovelace", header: "Lovelace",
// Specify demos to make sure they are put on top. // Specify order of pages. Any pages in the category folder but not listed here will
demos: [], // automatically be added after the pages listed here.
// Add a demoStart to automatically gather demos based on their name pages: ["introduction"],
}, },
{ {
category: "automation", category: "automation",
@ -19,22 +20,17 @@ module.exports = [
{ {
category: "components", category: "components",
header: "Components", header: "Components",
demos: [
"ha-alert",
"ha-bar",
"ha-chips",
"ha-faded",
"ha-form",
"ha-label-badge",
"ha-selector",
],
}, },
{ {
category: "more-info", category: "more-info",
header: "More Info", header: "More Info dialogs",
}, },
{ {
category: "rest", category: "misc",
header: "Rest", header: "Miscelaneous",
},
{
category: "design.home-assistant.io",
header: "Design Documentation",
}, },
]; ];

View File

@ -1,44 +0,0 @@
import { html, css, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { until } from "lit/directives/until";
import { haStyle } from "../../../src/resources/styles";
import { DEMOS } from "../../build/import-demos";
@customElement("demo-description")
class DemoDescription extends LitElement {
@property() public demo!: string;
render() {
if (!DEMOS[this.demo].description) {
return "";
}
return html`
${until(
DEMOS[this.demo].description().then(
(content) => html`
<ha-card>
<div class="card-content">${content}</div>
</ha-card>
`
),
""
)}
`;
}
static styles = [
haStyle,
css`
ha-card {
max-width: 600px;
margin: 16px auto;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"demo-description": DemoDescription;
}
}

View File

@ -0,0 +1,50 @@
import { html, css } from "lit";
import { customElement, property } from "lit/decorators";
import { until } from "lit/directives/until";
import { HaMarkdown } from "../../../src/components/ha-markdown";
import { PAGES } from "../../build/import-pages";
@customElement("page-description")
class PageDescription extends HaMarkdown {
@property() public page!: string;
render() {
if (!PAGES[this.page].description) {
return html``;
}
return html`
${until(
PAGES[this.page].description().then(
(content) => html`
<ha-card>
<div class="card-content">${content}</div>
</ha-card>
`
),
""
)}
`;
}
static styles = [
HaMarkdown.styles,
css`
ha-card {
max-width: 600px;
margin: 16px auto;
}
.card-content > *:first-child {
margin-top: 0;
}
.card-content > *:last-child {
margin-bottom: 0;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"page-description": PageDescription;
}
}

View File

@ -7,12 +7,12 @@ import "../../src/components/ha-card";
import "../../src/components/ha-icon-button"; import "../../src/components/ha-icon-button";
import "../../src/managers/notification-manager"; import "../../src/managers/notification-manager";
import { haStyle } from "../../src/resources/styles"; import { haStyle } from "../../src/resources/styles";
import { DEMOS, SIDEBAR } from "../build/import-demos"; import { PAGES, SIDEBAR } from "../build/import-pages";
import { dynamicElement } from "../../src/common/dom/dynamic-element-directive"; import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
import "./components/demo-description"; import "./components/page-description";
const GITHUB_DEMO_URL = const GITHUB_DEMO_URL =
"https://github.com/home-assistant/frontend/blob/dev/gallery/src/demos/"; "https://github.com/home-assistant/frontend/blob/dev/gallery/src/pages/";
const FAKE_HASS = { const FAKE_HASS = {
// Just enough for computeRTL for notification-manager // Just enough for computeRTL for notification-manager
@ -24,9 +24,9 @@ const FAKE_HASS = {
@customElement("ha-gallery") @customElement("ha-gallery")
class HaGallery extends LitElement { class HaGallery extends LitElement {
@property() private _demo = @property() private _page =
document.location.hash.substring(1) || document.location.hash.substring(1) ||
`${SIDEBAR[0].category}/${SIDEBAR[0].demos![0]}`; `${SIDEBAR[0].category}/${SIDEBAR[0].pages![0]}`;
@query("notification-manager") @query("notification-manager")
private _notifications!: HTMLElementTagNameMap["notification-manager"]; private _notifications!: HTMLElementTagNameMap["notification-manager"];
@ -42,12 +42,12 @@ class HaGallery extends LitElement {
for (const group of SIDEBAR) { for (const group of SIDEBAR) {
const links: unknown[] = []; const links: unknown[] = [];
for (const demo of group.demos!) { for (const page of group.pages!) {
const key = `${group.category}/${demo}`; const key = `${group.category}/${page}`;
const active = this._demo === key; const active = this._page === key;
const title = DEMOS[key].metadata.title || demo; const title = PAGES[key].metadata.title || page;
links.push(html` links.push(html`
<a ?active=${active} href=${`#${group.category}/${demo}`}>${title}</a> <a ?active=${active} href=${`#${group.category}/${page}`}>${title}</a>
`); `);
} }
@ -81,28 +81,28 @@ class HaGallery extends LitElement {
></ha-icon-button> ></ha-icon-button>
<div slot="title"> <div slot="title">
${DEMOS[this._demo].metadata.title || this._demo.split("/")[1]} ${PAGES[this._page].metadata.title || this._page.split("/")[1]}
</div> </div>
</mwc-top-app-bar-fixed> </mwc-top-app-bar-fixed>
<div> <div>
<demo-description .demo=${this._demo}></demo-description> <page-description .page=${this._page}></page-description>
${dynamicElement(`demo-${this._demo.replace("/", "-")}`)} ${dynamicElement(`demo-${this._page.replace("/", "-")}`)}
<div class="demo-footer"> <div class="page-footer">
${DEMOS[this._demo].description || ${PAGES[this._page].description ||
Object.keys(DEMOS[this._demo].metadata).length > 0 Object.keys(PAGES[this._page].metadata).length > 0
? html` ? html`
<a <a
href=${`${GITHUB_DEMO_URL}${this._demo}.markdown`} href=${`${GITHUB_DEMO_URL}${this._page}.markdown`}
target="_blank" target="_blank"
> >
Edit text Edit text
</a> </a>
` `
: ""} : ""}
${DEMOS[this._demo].load ${PAGES[this._page].load
? html` ? html`
<a <a
href=${`${GITHUB_DEMO_URL}${this._demo}.ts`} href=${`${GITHUB_DEMO_URL}${this._page}.ts`}
target="_blank" target="_blank"
> >
Edit demo Edit demo
@ -137,10 +137,10 @@ class HaGallery extends LitElement {
} }
}); });
document.location.hash = this._demo; document.location.hash = this._page;
window.addEventListener("hashchange", () => { window.addEventListener("hashchange", () => {
this._demo = document.location.hash.substring(1); this._page = document.location.hash.substring(1);
if (this._narrow) { if (this._narrow) {
this._drawer.open = false; this._drawer.open = false;
} }
@ -149,15 +149,20 @@ class HaGallery extends LitElement {
updated(changedProps: PropertyValues) { updated(changedProps: PropertyValues) {
super.updated(changedProps); super.updated(changedProps);
if (changedProps.has("_demo") && DEMOS[this._demo].load) { if (!changedProps.has("_page")) {
DEMOS[this._demo].load(); return;
const menuItem = this.shadowRoot!.querySelector( }
`a[href="#${this._demo}"]`
)!; if (PAGES[this._page].demo) {
// Make sure section is expanded PAGES[this._page].demo();
if (menuItem.parentElement instanceof HTMLDetailsElement) { }
menuItem.parentElement.open = true;
} const menuItem = this.shadowRoot!.querySelector(
`a[href="#${this._page}"]`
)!;
// Make sure section is expanded
if (menuItem.parentElement instanceof HTMLDetailsElement) {
menuItem.parentElement.open = true;
} }
} }
@ -211,12 +216,12 @@ class HaGallery extends LitElement {
opacity: 0.12; opacity: 0.12;
} }
.demo-footer { .page-footer {
text-align: center; text-align: center;
margin: 16px 0; margin: 16px 0;
} }
.demo-footer a { .page-footer a {
display: inline-block; display: inline-block;
margin: 0 8px; margin: 0 8px;
} }

View File

@ -7,7 +7,7 @@
content="width=device-width, initial-scale=1, shrink-to-fit=no" content="width=device-width, initial-scale=1, shrink-to-fit=no"
/> />
<meta name="theme-color" content="#2157BC" /> <meta name="theme-color" content="#2157BC" />
<title>HAGallery</title> <title>Home Assistant Design</title>
<script type="module" src="<%= latestGalleryJS %>"></script> <script type="module" src="<%= latestGalleryJS %>"></script>
<style> <style>

View File

@ -0,0 +1,7 @@
---
title: Home
---
# Welcome to Home Assistant Design
This portal aims to aid designers and developers on improving the Home Assistant interface.

View File

@ -0,0 +1,80 @@
---
title: Editing design.home-assistant.io
---
# How to edit design.home-assistant.io
All pages are stored in [the pages folder][pages-folder] on GitHub. Pages are grouped in a folder per sidebar section. Each page can contain a `<page name>.markdown` description file, a `<page name>.ts` demo file or both. If both are defined the description is rendered first. The description can contain metadata to specify the title of the page.
## Development
You can develop design.home-assistant.io locally by checking out [the Home Assistant frontend repository](https://github.com/home-assistant/frontend). The command to run the gallery is `gallery/script/develop_gallery`. It will automatically open a browser window and load the development version of the website.
## Creating a page
Navigate to the [the pages folder][pages-folder] on GitHub. If the folder for your category does not exist yet, create it. Create a new Markdown file inside this folder for your description, ie `usability.markdown`. This filename will be used in the URL. Add the following content:
```markdown
---
title: My new page
---
Hello and welcome to my new page!
```
Once saved, the page will be automatically added to the bottom of the sidebar. The title specified in the header will be shown as the page title and used in the sidebar.
## Linking the page in the sidebar
By default the sidebar will gather all pages and group them by category. You can override the order of the categories, define a name for categories and change the order of the pages in [`sidebar.js`](https://github.com/home-assistant/frontend/blob/dev/gallery/sidebar.js).
Any category not listed in `sidebar.js` will be placed at the end of the sidebar.
Any page not listed in `sidebar.js` will be placed at the end of its category.
## Adding a demo to a page
Create a file next to the description file with the same name as the description file, but with the `.ts` extension: `usability.ts`. For this example, we assume that the category folder that contains `usability.markdown` and `usability.ts` is called `user-experience`. Add the following content to `usability.ts`:
```ts
import { html, css, LitElement } from "lit";
import { customElement } from "lit/decorators";
import "../../../../src/components/ha-card";
@customElement("demo-user-experience-usability")
export class DemoUserExperienceUsability extends LitElement {
protected render() {
return html`
<ha-card>
<div class="card-content">
Hello world!
</div>
</ha-card>
`;
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-user-experience-usability": DemoUserExperienceUsability;
}
}
```
Note that the demo deosn't need to render anything itself. It can also be used to declare web components to be used by the page description. Because page descriptions are using markdown, they can embed any HTML.
## Publishing changes
The website is automatically published whenever the source files in the `dev` branch change. So to get your changes published, open a pull request with your changes.
[pages-folder]: https://github.com/home-assistant/frontend/tree/dev/gallery/src/pages

View File

@ -3,7 +3,7 @@ import { customElement, property } from "lit/decorators";
import "./ha-markdown-element"; import "./ha-markdown-element";
@customElement("ha-markdown") @customElement("ha-markdown")
class HaMarkdown extends LitElement { export class HaMarkdown extends LitElement {
@property() public content?; @property() public content?;
@property({ type: Boolean }) public allowSvg = false; @property({ type: Boolean }) public allowSvg = false;
@ -38,35 +38,35 @@ class HaMarkdown extends LitElement {
ha-markdown-element > *:last-child { ha-markdown-element > *:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
ha-markdown-element a { a {
color: var(--primary-color); color: var(--primary-color);
} }
ha-markdown-element img { img {
max-width: 100%; max-width: 100%;
} }
ha-markdown-element code, code,
pre { pre {
background-color: var(--markdown-code-background-color, none); background-color: var(--markdown-code-background-color, none);
border-radius: 3px; border-radius: 3px;
} }
ha-markdown-element svg { svg {
background-color: var(--markdown-svg-background-color, none); background-color: var(--markdown-svg-background-color, none);
color: var(--markdown-svg-color, none); color: var(--markdown-svg-color, none);
} }
ha-markdown-element code { code {
font-size: 85%; font-size: 85%;
padding: 0.2em 0.4em; padding: 0.2em 0.4em;
} }
ha-markdown-element pre code { pre code {
padding: 0; padding: 0;
} }
ha-markdown-element pre { pre {
padding: 16px; padding: 16px;
overflow: auto; overflow: auto;
line-height: 1.45; line-height: 1.45;
font-family: var(--code-font-family, monospace); font-family: var(--code-font-family, monospace);
} }
ha-markdown-element h2 { h2 {
font-size: 1.5em; font-size: 1.5em;
font-weight: bold; font-weight: bold;
} }