a homebrewed DnD campaign based in the Honkai: Star Rail universe
hsr honkaistarrail dnd
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

migrate factions to use remote functions

+150 -36
-16
app/src/routes/(app)/factions/+page.server.ts
··· 1 - import { db } from '$server/db' 2 - import { FactionRepository } from '$server/db/repos/faction' 3 - import { definePageMeta } from '$ui/page-meta' 4 - import type { PageServerLoad } from './$types' 5 - 6 - export const load: PageServerLoad = async () => { 7 - const factionsRepo = new FactionRepository(db) 8 - const queryFactions = await factionsRepo.getFactionsBy() 9 - 10 - return { 11 - meta: definePageMeta({ 12 - title: 'Factions', 13 - }), 14 - factions: queryFactions, 15 - } 16 - }
+6 -7
app/src/routes/(app)/factions/+page.svelte
··· 3 3 import FactionIcon from '$ui/icons/FactionIcon.svelte' 4 4 import { Breadcrumb } from '$ui/layout/breadcrumb' 5 5 import NoEntriesFound from '../NoEntriesFound.svelte' 6 - import type { PageProps } from './$types' 6 + import { getPageMeta, queryBreadcrumb, queryFactions } from './page.remote' 7 7 8 - const { data }: PageProps = $props() 9 - const { meta, factions } = $derived(data) 8 + const meta = await getPageMeta() 9 + const paths = await queryBreadcrumb() 10 + const factions = queryFactions() 10 11 </script> 11 12 12 13 <svelte:head> 13 14 <PageMeta {...meta} /> 14 15 </svelte:head> 15 16 16 - <Breadcrumb links={[ 17 - { path: '/factions', text: 'Factions' } 18 - ]} /> 17 + <Breadcrumb links={paths.root} /> 19 18 20 - {#each factions as faction (faction.id)} 19 + {#each await factions as faction (faction.id)} 21 20 {@debug faction} 22 21 {:else} 23 22 <NoEntriesFound entryName="faction">
-10
app/src/routes/(app)/factions/new/+page.server.ts
··· 1 - import { definePageMeta } from '$ui/page-meta' 2 - import type { PageServerLoad } from './$types' 3 - 4 - export const load: PageServerLoad = async () => { 5 - return { 6 - meta: definePageMeta({ 7 - title: 'Create a new faction entry', 8 - }), 9 - } 10 - }
+94 -3
app/src/routes/(app)/factions/new/+page.svelte
··· 1 1 <script lang="ts"> 2 + import GripVerticalIcon from '@lucide/svelte/icons/grip-vertical' 3 + import PencilLineIcon from '@lucide/svelte/icons/pencil-line' 4 + import PlusIcon from '@lucide/svelte/icons/plus' 5 + import Trash2Icon from '@lucide/svelte/icons/trash-2' 6 + import { Button } from '$ui/components/button' 7 + import { HeadingGroup, Heading, SubHeading } from '$ui/components/heading' 8 + import { Field } from '$ui/form/field' 9 + import { TextInput, TextAreaInput } from '$ui/form/text-input' 10 + import RelationshipIcon from '$ui/icons/RelationshipIcon.svelte' 11 + import { Breadcrumb } from '$ui/layout/breadcrumb' 12 + import { PageLayout } from '$ui/layout/page-layout' 2 13 import { PageMeta } from '$ui/page-meta' 3 - import type { PageProps } from './$types' 14 + import { queryBreadcrumb } from '../page.remote' 15 + import { createFaction, getPageMeta } from './page.remote' 4 16 5 - let { data }: PageProps = $props() 6 - const { meta } = $derived(data) 17 + const meta = await getPageMeta() 18 + const paths = await queryBreadcrumb() 19 + 20 + function capitalizeText(s: string): string { 21 + return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase() 22 + } 23 + 24 + let relationships: { faction: string, kind: 'friendly' | 'hostile' | 'neutral' }[] = $state([ 25 + { faction: 'Genius Society', kind: 'hostile' }, 26 + { faction: 'Intelligentsia Guild', kind: 'friendly' }, 27 + { faction: 'The Garden of Recollection', kind: 'neutral' }, 28 + ]) 7 29 </script> 8 30 9 31 <svelte:head> 10 32 <PageMeta {...meta} /> 11 33 </svelte:head> 34 + 35 + <Breadcrumb links={paths.newEntry} /> 36 + 37 + <PageLayout class="items-stretch"> 38 + <div class="flex flex-col gap-8 mx-auto max-w-50ch"> 39 + <HeadingGroup class="flex flex-col gap-2"> 40 + <Heading level={2}>Create a new faction entry</Heading> 41 + <SubHeading> 42 + A faction represents an organization that's centered around a certain set of ethics, values, and morals. 43 + All members of a given faction share a common, political purpose. 44 + </SubHeading> 45 + </HeadingGroup> 46 + <form class="w-full flex flex-col gap-6" {...createFaction}> 47 + <Field.Root> 48 + <Field.Content> 49 + <Field.Label for="name">Name</Field.Label> 50 + <Field.Description>Must be at least 5 characters long.</Field.Description> 51 + </Field.Content> 52 + <TextInput {...createFaction.fields.name.as('text')} /> 53 + </Field.Root> 54 + <Field.Root> 55 + <Field.Content> 56 + <Field.Label for="description">Description</Field.Label> 57 + <Field.Description>You can write about the shared beliefs, goals, their leader, and how they behave with other factions.</Field.Description> 58 + </Field.Content> 59 + <TextAreaInput rows={6} {...createFaction.fields.description.as('text')} /> 60 + </Field.Root> 61 + <Field.Label for="faction">Relationships with other factions</Field.Label> 62 + <div class="grid grid-cols-6 gap-2"> 63 + {#each relationships as relationship, i (relationship.faction)} 64 + {@const formattedNum = (i + 1).toString().padStart(2, '0')} 65 + {@const formattedKind = capitalizeText(relationship.kind)} 66 + <div class="col-span-4 flex flex-row gap-2 items-center"> 67 + <GripVerticalIcon class="size-5 text-zinc-500" /> 68 + <Field.Root> 69 + <TextInput name="faction" value={relationship.faction} class="self-stretch"> 70 + {#snippet before()} 71 + <div class="font-mono text-zinc-500 text-sm mr-1.5"> 72 + {formattedNum} 73 + </div> 74 + {/snippet} 75 + </TextInput> 76 + </Field.Root> 77 + </div> 78 + <div class="col-span-2 flex flex-row gap-2 items-center"> 79 + <Field.Root class="col-span-2"> 80 + <TextInput name="faction-kind" value={formattedKind}> 81 + {#snippet before()} 82 + <RelationshipIcon kind={relationship.kind} class="size-5 text-zinc-500 mr-1.5" /> 83 + {/snippet} 84 + </TextInput> 85 + </Field.Root> 86 + <Trash2Icon class="size-5 text-zinc-500" /> 87 + </div> 88 + {/each} 89 + <div class="col-span-6 border border-zinc-300 border-dashed bg-zinc-100 p-3 rounded-md flex flex-row items-center justify-center gap-4 cursor-pointer"> 90 + <PlusIcon class="size-5 text-zinc-500" /> 91 + Add new faction relationship 92 + </div> 93 + </div> 94 + <footer> 95 + <Button> 96 + <PencilLineIcon /> 97 + Publish faction entry 98 + </Button> 99 + </footer> 100 + </form> 101 + </div> 102 + </PageLayout>
+22
app/src/routes/(app)/factions/new/page.remote.ts
··· 1 + import { form, prerender } from '$app/server' 2 + import { db } from '$server/db' 3 + import { FactionRepository, zFactionSchema } from '$server/db/repos/faction' 4 + import { definePageMeta } from '$ui/page-meta' 5 + 6 + export const getPageMeta = prerender(() => 7 + definePageMeta({ 8 + title: 'Create a new faction entry', 9 + }), 10 + ) 11 + 12 + export const createFaction = form( 13 + zFactionSchema.toInsert.omit({ parentFactionId: true }), 14 + async ({ name, description }, _issue) => { 15 + const factionRepo = new FactionRepository(db) 16 + await factionRepo.createFaction({ 17 + name, 18 + description, 19 + sourcebookId: 1, 20 + }) 21 + }, 22 + )
+28
app/src/routes/(app)/factions/page.remote.ts
··· 1 + import { prerender, query } from '$app/server' 2 + import { db } from '$server/db' 3 + import { FactionRepository } from '$server/db/repos/faction' 4 + import { definePaths } from '$ui/layout/breadcrumb' 5 + import { definePageMeta } from '$ui/page-meta' 6 + 7 + export const getPageMeta = prerender(() => 8 + definePageMeta({ 9 + title: 'Factions', 10 + }), 11 + ) 12 + 13 + export const queryFactions = query(async () => { 14 + const factionsRepo = new FactionRepository(db) 15 + const queryFactions = await factionsRepo.getFactionsBy() 16 + 17 + return queryFactions 18 + }) 19 + 20 + export const queryBreadcrumb = query(async () => { 21 + const root = { path: '/factions', text: 'Factions' } 22 + const newEntry = { path: '/factions/new', text: 'Create a new faction entry' } 23 + 24 + return definePaths({ 25 + root: [root], 26 + newEntry: [root, newEntry], 27 + }) 28 + })