Beginning implementation of Figma campaign UI
This commit is contained in:
parent
3de2b0fe19
commit
2a158be3fa
|
|
@ -10,6 +10,7 @@ export type Campaign = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
link: string;
|
link: string;
|
||||||
|
status: "PREPARING" | "PLAYING" | "ARCHIVED";
|
||||||
owner: { id: number, username: string };
|
owner: { id: number, username: string };
|
||||||
members: Array<{ member: { id: number, username: string } }>;
|
members: Array<{ member: { id: number, username: string } }>;
|
||||||
characters: Array<Partial<{ character: { id: number, name: string, owner: number } }>>;
|
characters: Array<Partial<{ character: { id: number, name: string, owner: number } }>>;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
import type { User } from "~/types/auth";
|
import type { User } from "~/types/auth";
|
||||||
import type { Campaign } from "~/types/campaign";
|
import type { Campaign } from "~/types/campaign";
|
||||||
import { div, dom, icon, span, text } from "#shared/dom.util";
|
import { div, dom, icon, span, svg, text } from "#shared/dom.util";
|
||||||
import { button, loading } from "#shared/components.util";
|
import { button, loading, tabgroup } from "#shared/components.util";
|
||||||
import { CharacterCompiler } from "#shared/character.util";
|
import { CharacterCompiler } from "#shared/character.util";
|
||||||
import { tooltip } from "./floating.util";
|
import { tooltip } from "#shared/floating.util";
|
||||||
import type { Character } from "~/types/character";
|
import markdown from "#shared/markdown.util";
|
||||||
|
import { preview } from "./proses";
|
||||||
|
import { format } from "./general.util";
|
||||||
|
|
||||||
export const CampaignValidation = z.object({
|
export const CampaignValidation = z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
|
|
@ -13,33 +15,23 @@ export const CampaignValidation = z.object({
|
||||||
description: z.string()
|
description: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
const { data: campaign, error, status } = await useFetch(`/api/campaign/${id}`);
|
|
||||||
const copied = ref(false);
|
|
||||||
const stopCopy = useDebounceFn(() => copied.value = false, 5000);
|
|
||||||
|
|
||||||
async function copyLink()
|
|
||||||
{
|
|
||||||
await navigator.clipboard.writeText(`https://d-any.com/campaign/join/${ encodeURIComponent(campaign.value!.link) }`);
|
|
||||||
copied.value = true;
|
|
||||||
stopCopy();
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
class CharacterPrinter
|
class CharacterPrinter
|
||||||
{
|
{
|
||||||
private compiler?: CharacterCompiler;
|
compiler?: CharacterCompiler;
|
||||||
container: HTMLElement = div('flex flex-col gap-2');
|
container: HTMLElement = div('flex flex-col gap-2 px-1');
|
||||||
constructor(character: number, name: string)
|
constructor(character: number, name: string)
|
||||||
{
|
{
|
||||||
this.container.replaceChildren(div('flex flex-row gap-1', [span(undefined, 'joue'), span('font-bold', name)]), loading('small'));
|
this.container.replaceChildren(div('flex flex-row justify-between items-center', [ span('text-bold text-xl', name), loading('small')]));
|
||||||
useRequestFetch()(`/api/character/${character}`).then((character) => {
|
useRequestFetch()(`/api/character/${character}`).then((character) => {
|
||||||
if(character)
|
if(character)
|
||||||
{
|
{
|
||||||
this.compiler = new CharacterCompiler(character);
|
this.compiler = new CharacterCompiler(character);
|
||||||
this.container.replaceChildren(div('flex flex-row gap-1', [span(undefined, 'joue'), span('font-bold', name)]), );
|
const compiled = this.compiler.compiled;
|
||||||
|
|
||||||
|
this.container.replaceChildren(div('flex flex-row justify-between items-center', [
|
||||||
|
span('text-bold text-xl', compiled.name),
|
||||||
|
div('flex flex-row gap-2 items-baseline', [ span('text-sm font-bold', 'PV'), span('text-lg', `${(compiled.health ?? 0) - (compiled.variables.health ?? 0)}/${compiled.health ?? 0}`) ])
|
||||||
|
]));
|
||||||
}
|
}
|
||||||
else throw new Error();
|
else throw new Error();
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
|
|
@ -68,9 +60,10 @@ export class CampaignSheet
|
||||||
{
|
{
|
||||||
user: ComputedRef<User | null>;
|
user: ComputedRef<User | null>;
|
||||||
campaign?: Campaign;
|
campaign?: Campaign;
|
||||||
container: HTMLElement = div('flex flex-1 h-full w-full items-start justify-center');
|
container: HTMLElement = div('flex flex-col flex-1 h-full w-full items-center justify-start');
|
||||||
dm!: PlayerState;
|
dm!: PlayerState;
|
||||||
players!: Array<PlayerState & { characters: CharacterPrinter[] }>
|
players!: Array<PlayerState>;
|
||||||
|
characters!: Array<CharacterPrinter>;
|
||||||
constructor(id: string, user: ComputedRef<User | null>)
|
constructor(id: string, user: ComputedRef<User | null>)
|
||||||
{
|
{
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
|
@ -81,10 +74,9 @@ export class CampaignSheet
|
||||||
{
|
{
|
||||||
this.campaign = campaign;
|
this.campaign = campaign;
|
||||||
this.dm = defaultPlayerState(campaign.owner);
|
this.dm = defaultPlayerState(campaign.owner);
|
||||||
this.players = campaign.members.map(e => ({
|
this.players = campaign.members.map(e => defaultPlayerState(e.member));
|
||||||
...defaultPlayerState(e.member),
|
this.characters = campaign.characters.map(e => new CharacterPrinter(e.character!.id, e.character!.name));
|
||||||
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}`;
|
document.title = `d[any] - Campagne ${campaign.name}`;
|
||||||
this.render();
|
this.render();
|
||||||
|
|
||||||
|
|
@ -110,43 +102,55 @@ export class CampaignSheet
|
||||||
if(!campaign)
|
if(!campaign)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.container.replaceChildren(div('flex flex-col w-full h-full items-center gap-4', [
|
this.container.replaceChildren(div('grid grid-cols-3 gap-2 py-4', [
|
||||||
div('flex flex-row gap-8 items-center', [
|
div('flex flex-row gap-2 items-center py-2', [
|
||||||
div('text-2xl font-semibold', [text(campaign.name)]),
|
tooltip(div('w-8 h-8 border border-light-40 dark:border-dark-40 box-content rounded-full'), this.dm.user.username, "bottom"),
|
||||||
|
div('border-l h-full w-0 border-light-40 dark:border-dark-40'),
|
||||||
|
div('flex flex-row gap-1', this.players.map(e => tooltip(div('w-8 h-8 border border-light-40 dark:border-dark-40 box-content rounded-full'), e.user.username, "bottom"))),
|
||||||
|
]),
|
||||||
|
div('flex flex-1 flex-col items-center justify-center gap-2', [
|
||||||
|
span('text-2xl font-serif font-bold italic', campaign.name),
|
||||||
|
span('italic text-light-70 dark:text-dark-70 text-sm', campaign.status === 'PREPARING' ? 'En préparation' : campaign.status === 'PLAYING' ? 'En jeu' : 'Archivé'),
|
||||||
|
]),
|
||||||
|
div('flex flex-1 flex-col items-center justify-center', [
|
||||||
div('border border-light-35 dark:border-dark-35 p-1 flex flex-row items-center gap-2', [
|
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(campaign.link) }`) ]),
|
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'),
|
button(icon('radix-icons:clipboard', { width: 16, height: 16 }), () => {}, 'p-1'),
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
div('flex flex-row gap-4 flex-1', [
|
]),
|
||||||
div('flex flex-col w-64', [
|
div('flex flex-row gap-4 flex-1', [
|
||||||
div('flex flex-row items-center gap-4', [ span('text-lg font-bold tracking-tight flex-1', 'Maitre de jeu'), span('border-b border-dashed border-light-35 dark:border-dark-35 flex-1') ]),
|
div('flex flex-col gap-2', [
|
||||||
div('flex flex-col divide-y divide-light-25 dark:divide-dark-25', [
|
div('flex flex-row items-center gap-4 w-[320px]', [ span('font-bold text-lg', 'Etat'), div('border-t border-light-40 dark:border-dark-40 border-dashed flex-1') ]),
|
||||||
div('flex flex-col py-1 my-1', [
|
...this.characters.map(e => e.container),
|
||||||
div('flex flex-row items-center justify-between', [
|
]),
|
||||||
span(undefined, this.dm.user.username),
|
div('flex h-full border-l border-light-40 dark:border-dark-40'),
|
||||||
this.dm.statusDOM,
|
div('flex flex-col w-full max-w-[900px] w-[900px]', [
|
||||||
])
|
tabgroup([
|
||||||
])
|
{ id: 'campaign', title: [ text('Campagne') ], content: () => [
|
||||||
]),
|
markdown(campaign.public_notes, '', { tags: { a: preview } }),
|
||||||
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',
|
{ id: 'inventory', title: [ text('Inventaire') ], content: () => [
|
||||||
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.user.username),
|
{ id: 'logs', title: [ text('Logs') ], content: () => [
|
||||||
player.statusDOM,
|
campaign.logs.length > 0 ? div('flex flex-row ps-12 py-4', [
|
||||||
]),
|
div('border-l-2 border-light-40 dark:border-dark-40'),
|
||||||
player.characters && player.characters.length > 0 ? div('flex flex-col',
|
div('flex flex-col gap-8 py-4', campaign.logs.map(e => div('flex flex-row gap-2 items-center relative -left-4', [
|
||||||
player.characters.map((character) => div('flex flex-row items-center justify-between', [
|
div('w-3 h-3 border-2 rounded-full bg-light-40 dark:border-dark-40 border-light-0 dark:border-dark-0'),
|
||||||
character.container
|
div('flex flex-row items-center', [ svg('svg', { class: ' fill-light-40 dark:fill-dark-40', attributes: { width: "6", height: "9", viewBox: "0 0 6 9" } }, [svg('path', { attributes: { d: "M0 4.5L6 -4.15L6 9L0 4.5Z" } })]), span('px-4 py-2 bg-light-25 dark:bg-dark-25 border border-light-40 dark:border-dark-40', e.details) ]),
|
||||||
]))
|
span('italic text-sm tracking-tight text-light-70 dark:text-dark-70', format(new Date(e.timestamp), 'hh:mm:ss')),
|
||||||
) : 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')],
|
]) : div('flex py-4 px-16', [ span('italic text-light-70 dark:text-darl-70', 'Aucune entrée pour le moment') ])
|
||||||
)
|
] },
|
||||||
]),
|
{ id: 'settings', title: [ text('Paramètres') ], content: () => [
|
||||||
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]')
|
] },
|
||||||
|
{ id: 'ressources', title: [ text('Ressources') ], content: () => [
|
||||||
|
|
||||||
|
] }
|
||||||
|
], { focused: 'campaign', })
|
||||||
])
|
])
|
||||||
]));
|
]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue