Skip to content

Commit

Permalink
Add a basic package page.
Browse files Browse the repository at this point in the history
Fixes #1444
  • Loading branch information
rictic committed Mar 3, 2023
1 parent e31ed17 commit b18ccba
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 4 deletions.
4 changes: 2 additions & 2 deletions packages/catalog-api/src/lib/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ export const PackageStatus = {
...ReadablePackageStatus,
...UnreadablePackageStatus,
} as const;
export type PackageStatus = typeof PackageStatus[keyof typeof PackageStatus];
export type PackageStatus = (typeof PackageStatus)[keyof typeof PackageStatus];

export const VersionStatus = {
...ReadableVersionStatus,
...UnreadableVersionStatus,
} as const;
export type VersionStatus = typeof VersionStatus[keyof typeof VersionStatus];
export type VersionStatus = (typeof VersionStatus)[keyof typeof VersionStatus];

export const isReadablePackage = (
p: PackageInfo | undefined
Expand Down
2 changes: 1 addition & 1 deletion packages/custom-elements-manifest-tools/src/lib/npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface PackageFiles {
*
* The response must be compatible with the npm registry Package Metadata
* API: https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md
*
*
* If a package is not found, the returned Promise must reject with an
* HttpError with status 404.
*/
Expand Down
3 changes: 2 additions & 1 deletion packages/site-client/src/pages/element/wco-element-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,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 2022 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-element-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 2022 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>`;
74 changes: 74 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,74 @@
/**
* @license
* Copyright 2022 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`
.full-screen-error {
display: flex;
flex: 1;
align-items: center;
justify-items: center;
}
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 html`<div class="full-screen-error">No package to display</div>`;
}

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;
}
}
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,95 @@
/**
* @license
* Copyright 2022 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) {
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 b18ccba

Please sign in to comment.