Changes
6 changed files (+111/-83)
-
-
-
@@ -27,6 +27,7 @@ "@fontsource-variable/inter": "^5.1.0","@fontsource/ibm-plex-sans-jp": "^5.1.0", "@fontsource/monaspace-neon": "^5.1.0", "@yamori/proto": "workspace:*", "lit-html": "^3.2.1", "lucide": "^0.468.0" }, "devDependencies": {
-
-
-
@@ -146,5 +146,26 @@ </p><Preview> <yamori-workspace-list list-response={sampleAttr}></yamori-workspace-list> </Preview> <h2>Pending</h2> <p> <code>pending</code> 属性を指定することで読込中表示とすることができます。 </p> <Preview title="Pending"> <yamori-workspace-list pending data-id="demo"> <p slot="empty-fallback">FALLBACK</p> </yamori-workspace-list> </Preview> <Preview title="Pending / No Workspaces"> <yamori-workspace-list pending data-id="no_workspaces"> <p slot="empty-fallback">FALLBACK</p> </yamori-workspace-list> </Preview> <Preview title="Pending / System Error"> <yamori-workspace-list pending data-id="system_error" ></yamori-workspace-list> </Preview> </Document> </DocsLayout>
-
-
-
@@ -90,6 +90,14 @@ this.dispatchEvent(new OpenEvent(workspace));}); }; get workspace(): Workspace | null { return this.#workspace; } set workspace(workspace: Workspace) { this.setWorkspace(workspace); } setWorkspace(workspace: Workspace) { this.#workspace = workspace; this.#render();
-
-
-
@@ -9,6 +9,7 @@ box-sizing: border-box;} :host { position: relative; display: block; }
-
@@ -24,3 +25,18 @@.fallback { display: contents; } .revalidating { display: block; position: absolute; top: 0; right: 0; padding: var(--space-px-3) var(--space-px-5); font-size: var(--font-sm); border: 1px solid oklch(var(--color-border) / var(--alpha-border-subtle)); background-color: oklch(var(--color-bg)); border-radius: var(--space-px-2); box-shadow: 2px 2px 4px 0 oklch(0% 0% 0deg / 5%); color: oklch(var(--color-fg) / var(--alpha-fg-medium)); }
-
-
-
@@ -2,6 +2,8 @@ // SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com>// SPDX-License-Identifier: AGPL-3.0-only import { fromJsonString } from "@bufbuild/protobuf"; import { html, nothing, render, type TemplateResult } from "lit-html"; import { repeat } from "lit-html/directives/repeat.js"; import { type ListResponse,
-
@@ -47,8 +49,6 @@#pending: boolean = this.hasAttribute("pending"); #data: ListResponse | null = this.#parseListResponseAttribute(); #shadow: ShadowRoot; #startMarker: Comment; #endMarker: Comment; attributeChangedCallback( name: ObservedAttributes[number],
-
@@ -97,16 +97,6 @@this.#shadow = this.attachShadow({ mode: "open", }); const style = document.createElement("style"); style.textContent = css; this.#shadow.appendChild(style); this.#startMarker = document.createComment(""); this.#shadow.appendChild(this.#startMarker); this.#endMarker = document.createComment(""); this.#shadow.appendChild(this.#endMarker); } setListResponse(listResponse: ListResponse): void {
-
@@ -114,99 +104,91 @@ this.#data = listResponse;this.#render(); } #render(): void { const range = document.createRange(); range.setStartAfter(this.#startMarker); range.setEndBefore(this.#endMarker); range.deleteContents(); #pendingIndicator(): TemplateResult { if (!this.#pending) { return html`${nothing}`; } return html` <span class="revalidating">データ取得中...</span> `; } #view(): TemplateResult { // データがない場合はどうしようもないので読込中としてお茶を濁す if (!this.#data) { // データがない場合はどうしようもないので読込中と同様とする const label = document.createElement("p"); label.textContent = "取得中..."; this.#shadow.insertBefore(label, this.#endMarker); return; } return html` ${this.#pendingIndicator()} if (this.#pending) { const spinner = document.createElement("div"); spinner.classList.add("spinner"); this.#shadow.insertBefore(spinner, this.#endMarker); <p>取得中...</p> `; } switch (this.#data.result.case) { case "systemError": { const empty = document.createElement(YamoriEmpty.tagName) as InstanceType< typeof YamoriEmpty.constructor >; const icon = document.createElement(LucideBadgeX.tagName) as InstanceType< typeof LucideBadgeX.constructor >; icon.slot = "icon"; empty.appendChild(icon); const title = document.createElement("span"); title.slot = "title"; title.textContent = "取得エラー"; empty.appendChild(title); empty.append( "ワークスペース一覧の取得に失敗しました。", document.createElement("br"), `エラーコード: ${this.#data.result.value.code}`, ); const retry = document.createElement(YamoriButton.tagName) as InstanceType< typeof YamoriButton.constructor >; retry.slot = "action"; retry.textContent = "再取得"; retry.addEventListener("click", (event) => { const onRetry = (event: MouseEvent) => { event.preventDefault(); event.stopPropagation(); this.dispatchEvent(new RetryEvent()); }); empty.appendChild(retry); }; this.#shadow.insertBefore(empty, this.#endMarker); return; return html` ${this.#pendingIndicator()} <yamori-empty> <lucide-badge-x slot="icon"></lucide-badge-x> <span slot="title">取得エラー</span> ワークスペース一覧の取得に失敗しました。<br/> エラーコード: ${this.#data.result.value.code} <yamori-button slot="action" ?pending=${this.#pending} @click=${onRetry}> 再取得 </yamori-button> </yamori-empty> `; } case "ok": { if (this.#data.result.value.workspaces.length === 0) { const slot = document.createElement("slot"); slot.name = "empty-fallback"; slot.classList.add("fallback"); this.#shadow.insertBefore(slot, this.#endMarker); return; } const { workspaces } = this.#data.result.value; const list = document.createElement("ul"); list.classList.add("list"); this.#shadow.insertBefore(list, this.#endMarker); if (workspaces.length === 0) { return html` ${this.#pendingIndicator()} const { workspaces } = this.#data.result.value; <slot name="empty-fallback" class="fallback"></slot> `; } customElements.whenDefined(YamoriWorkspaceListEntry.tagName).then(() => { for (const workspace of workspaces) { const item = document.createElement( YamoriWorkspaceListEntry.tagName, ) as InstanceType<typeof YamoriWorkspaceListEntry.constructor>; item.setWorkspace(workspace); list.appendChild(item); } }); return html` ${this.#pendingIndicator()} return; <ul class="list"> ${repeat( workspaces, (workspace) => workspace.id?.value, (workspace) => html` <yamori-workspace-list-entry .workspace=${workspace}></yamori-workspace-list-entry> `, )} </ul> `; } default: { const message = document.createElement("p"); message.textContent = "データが破損しています"; this.#shadow.insertBefore(message, this.#endMarker); return; return html`<p>データが破損しています</p>`; } } } #render(): void { render( html` <style>${css}</style> ${this.#view()} `, this.#shadow, ); } }, });
-