Skip to content

Commit

Permalink
Add a basic package page. (#1448)
Browse files Browse the repository at this point in the history
* Add a basic package page.
Fixes #1444

* Address review comments.
  • Loading branch information
rictic authored Mar 3, 2023
1 parent 391eeec commit 5bf9c8c
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 9 deletions.
12 changes: 3 additions & 9 deletions packages/site-client/src/pages/element/wco-element-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,6 @@ export class WCOElementPage extends WCOPage {
static styles = [
WCOPage.styles,
css`
.full-screen-error {
display: flex;
flex: 1;
align-items: center;
justify-items: center;
}
main {
display: grid;
max-width: var(--content-width);
Expand Down Expand Up @@ -90,7 +83,7 @@ export class WCOElementPage extends WCOPage {

renderContent() {
if (this.elementData === undefined) {
return html`<div class="full-screen-error">No element to display</div>`;
return this.fullScreenError('No element to display');
}
const {
packageName,
Expand Down Expand Up @@ -131,7 +124,8 @@ export class WCOElementPage extends WCOPage {
<div id="logo-container"><div id="logo"></div></div>
<div id="meta-container">
<span id="package-meta"
>${packageName}<select>
><a href="/catalog/package/${packageName}">${packageName}</a
><select>
<!-- TODO (justinfagnani): get actual version and dist tag data -->
<option>x.x.x</option>
</select></span
Expand Down
19 changes: 19 additions & 0 deletions packages/site-client/src/pages/package/boot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import 'lit/experimental-hydrate-support.js';
import {hydrate} from 'lit/experimental-hydrate.js';
import {renderPackagePage} from './shell.js';

const data = (
globalThis as unknown as {__ssrData: Parameters<typeof renderPackagePage>}
).__ssrData;

// We need to hydrate the whole page to remove any defer-hydration attributes.
// We could also remove the attribute manually, or not use deferhydration, but
// instead manually assign the data into the <wco-package-page> element, and
// time imports so that automatic element hydration happend after.
hydrate(renderPackagePage(...data), document.body);
12 changes: 12 additions & 0 deletions packages/site-client/src/pages/package/shell.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import './wco-package-page.js';
import type {PackageData} from './wco-package-page.js';
import {html} from 'lit';

export const renderPackagePage = (packageData: PackageData) =>
html`<wco-package-page .packageData=${packageData}></wco-package-page>`;
68 changes: 68 additions & 0 deletions packages/site-client/src/pages/package/wco-package-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {CustomElement} from '@webcomponents/catalog-api/lib/_schema.js';
import {html, css} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {unsafeHTML} from 'lit/directives/unsafe-html.js';

import {WCOPage} from '../../shared/wco-page.js';
import '../catalog/wco-element-card.js';

export interface PackageData {
name: string;
description: string;
version: string;
elements: CustomElement[];
}

@customElement('wco-package-page')
export class WCOPackagePage extends WCOPage {
static styles = [
WCOPage.styles,
css`
h1 {
display: inline-block;
}
.elements {
display: grid;
grid-template-columns: repeat(4, 200px);
grid-template-rows: auto;
grid-auto-columns: 200px;
grid-auto-rows: 200px;
gap: 8px;
}
`,
];

@property({attribute: false})
packageData?: PackageData;

renderContent() {
if (this.packageData === undefined) {
return this.fullScreenError('No package to display');
}

return html`
<div>
<h1>${this.packageData.name}</h1>
v${this.packageData.version}
</div>
<div>${unsafeHTML(this.packageData.description)}</div>
<div class="elements">
${this.packageData.elements.map((e) => {
return html`<wco-element-card .element=${e}></wco-element-card>`;
})}
</div>
`;
}
}

declare global {
interface HTMLElementTagNameMap {
'wco-package-page': WCOPackagePage;
}
}
11 changes: 11 additions & 0 deletions packages/site-client/src/shared/wco-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ export class WCOPage extends LitElement {
wco-footer {
width: 100%;
}
.full-screen-error {
display: flex;
flex: 1;
align-items: center;
justify-items: center;
}
`;

render() {
Expand All @@ -48,6 +55,10 @@ export class WCOPage extends LitElement {
protected renderContent() {
return html`<slot></slot>`;
}

protected fullScreenError(message: unknown) {
return html`<div class="full-screen-error">${message}</div>`;
}
}

declare global {
Expand Down
3 changes: 3 additions & 0 deletions packages/site-server/src/lib/catalog/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Router from '@koa/router';
import {handleCatalogRoute} from './routes/catalog/catalog-route.js';
import {handleCatalogSearchRoute} from './routes/catalog/search-route.js';
import {handleElementRoute} from './routes/element/element-route.js';
import {handlePackageRoute} from './routes/package/package-route.js';
// import cors from '@koa/cors';

export const catalogRouter = new Router();
Expand All @@ -19,3 +20,5 @@ catalogRouter.get('/', handleCatalogRoute);
catalogRouter.get('/search', handleCatalogSearchRoute);

catalogRouter.get('/element/:path+', handleElementRoute);

catalogRouter.get('/package/:name+', handlePackageRoute);
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

// This must be imported before lit
import {renderPage} from '@webcomponents/internal-site-templates/lib/base.js';

import {DefaultContext, DefaultState, ParameterizedContext} from 'koa';
import {Readable} from 'stream';
import {gql} from '@apollo/client/core/index.js';
import Router from '@koa/router';

import {renderPackagePage} from '@webcomponents/internal-site-client/lib/pages/package/shell.js';
import {client} from '../../graphql.js';
import {PackageData} from '@webcomponents/internal-site-client/lib/pages/package/wco-package-page.js';
import {marked} from 'marked';

export const handlePackageRoute = async (
context: ParameterizedContext<
DefaultState,
DefaultContext & Router.RouterParamContext<DefaultState, DefaultContext>,
unknown
>
) => {
const {params} = context;

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const packageName = params['name']!;

// TODO (justinfagnani): To make this type-safe, we need to write
// a query .graphql document and generate a TypedDocumentNode from it.
const result = await client.query({
query: gql`{
package(packageName: "${packageName}") {
... on ReadablePackageInfo {
name
description
version {
... on ReadablePackageVersion {
version
description
customElements {
tagName
package
declaration
customElementExport
declaration
}
}
}
}
}
}`,
});

if (result.errors !== undefined && result.errors.length > 0) {
throw new Error(result.errors.map((e) => e.message).join('\n'));
}
const {data} = result;
const packageVersion = data.package?.version;
if (packageVersion === undefined) {
// TODO: 404
throw new Error(`No such package version: ${packageName}`);
}

// Set location because wco-nav-bar reads pathname from it. URL isn't
// exactly a Location, but it's close enough for read-only uses
globalThis.location = new URL(context.URL.href) as unknown as Location;

const responseData: PackageData = {
name: packageName,
description: marked(data.package.description ?? ''),
version: packageVersion.version,
elements: packageVersion.customElements,
};

context.type = 'html';
context.status = 200;
context.body = Readable.from(
renderPage(
{
title: `${packageName}`,
initScript: '/js/package/boot.js',
content: renderPackagePage(responseData),
initialData: [responseData],
},
{
// We need to defer elements from hydrating so that we can
// manually provide data to the element in element-hydrate.js
deferHydration: true,
}
)
);
};

0 comments on commit 5bf9c8c

Please sign in to comment.