From 3de2b0fe19904b314fb43e014552864b290443f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pons?= Date: Mon, 17 Nov 2025 17:54:28 +0100 Subject: [PATCH] Add character selection using campaign visibility and player characters in campaign --- app/composables/useDatabase.ts | 1 + app/types/campaign.d.ts | 25 ++++++++ db.sqlite | Bin 761856 -> 761856 bytes server/api/campaign/[id].get.ts | 13 ++-- server/api/character/[id].get.ts | 22 ++++++- shared/campaign.util.ts | 105 ++++++++++++++----------------- shared/character.util.ts | 4 +- 7 files changed, 98 insertions(+), 72 deletions(-) create mode 100644 app/types/campaign.d.ts diff --git a/app/composables/useDatabase.ts b/app/composables/useDatabase.ts index 622aa6a..d4fb931 100644 --- a/app/composables/useDatabase.ts +++ b/app/composables/useDatabase.ts @@ -1,6 +1,7 @@ import { Database } from "bun:sqlite"; import { BunSQLiteDatabase, drizzle } from "drizzle-orm/bun-sqlite"; import * as schema from '../db/schema'; +import { eq, or, sql } from "drizzle-orm"; let instance: BunSQLiteDatabase & { $client: Database; diff --git a/app/types/campaign.d.ts b/app/types/campaign.d.ts new file mode 100644 index 0000000..683894b --- /dev/null +++ b/app/types/campaign.d.ts @@ -0,0 +1,25 @@ +import type { User } from "./auth"; +import type { Character, ItemState } from "./character"; +import type { Serialize } from 'nitropack'; + +export type CampaignVariables = { + money: number; + inventory: ItemState[]; +}; +export type Campaign = { + id: number; + name: string; + link: string; + owner: { id: number, username: string }; + members: Array<{ member: { id: number, username: string } }>; + characters: Array>; + public_notes: string; + dm_notes: string; + logs: CampaignLog[]; +} & CampaignVariables; +export type CampaignLog = { + from: number; + timestamp: Serialize; + type: 'ITEM' | 'CHARACTER' | 'PLACE' | 'EVENT' | 'FIGHT' | 'TEXT'; + details: string; +}; \ No newline at end of file diff --git a/db.sqlite b/db.sqlite index 9b536c3d9c013b6289e13510133e4e42018f9dd9..76ab3e74e8cd30a7f0994135b996c5857c24c040 100644 GIT binary patch delta 43513 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 A$p8QV diff --git a/server/api/campaign/[id].get.ts b/server/api/campaign/[id].get.ts index 4cfc53e..42674c4 100644 --- a/server/api/campaign/[id].get.ts +++ b/server/api/campaign/[id].get.ts @@ -19,20 +19,17 @@ export default defineEventHandler(async (e) => { const db = useDatabase(); const data = db.query.campaignTable.findFirst({ + columns: { owner: false }, with: { - members: { with: { member: { columns: { username: true, id: true } } }, columns: { id: false, rights: false, user: false }, extras: { 'characters': sql`NULL`.as('characters') } }, - characters: { with: { character: { columns: { id: true, name: true, owner: true } } } }, + members: { with: { member: { columns: { username: true, id: true } } }, columns: { id: false, user: false } }, + characters: { with: { character: { columns: { id: true, name: true, owner: true } } }, columns: { character: false } }, owner: { columns: { username: true, id: true } }, - logs: { columns: { details: true, from: true, timestamp: true, type: true }, orderBy: ({ timestamp }) => timestamp } + logs: { columns: { details: true, from: true, timestamp: true, type: true }, orderBy: ({ timestamp }) => timestamp }, }, where: ({ id: _id }) => eq(_id, parseInt(id, 10)), }).sync(); - if(data && (data.owner.id === session.user.id || data.members.find(e => e.member?.id === session.user!.id))) - { - data.members.forEach(e => e.characters = data.characters.filter(_e => _e.character?.owner === e.member?.id)); - return data as Campaign; - } + if(data && (data.owner.id === session.user.id || data.members.find(e => e.member?.id === session.user!.id))) return data as Campaign; else if(!data) return setResponseStatus(e, 404); else return setResponseStatus(e, 403); }); \ No newline at end of file diff --git a/server/api/character/[id].get.ts b/server/api/character/[id].get.ts index 3e2c989..a242622 100644 --- a/server/api/character/[id].get.ts +++ b/server/api/character/[id].get.ts @@ -1,7 +1,8 @@ import useDatabase from '~/composables/useDatabase'; -import { characterTable } from '~/db/schema'; +import { campaignCharactersTable, campaignMembersTable, campaignTable, characterAbilitiesTable, characterChoicesTable, characterLevelingTable, characterTable, characterTrainingTable, usersTable } from '~/db/schema'; import { group } from '#shared/general.util'; -import type { Character, CharacterVariables, Level, MainStat, TrainingLevel } from '~/types/character'; +import type { Character, MainStat, TrainingLevel } from '~/types/character'; +import { and, eq, exists, getTableColumns, isNotNull, or, sql } from 'drizzle-orm'; export default defineEventHandler(async (e) => { const id = getRouterParam(e, "id"); @@ -21,6 +22,7 @@ export default defineEventHandler(async (e) => { } const db = useDatabase(); + const character = db.query.characterTable.findFirst({ with: { abilities: true, @@ -29,9 +31,23 @@ export default defineEventHandler(async (e) => { choices: true, user: { columns: { username: true } + }, + campaign: { + columns: { character: false, id: false, }, + with: { + campaign: { + columns: { owner: true, }, + with: { + members: { columns: { user: true } } + } + } + } } }, - where: (character, { eq, and }) => and(eq(character.id, parseInt(id, 10)), eq(characterTable.owner, session.user!.id)), + where: and(eq(characterTable.id, parseInt(id, 10)), or(eq(characterTable.visibility, 'public'), eq(characterTable.owner, session.user!.id), exists(db.select({ id: sql`NULL` }).from(campaignCharactersTable) + .leftJoin(campaignTable, eq(campaignCharactersTable.id, campaignTable.id)) + .leftJoin(campaignMembersTable, and(eq(campaignMembersTable.id, campaignTable.id), eq(campaignMembersTable.user, session.user.id))) + .where(and(eq(campaignCharactersTable.character, parseInt(id, 10)), or(eq(campaignTable.owner, session.user.id), isNotNull(campaignMembersTable.user))))))), }).sync(); if(character !== undefined) diff --git a/shared/campaign.util.ts b/shared/campaign.util.ts index 1cd4265..da64185 100644 --- a/shared/campaign.util.ts +++ b/shared/campaign.util.ts @@ -5,6 +5,7 @@ import { div, dom, icon, span, text } from "#shared/dom.util"; import { button, loading } from "#shared/components.util"; import { CharacterCompiler } from "#shared/character.util"; import { tooltip } from "./floating.util"; +import type { Character } from "~/types/character"; export const CampaignValidation = z.object({ id: z.number(), @@ -27,13 +28,33 @@ async function copyLink() */ +class CharacterPrinter +{ + private compiler?: CharacterCompiler; + container: HTMLElement = div('flex flex-col gap-2'); + constructor(character: number, name: string) + { + this.container.replaceChildren(div('flex flex-row gap-1', [span(undefined, 'joue'), span('font-bold', name)]), loading('small')); + useRequestFetch()(`/api/character/${character}`).then((character) => { + if(character) + { + this.compiler = new CharacterCompiler(character); + this.container.replaceChildren(div('flex flex-row gap-1', [span(undefined, 'joue'), span('font-bold', name)]), ); + } + else throw new Error(); + }).catch((e) => { + console.error(e); + this.container.replaceChildren(span('text-sm italic text-light-red dark:text-dark-red', 'Données indisponible')); + }) + } +} type PlayerState = { status: boolean; statusDOM: HTMLElement; statusTooltip: Text; - user: User; + user: { id: number, username: string }; }; -function defaultPlayerState(user: User): PlayerState +function defaultPlayerState(user: { id: number, username: string }): PlayerState { const statusTooltip = text('Absent'); return { @@ -49,16 +70,21 @@ export class CampaignSheet campaign?: Campaign; container: HTMLElement = div('flex flex-1 h-full w-full items-start justify-center'); dm!: PlayerState; + players!: Array constructor(id: string, user: ComputedRef) { this.user = user; const load = div("flex justify-center items-center w-full h-full", [ loading('large') ]); this.container.replaceChildren(load); - useRequestFetch()(`/api/campaign/${id}`).then(campaign => { + useRequestFetch()(`/api/campaign/${id}`).then((campaign) => { if(campaign) { this.campaign = campaign; this.dm = defaultPlayerState(campaign.owner); + this.players = campaign.members.map(e => ({ + ...defaultPlayerState(e.member), + characters: campaign.characters.filter(_e => _e.character?.owner === e.member.id).map(_e => new CharacterPrinter(_e.character!.id, _e.character!.name)), + })); document.title = `d[any] - Campagne ${campaign.name}`; this.render(); @@ -77,14 +103,19 @@ export class CampaignSheet ])); }); } - render() + private render() { + const campaign = this.campaign; + + if(!campaign) + return; + this.container.replaceChildren(div('flex flex-col w-full h-full items-center gap-4', [ div('flex flex-row gap-8 items-center', [ - div('text-2xl font-semibold', [text(this.campaign.name)]), + div('text-2xl font-semibold', [text(campaign.name)]), div('border border-light-35 dark:border-dark-35 p-1 flex flex-row items-center gap-2', [ - dom('pre', { class: 'ps-1 w-[400px] truncate' }, [ text(`https://d-any.com/campaign/join/${ encodeURIComponent(this.campaign.link) }`) ]), - button(icon('radix-icons:clipboard', { width: 16, height: 16 })), + dom('pre', { class: 'ps-1 w-[400px] truncate' }, [ text(`https://d-any.com/campaign/join/${ encodeURIComponent(campaign.link) }`) ]), + button(icon('radix-icons:clipboard', { width: 16, height: 16 }), () => {}, 'p-1'), ]) ]), div('flex flex-row gap-4 flex-1', [ @@ -93,73 +124,29 @@ export class CampaignSheet div('flex flex-col divide-y divide-light-25 dark:divide-dark-25', [ div('flex flex-col py-1 my-1', [ div('flex flex-row items-center justify-between', [ - span(undefined, this.campaign.owner.username), + span(undefined, this.dm.user.username), this.dm.statusDOM, ]) ]) ]), div('flex flex-row items-center gap-4', [ span('text-lg font-bold tracking-tight', 'Joueurs'), span('border-b border-dashed border-light-35 dark:border-dark-35 flex-1') ]), div('flex flex-col divide-y divide-light-25 dark:divide-dark-25', - this.campaign.members.length > 0 ? this.campaign.members.map((player: any) => div('flex flex-col py-1 my-1', [ + this.players.length > 0 ? this.players.map((player) => div('flex flex-col py-1 my-1', [ div('flex flex-row items-center justify-between', [ - span(undefined, player.member.username), - undefined, + span(undefined, player.user.username), + player.statusDOM, ]), - player.characters.length > 0 ? div('flex flex-col', - player.characters.map((character: any) => div('flex flex-row items-center justify-between', [ - span(undefined, 'joue'), span('text-bold', character.name) + player.characters && player.characters.length > 0 ? div('flex flex-col', + player.characters.map((character) => div('flex flex-row items-center justify-between', [ + character.container ])) ) : span('text-sm italic text-light-70 dark:text-dark-70', 'Pas de personnages'), - ])) : span('text-sm italic py-2 text-center', 'Invitez des joueurs via le lien'), + ])) : [span('text-sm italic py-2 text-center', 'Invitez des joueurs via le lien')], ) ]), div('border-l border-light-35 dark:border-dark-35'), div('flex flex-col divide-y divide-light-25 dark:divide-dark-25 w-[800px]') ]) ])); - /* -
-
-
-
Maitre de jeu
-
-
-
- {{ campaign.owner.username }} - -
-
-
-
Joueurs
-
- - -
-
-
-
-
-
-
- */ } } \ No newline at end of file diff --git a/shared/character.util.ts b/shared/character.util.ts index 40f9223..e40e520 100644 --- a/shared/character.util.ts +++ b/shared/character.util.ts @@ -1359,7 +1359,7 @@ export class CharacterSheet readonly: dom("span", { class: "font-bold px-2 border-transparent border cursor-pointer hover:border-light-35 dark:hover:border-dark-35", text: `${character.health - character.variables.health}`, - listeners: { click: () => health.readonly.replaceWith(health.edit) }, + listeners: { click: () => { health.readonly.replaceWith(health.edit); health.edit.select(); health.edit.focus(); } }, }), edit: input('text', { defaultValue: (character.health - character.variables.health).toString(), input: (v) => { return v.startsWith('-') || v.startsWith('+') ? v.length === 1 || !isNaN(parseInt(v.substring(1), 10)) : v.length === 0 || !isNaN(parseInt(v, 10)); @@ -1370,7 +1370,7 @@ export class CharacterSheet readonly: dom("span", { class: "font-bold px-2 border-transparent border cursor-pointer hover:border-light-35 dark:hover:border-dark-35", text: `${character.mana - character.variables.mana}`, - listeners: { click: () => mana.readonly.replaceWith(mana.edit) }, + listeners: { click: () => { mana.readonly.replaceWith(mana.edit); mana.edit.select(); mana.edit.focus(); } }, }), edit: input('text', { defaultValue: (character.mana - character.variables.mana).toString(), input: (v) => { return v.startsWith('-') || v.startsWith('+') ? v.length === 1 || !isNaN(parseInt(v.substring(1), 10)) : v.length === 0 || !isNaN(parseInt(v, 10));