455 lines
14 KiB
TypeScript
455 lines
14 KiB
TypeScript
/**
|
|
* Template seeder for new rSpace communities.
|
|
*
|
|
* Every non-demo space gets generic "Getting Started" content covering
|
|
* all 25+ modules so users immediately see what each rApp can do.
|
|
*
|
|
* Shape IDs use a `tmpl-` prefix so they never collide with `demo-` or
|
|
* user-created shapes. Seeding is idempotent — only spaces with 0 shapes
|
|
* are touched, and the demo space is always skipped.
|
|
*/
|
|
|
|
import {
|
|
addShapes,
|
|
getDocumentData,
|
|
listCommunities,
|
|
loadCommunity,
|
|
} from "./community-store";
|
|
|
|
// ── Template Shapes ─────────────────────────────────────────────────
|
|
|
|
const TEMPLATE_SHAPES: Record<string, unknown>[] = [
|
|
// ─── rTrips: Itinerary ──────────────────────────────────────
|
|
{
|
|
id: "tmpl-itinerary",
|
|
type: "folk-itinerary",
|
|
x: 50, y: 50, width: 400, height: 300, rotation: 0,
|
|
tripTitle: "My Trip",
|
|
startDate: "",
|
|
endDate: "",
|
|
travelers: [],
|
|
items: [
|
|
{ date: "Day 1", activity: "Arrive & settle in", category: "travel" },
|
|
{ date: "Day 2", activity: "Explore the area", category: "adventure" },
|
|
{ date: "Day 3", activity: "Head home", category: "travel" },
|
|
],
|
|
},
|
|
|
|
// ─── rTrips: Destination ────────────────────────────────────
|
|
{
|
|
id: "tmpl-destination",
|
|
type: "folk-destination",
|
|
x: 500, y: 50, width: 300, height: 200, rotation: 0,
|
|
destName: "My Destination",
|
|
country: "",
|
|
lat: 0,
|
|
lng: 0,
|
|
arrivalDate: "",
|
|
departureDate: "",
|
|
notes: "Add your destination details here.",
|
|
},
|
|
|
|
// ─── rNotes: Notebook ───────────────────────────────────────
|
|
{
|
|
id: "tmpl-notebook",
|
|
type: "folk-notebook",
|
|
x: 850, y: 50, width: 300, height: 180, rotation: 0,
|
|
notebookTitle: "My Notebook",
|
|
description: "A shared notebook for your space. Add notes, ideas, and reference material.",
|
|
noteCount: 0,
|
|
collaborators: [],
|
|
},
|
|
|
|
// ─── rNotes: Welcome Note ───────────────────────────────────
|
|
{
|
|
id: "tmpl-note-welcome",
|
|
type: "folk-note",
|
|
x: 850, y: 260, width: 300, height: 250, rotation: 0,
|
|
noteTitle: "Welcome to rSpace",
|
|
content: "## Getting Started\n\nThis is your space! Here's what you can do:\n\n- **Explore** the canvas — drag, zoom, and click shapes\n- **Create** new items using the module sidebar\n- **Collaborate** in real-time with your team\n- **Customize** by rearranging or removing these starter shapes\n\nEach shape represents an rApp module. Click one to dive in!",
|
|
tags: ["getting-started"],
|
|
editor: "",
|
|
editedAt: "",
|
|
},
|
|
|
|
// ─── rNotes: Ideas Note ─────────────────────────────────────
|
|
{
|
|
id: "tmpl-note-ideas",
|
|
type: "folk-note",
|
|
x: 1200, y: 50, width: 300, height: 200, rotation: 0,
|
|
noteTitle: "Ideas & Brainstorms",
|
|
content: "## Ideas\n\nCapture your ideas here.\n\n- Idea 1\n- Idea 2\n- Idea 3\n\nUse tags to organize your notes across the space.",
|
|
tags: ["ideas"],
|
|
editor: "",
|
|
editedAt: "",
|
|
},
|
|
|
|
// ─── rVote: Poll ────────────────────────────────────────────
|
|
{
|
|
id: "tmpl-poll",
|
|
type: "demo-poll",
|
|
x: 50, y: 400, width: 350, height: 200, rotation: 0,
|
|
question: "What should we work on first?",
|
|
options: [
|
|
{ label: "Set up the space", votes: 0 },
|
|
{ label: "Invite team members", votes: 0 },
|
|
{ label: "Plan our first project", votes: 0 },
|
|
],
|
|
totalVoters: 0,
|
|
status: "active",
|
|
endsAt: "",
|
|
},
|
|
|
|
// ─── rCart: Item ────────────────────────────────────────────
|
|
{
|
|
id: "tmpl-cart-item",
|
|
type: "demo-cart-item",
|
|
x: 50, y: 630, width: 320, height: 60, rotation: 0,
|
|
name: "Example Item",
|
|
price: 0,
|
|
funded: 0,
|
|
status: "In Cart",
|
|
requestedBy: "",
|
|
store: "",
|
|
},
|
|
|
|
// ─── rFunds: Budget ─────────────────────────────────────────
|
|
{
|
|
id: "tmpl-budget",
|
|
type: "folk-budget",
|
|
x: 450, y: 400, width: 350, height: 250, rotation: 0,
|
|
budgetTitle: "Space Budget",
|
|
currency: "USD",
|
|
budgetTotal: 0,
|
|
spent: 0,
|
|
categories: [
|
|
{ name: "Operations", budget: 0, spent: 0 },
|
|
{ name: "Marketing", budget: 0, spent: 0 },
|
|
{ name: "Development", budget: 0, spent: 0 },
|
|
],
|
|
},
|
|
|
|
// ─── rFunds: Expense ────────────────────────────────────────
|
|
{
|
|
id: "tmpl-expense",
|
|
type: "demo-expense",
|
|
x: 450, y: 680, width: 320, height: 60, rotation: 0,
|
|
description: "Example expense",
|
|
amount: 0,
|
|
currency: "USD",
|
|
paidBy: "",
|
|
split: "equal",
|
|
category: "operations",
|
|
date: "",
|
|
},
|
|
|
|
// ─── rMaps: Location Marker ─────────────────────────────────
|
|
{
|
|
id: "tmpl-map-marker",
|
|
type: "demo-map-marker",
|
|
x: 500, y: 280, width: 40, height: 40, rotation: 0,
|
|
name: "Home Base",
|
|
lat: 0,
|
|
lng: 0,
|
|
emoji: "📍",
|
|
category: "home",
|
|
status: "",
|
|
},
|
|
|
|
// ─── rTokens: Mint ──────────────────────────────────────────
|
|
{
|
|
id: "tmpl-mint",
|
|
type: "folk-token-mint",
|
|
x: 1550, y: 50, width: 320, height: 280, rotation: 0,
|
|
tokenName: "Contribution Token",
|
|
tokenSymbol: "CONTRIB",
|
|
description: "Track and reward contributions to your space. Customize the name, symbol, and supply.",
|
|
totalSupply: 1000,
|
|
issuedSupply: 0,
|
|
tokenColor: "#6d28d9",
|
|
tokenIcon: "⭐",
|
|
createdBy: "",
|
|
createdAt: "",
|
|
},
|
|
|
|
// ─── rTokens: Ledger ────────────────────────────────────────
|
|
{
|
|
id: "tmpl-ledger",
|
|
type: "folk-token-ledger",
|
|
x: 1940, y: 50, width: 380, height: 400, rotation: 0,
|
|
mintId: "tmpl-mint",
|
|
entries: [],
|
|
},
|
|
|
|
// ─── rTokens: Arrow ─────────────────────────────────────────
|
|
{
|
|
id: "tmpl-arrow-tokens",
|
|
type: "folk-arrow",
|
|
x: 0, y: 0, width: 0, height: 0, rotation: 0,
|
|
sourceId: "tmpl-mint",
|
|
targetId: "tmpl-ledger",
|
|
color: "#6d28d9",
|
|
},
|
|
|
|
// ─── rFiles: File ───────────────────────────────────────────
|
|
{
|
|
id: "tmpl-file-readme",
|
|
type: "folk-file",
|
|
x: 1550, y: 500, width: 280, height: 80, rotation: 0,
|
|
fileName: "README.md",
|
|
fileSize: "0 KB",
|
|
mimeType: "text/markdown",
|
|
uploadedBy: "",
|
|
uploadedAt: "",
|
|
tags: ["getting-started"],
|
|
},
|
|
|
|
// ─── rForum: Thread ─────────────────────────────────────────
|
|
{
|
|
id: "tmpl-forum-intro",
|
|
type: "folk-forum-thread",
|
|
x: 1550, y: 620, width: 320, height: 160, rotation: 0,
|
|
threadTitle: "Introductions",
|
|
author: "",
|
|
createdAt: "",
|
|
replyCount: 0,
|
|
lastReply: "",
|
|
preview: "Introduce yourself to the space! Share who you are and what you're excited to work on.",
|
|
tags: ["introductions"],
|
|
},
|
|
|
|
// ─── rBooks: Book ───────────────────────────────────────────
|
|
{
|
|
id: "tmpl-book",
|
|
type: "folk-book",
|
|
x: 1940, y: 500, width: 280, height: 200, rotation: 0,
|
|
bookTitle: "Getting Started Guide",
|
|
author: "rSpace",
|
|
coverColor: "#3b82f6",
|
|
pageCount: 0,
|
|
currentPage: 0,
|
|
readers: [],
|
|
status: "want-to-read",
|
|
},
|
|
|
|
// ─── rPubs: Publication ─────────────────────────────────────
|
|
{
|
|
id: "tmpl-pub",
|
|
type: "folk-pub",
|
|
x: 50, y: 780, width: 300, height: 180, rotation: 0,
|
|
pubTitle: "My First Publication",
|
|
pubType: "zine",
|
|
creator: "",
|
|
format: "",
|
|
status: "draft",
|
|
copies: 0,
|
|
price: 0,
|
|
currency: "USD",
|
|
description: "A placeholder publication. Edit this to create your first zine, booklet, or print artifact.",
|
|
},
|
|
|
|
// ─── rSwag: Item ────────────────────────────────────────────
|
|
{
|
|
id: "tmpl-swag",
|
|
type: "folk-swag",
|
|
x: 400, y: 780, width: 280, height: 120, rotation: 0,
|
|
swagTitle: "Space Sticker",
|
|
swagType: "sticker",
|
|
designer: "",
|
|
sizes: [],
|
|
price: 0,
|
|
currency: "USD",
|
|
status: "draft",
|
|
orderCount: 0,
|
|
},
|
|
|
|
// ─── rProviders: Provider ───────────────────────────────────
|
|
{
|
|
id: "tmpl-provider",
|
|
type: "folk-provider",
|
|
x: 400, y: 930, width: 300, height: 160, rotation: 0,
|
|
providerName: "Local Print Shop",
|
|
location: "",
|
|
capabilities: ["print", "stickers"],
|
|
substrates: ["paper", "vinyl"],
|
|
turnaround: "",
|
|
rating: 0,
|
|
ordersFulfilled: 0,
|
|
},
|
|
|
|
// ─── rWork: Task Board ──────────────────────────────────────
|
|
{
|
|
id: "tmpl-work-board",
|
|
type: "folk-work-board",
|
|
x: 750, y: 780, width: 500, height: 280, rotation: 0,
|
|
boardTitle: "Getting Started Tasks",
|
|
columns: [
|
|
{
|
|
name: "To Do",
|
|
tasks: [
|
|
{ title: "Customize your space", assignee: "", priority: "high" },
|
|
{ title: "Invite collaborators", assignee: "", priority: "medium" },
|
|
],
|
|
},
|
|
{
|
|
name: "In Progress",
|
|
tasks: [
|
|
{ title: "Explore the modules", assignee: "", priority: "medium" },
|
|
],
|
|
},
|
|
{
|
|
name: "Done",
|
|
tasks: [],
|
|
},
|
|
],
|
|
},
|
|
|
|
// ─── rCal: Calendar ─────────────────────────────────────────
|
|
{
|
|
id: "tmpl-calendar",
|
|
type: "folk-calendar",
|
|
x: 50, y: 1000, width: 350, height: 250, rotation: 0,
|
|
calTitle: "My Calendar",
|
|
month: "",
|
|
events: [
|
|
{ date: "Today", title: "Space Created", color: "#22c55e" },
|
|
],
|
|
},
|
|
|
|
// ─── rNetwork: Graph ────────────────────────────────────────
|
|
{
|
|
id: "tmpl-network",
|
|
type: "folk-network",
|
|
x: 1300, y: 780, width: 400, height: 280, rotation: 0,
|
|
networkTitle: "My Network",
|
|
nodes: [
|
|
{ id: "me", label: "Me", role: "organizer" },
|
|
],
|
|
edges: [],
|
|
},
|
|
|
|
// ─── rTube: Video ───────────────────────────────────────────
|
|
{
|
|
id: "tmpl-video",
|
|
type: "folk-video",
|
|
x: 1750, y: 780, width: 300, height: 180, rotation: 0,
|
|
videoTitle: "Welcome Video",
|
|
duration: "",
|
|
creator: "",
|
|
uploadedAt: "",
|
|
views: 0,
|
|
thumbnail: "",
|
|
},
|
|
|
|
// ─── rInbox: Inbox ──────────────────────────────────────────
|
|
{
|
|
id: "tmpl-inbox",
|
|
type: "folk-inbox",
|
|
x: 1750, y: 990, width: 300, height: 160, rotation: 0,
|
|
inboxTitle: "Inbox",
|
|
messages: [
|
|
{ from: "rSpace", text: "Welcome to your new space! Start by exploring the canvas.", time: "" },
|
|
],
|
|
},
|
|
|
|
// ─── rData: Dashboard ───────────────────────────────────────
|
|
{
|
|
id: "tmpl-dashboard",
|
|
type: "folk-dashboard",
|
|
x: 50, y: 1280, width: 320, height: 220, rotation: 0,
|
|
dashTitle: "Space Dashboard",
|
|
metrics: [
|
|
{ label: "Members", value: "1", trend: "neutral" },
|
|
{ label: "Shapes", value: "0", trend: "neutral" },
|
|
{ label: "Tasks", value: "0/3", trend: "neutral" },
|
|
],
|
|
},
|
|
|
|
// ─── rChoices: Decision Matrix ──────────────────────────────
|
|
{
|
|
id: "tmpl-choices",
|
|
type: "folk-choice-matrix",
|
|
x: 420, y: 1280, width: 320, height: 200, rotation: 0,
|
|
choiceTitle: "Decision Matrix",
|
|
options: [
|
|
{ name: "Option A", score: 0, criteria: {} },
|
|
{ name: "Option B", score: 0, criteria: {} },
|
|
],
|
|
decidedBy: "",
|
|
status: "open",
|
|
winner: "",
|
|
},
|
|
|
|
// ─── rSplat: 3D Capture ─────────────────────────────────────
|
|
{
|
|
id: "tmpl-splat",
|
|
type: "folk-splat",
|
|
x: 790, y: 1280, width: 300, height: 160, rotation: 0,
|
|
splatTitle: "My 3D Capture",
|
|
pointCount: "0",
|
|
capturedBy: "",
|
|
capturedAt: "",
|
|
fileSize: "0 KB",
|
|
status: "pending",
|
|
},
|
|
|
|
// ─── rNotes: Packing List ───────────────────────────────────
|
|
{
|
|
id: "tmpl-packing",
|
|
type: "folk-packing-list",
|
|
x: 1140, y: 1280, width: 300, height: 300, rotation: 0,
|
|
listTitle: "My Packing List",
|
|
items: [
|
|
{ name: "Item 1", packed: false, category: "general" },
|
|
{ name: "Item 2", packed: false, category: "general" },
|
|
{ name: "Item 3", packed: false, category: "general" },
|
|
],
|
|
},
|
|
];
|
|
|
|
// ── Public API ──────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Seed template shapes into a space if it has 0 shapes.
|
|
* Skips the demo space. Returns true if shapes were added.
|
|
*/
|
|
export function seedTemplateShapes(slug: string): boolean {
|
|
if (slug === "demo") return false;
|
|
|
|
const data = getDocumentData(slug);
|
|
const shapeCount = data ? Object.keys(data.shapes || {}).length : 0;
|
|
|
|
if (shapeCount > 0) {
|
|
return false;
|
|
}
|
|
|
|
addShapes(slug, TEMPLATE_SHAPES);
|
|
console.log(`[Template] Seeded ${TEMPLATE_SHAPES.length} template shapes into "${slug}"`);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Iterate all existing communities on startup and seed any
|
|
* empty (0-shape) spaces with template content.
|
|
*/
|
|
export async function ensureTemplateSeeding(): Promise<void> {
|
|
const slugs = await listCommunities();
|
|
let seededCount = 0;
|
|
|
|
for (const slug of slugs) {
|
|
if (slug === "demo") continue;
|
|
try {
|
|
await loadCommunity(slug);
|
|
if (seedTemplateShapes(slug)) {
|
|
seededCount++;
|
|
}
|
|
} catch (e) {
|
|
console.error(`[Template] Failed to seed "${slug}":`, e);
|
|
}
|
|
}
|
|
|
|
if (seededCount > 0) {
|
|
console.log(`[Template] Retroactively seeded ${seededCount} space(s)`);
|
|
}
|
|
}
|