127 lines
4.6 KiB
TypeScript
127 lines
4.6 KiB
TypeScript
/**
|
|
* <folk-choices-dashboard> — lists choice shapes (polls, rankings, spider charts)
|
|
* from the current space and links to the canvas to create/interact with them.
|
|
*/
|
|
|
|
class FolkChoicesDashboard extends HTMLElement {
|
|
private shadow: ShadowRoot;
|
|
private choices: any[] = [];
|
|
private loading = true;
|
|
private space: string;
|
|
|
|
constructor() {
|
|
super();
|
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
this.space = this.getAttribute("space") || "demo";
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.render();
|
|
this.loadChoices();
|
|
}
|
|
|
|
private getApiBase(): string {
|
|
const path = window.location.pathname;
|
|
const parts = path.split("/").filter(Boolean);
|
|
return parts.length >= 2 ? `/${parts[0]}/choices` : "/demo/choices";
|
|
}
|
|
|
|
private async loadChoices() {
|
|
this.loading = true;
|
|
this.render();
|
|
try {
|
|
const res = await fetch(`${this.getApiBase()}/api/choices`);
|
|
const data = await res.json();
|
|
this.choices = data.choices || [];
|
|
} catch (e) {
|
|
console.error("Failed to load choices:", e);
|
|
}
|
|
this.loading = false;
|
|
this.render();
|
|
}
|
|
|
|
private render() {
|
|
const typeIcons: Record<string, string> = {
|
|
"folk-choice-vote": "\u2611",
|
|
"folk-choice-rank": "\uD83D\uDCCA",
|
|
"folk-choice-spider": "\uD83D\uDD78",
|
|
};
|
|
const typeLabels: Record<string, string> = {
|
|
"folk-choice-vote": "Poll",
|
|
"folk-choice-rank": "Ranking",
|
|
"folk-choice-spider": "Spider Chart",
|
|
};
|
|
|
|
this.shadow.innerHTML = `
|
|
<style>
|
|
:host { display: block; padding: 1.5rem; }
|
|
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; }
|
|
.header h2 { margin: 0; color: #f1f5f9; font-size: 1.5rem; }
|
|
.create-btns { display: flex; gap: 0.5rem; }
|
|
.create-btn { padding: 0.5rem 1rem; border-radius: 8px; border: 1px solid #334155; background: #1e293b; color: #94a3b8; cursor: pointer; font-size: 0.875rem; text-decoration: none; }
|
|
.create-btn:hover { border-color: #6366f1; color: #f1f5f9; }
|
|
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1rem; }
|
|
.card { background: #1e293b; border: 1px solid #334155; border-radius: 12px; padding: 1.25rem; cursor: pointer; text-decoration: none; display: block; }
|
|
.card:hover { border-color: #6366f1; }
|
|
.card-icon { font-size: 1.5rem; margin-bottom: 0.5rem; }
|
|
.card-title { color: #f1f5f9; font-weight: 600; font-size: 1rem; margin: 0 0 0.25rem; }
|
|
.card-type { color: #818cf8; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
|
|
.card-meta { color: #94a3b8; font-size: 0.8125rem; }
|
|
.stat { display: inline-block; margin-right: 1rem; }
|
|
.empty { text-align: center; padding: 3rem; color: #64748b; }
|
|
.empty-icon { font-size: 3rem; margin-bottom: 1rem; }
|
|
.empty p { margin: 0.5rem 0; font-size: 0.875rem; }
|
|
.loading { text-align: center; padding: 3rem; color: #94a3b8; }
|
|
.info { background: rgba(99,102,241,0.1); border: 1px solid rgba(99,102,241,0.2); border-radius: 8px; padding: 1rem; margin-bottom: 1.5rem; color: #a5b4fc; font-size: 0.8125rem; }
|
|
</style>
|
|
|
|
<div class="header">
|
|
<h2>\u2611 Choices</h2>
|
|
<div class="create-btns">
|
|
<a class="create-btn" href="/${this.space}/canvas" title="Open canvas to create choices">\u2795 New on Canvas</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="info">
|
|
Choice tools (Polls, Rankings, Spider Charts) live on the collaborative canvas.
|
|
Create them there and they'll appear here for quick access.
|
|
</div>
|
|
|
|
${this.loading ? `<div class="loading">\u23F3 Loading choices...</div>` :
|
|
this.choices.length === 0 ? this.renderEmpty() : this.renderGrid(typeIcons, typeLabels)}
|
|
`;
|
|
}
|
|
|
|
private renderEmpty(): string {
|
|
return `<div class="empty">
|
|
<div class="empty-icon">\u2611</div>
|
|
<p>No choices in this space yet.</p>
|
|
<p>Open the <a href="/${this.space}/canvas" style="color:#818cf8">canvas</a> and use the Poll, Rank, or Spider buttons to create one.</p>
|
|
</div>`;
|
|
}
|
|
|
|
private renderGrid(icons: Record<string, string>, labels: Record<string, string>): string {
|
|
return `<div class="grid">
|
|
${this.choices.map((ch) => `
|
|
<a class="card" href="/${this.space}/canvas">
|
|
<div class="card-icon">${icons[ch.type] || "\u2611"}</div>
|
|
<div class="card-type">${labels[ch.type] || ch.type}</div>
|
|
<h3 class="card-title">${this.esc(ch.title)}</h3>
|
|
<div class="card-meta">
|
|
<span class="stat">${ch.optionCount} options</span>
|
|
<span class="stat">${ch.voteCount} responses</span>
|
|
</div>
|
|
</a>
|
|
`).join("")}
|
|
</div>`;
|
|
}
|
|
|
|
private esc(s: string): string {
|
|
const d = document.createElement("div");
|
|
d.textContent = s;
|
|
return d.innerHTML;
|
|
}
|
|
}
|
|
|
|
customElements.define("folk-choices-dashboard", FolkChoicesDashboard);
|