Skip to content

Instantly share code, notes, and snippets.

@markmals
Last active August 20, 2023 08:18
Show Gist options
  • Save markmals/4d8d665ab92bd669285731b63a26259e to your computer and use it in GitHub Desktop.
Save markmals/4d8d665ab92bd669285731b63a26259e to your computer and use it in GitHub Desktop.
import { Observable, ObservationIgnored, withObservationTracking } from '@webstd/observable';
import type { Identifiable } from '@webstd/types';
import { CustomElement, ReactiveElement, html, Environment, State } from '@webstd/custom-elements';
import { stream } from '@webstd/request';
import { Author } from './author.js'
@Observable
class Book implements Identifiable {
@ObservationIgnored id = crypto.randomUUID(); // A unique identifier that never changes.
title = 'Sample Book Title';
author = new Author();
isAvailable = true;
}
@Observable
class Library {
books = [new Book(), new Book(), new Book()];
get availableBooksCount() {
return this.books.filter(book => book.isAvailable).length;
}
}
@CustomElement('app-root')
export class RootElement extends ReactiveElement {
#library = new Library();
override onAppear() {
super.onAppear()
withObservationTracking(() => {
console.log(`Books available: ${this.#library.availableBooksCount}`)
})
}
override onDisappear() {
super.onDisappear()
console.log('Destroyed root element')
}
get root() {
return html`
<std-environment .value=${this.#library}>
<app-library></app-library>
</std-environment>
`;
}
}
@CustomElement('app-library')
export class LibraryElement extends ReactiveElement {
@Environment(Library) #library!: Library;
@State titleElement!: HTMLHeadingElement;
onClick = () => document.scrollTo(this.titleElement.scrollHeight);
get root() {
return html`
<nav class:list=${['hello goodbye', { hello: true, world: true }, new Set(['hello', 'friend'])]}>
<h1 #ref=${el => (this.titleElement = el)} on:click=${this.onClick}>
Books available: ${this.#library.availableBooksCount}
</h1>
</nav>
{#for memo ${this.#library.books}}
${book => html`<app-book .book=${book}></app-book>`}
{/for}
`;
}
}
@CustomElement('app-book')
export class BookElement extends ReactiveElement {
book: Book;
@State isEditorPresented = false;
constructor(book: Book) {
this.book = book;
withObservationTracking(() => {
console.log(
`Book ${this.book.title} being edited: `,
this.isEditorPresented
)
})
}
get root() {
return html`
{#if ${this.isEditorPresented}}
{#portal to ${document.getElementById('modal')}}
<ui-modal-sheet>
<app-book-edit .book=${this.book}> </app-book-edit>
</ui-modal-sheet>
<div class="dark-bg-overlay"></div>
{/portal}
{/if}
<div class="flex flex-row gap-2">
<span class="mb-auto">
${this.book.title}
</span>
<button on:click=${() => (this.isEditorPresented = true)}>
Edit
</button>
</div>
`;
}
}
@CustomElement('app-message')
export class MessageElement extends ReactiveElement {
request = new Request('https://example.com', {
body: new ReadableStream(),
method: 'POST',
})
@State #message = 'Loading...'
async task() {
try {
let receivedLines: string[] = []
for await (line of stream(this.request)) {
receivedLines.push(line)
this.#message = `Received ${receivedLines.length} lines`
}
} catch {
this.#message = 'Failed to load'
}
}
get root() {
return html`<span>${this.#message}</span>`;
}
}
import type { Component } from '@webstd/static';
import { html } from '@webstd/static';
import type { LoaderArgs, RouteArgs } from '@webstd/router';
import { defer } from '@webstd/router';
import { prisma } from './db.js'
const Layout: Component<{ title: string }> = ({ children, title }) => html`
<html>
<head>
<title>${title}</title>
</head>
<body>
<div id="modal"></div>
${children}
</body>
</html>
`;
const Footer: Component = () => html`<footer>Copyright</footer>`;
export async function loader({ query }: LoaderArgs) {
return defer({
results: prisma
.posts
.findFirst({
where: query.get('q')
})
})
}
type DataFunctions = { loader: typeof loader }
export default function Route(
{ params, query, request, loaderData }: RouteArgs<DataFunctions>
) {
return html`
<${Layout} title="Home">
<h1>Home</h1>
<nav>
<${BreadCrumbs} path=${request.url.pathname} />
</nav>
<ul>
${['foo', 'bar', 'baz'].map(i => html`<li>${i}</li>`)}
</ul>
<app-library client:load></app-library>
{#await ${loaderData.results}}
<div>Loading...</div>
{:then}
${results => html`
<ul>
${results.map(r => html`
<li>
<a href=${r.link}>
${r.title}
</a>
</li>
`)}
</ul>
`}
{:catch}
${error => html`
<span id="error">
Uh-oh! Error: ${JSON.stringify(error)}
</span>
`}
{/await}
<${Footer} />
<//>
`;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment