Campaign character insertion and deletion. Updating the inventory rendering. Update of the character_config IDs.
This commit is contained in:
parent
41ae5da98c
commit
b1229f81f6
|
|
@ -159,7 +159,9 @@ export default defineNuxtConfig({
|
|||
"base-uri": "localhost:*"
|
||||
}
|
||||
},
|
||||
xssValidator: false,
|
||||
xssValidator: {
|
||||
escapeHtml: false,
|
||||
},
|
||||
},
|
||||
sitemap: {
|
||||
exclude: ['/admin/**', '/explore/edit', '/user/mailvalidated', '/user/changing-password', '/user/reset-password', '/character/manage', '/campaign/create'],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
import { and, eq, or } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { campaignCharactersTable, campaignMembersTable, campaignTable, characterTable } from '~/db/schema';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const _id = getRouterParam(e, "id");
|
||||
if(!_id)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
const id = parseInt(_id, 10);
|
||||
|
||||
const _campaign_id = getRouterParam(e, "cid");
|
||||
if(!_campaign_id)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
const campaign_id = parseInt(_campaign_id, 10);
|
||||
|
||||
const session = await getUserSession(e);
|
||||
if(!session.user || session.user.state !== 1)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
|
||||
const campaign = db.select({ id: campaignTable.id }).from(campaignMembersTable).innerJoin(campaignTable, eq(campaignMembersTable.id, campaignTable.id)).where(and(eq(campaignMembersTable.id, campaign_id), or(eq(campaignMembersTable.user, session.user.id), eq(campaignTable.owner, session.user.id)))).get();
|
||||
if(!campaign || campaign.id !== campaign_id)
|
||||
return setResponseStatus(e, 404);
|
||||
|
||||
const character = db.select({ id: campaignCharactersTable.character }).from(campaignCharactersTable).where(and(eq(campaignCharactersTable.id, campaign_id), eq(campaignCharactersTable.character, id))).get();
|
||||
if(!character || character.id !== id)
|
||||
return setResponseStatus(e, 403);
|
||||
|
||||
db.delete(campaignCharactersTable).where(and(eq(campaignCharactersTable.id, campaign_id), eq(campaignCharactersTable.character, id))).run();
|
||||
|
||||
setResponseStatus(e, 200);
|
||||
return;
|
||||
});
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import { and, eq, notExists } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { campaignCharactersTable, campaignMembersTable, campaignTable, characterTable } from '~/db/schema';
|
||||
import { CharacterVariablesValidation } from '#shared/character.util';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const _id = getRouterParam(e, "id");
|
||||
if(!_id)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
const id = parseInt(_id, 10);
|
||||
|
||||
const _campaign_id = getRouterParam(e, "cid");
|
||||
if(!_campaign_id)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
const campaign_id = parseInt(_campaign_id, 10);
|
||||
|
||||
const session = await getUserSession(e);
|
||||
if(!session.user || session.user.state !== 1)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
const character = db.select({ id: characterTable.id }).from(characterTable).where(and(eq(characterTable.id, id), eq(characterTable.owner, session.user.id))).get();
|
||||
if(!character || character.id !== id)
|
||||
return setResponseStatus(e, 403);
|
||||
|
||||
const campaign = db.select({ id: campaignMembersTable.id }).from(campaignMembersTable).where(and(eq(campaignMembersTable.id, campaign_id), eq(campaignMembersTable.user, session.user.id))).get();
|
||||
if(!campaign || campaign.id !== campaign_id)
|
||||
return setResponseStatus(e, 404);
|
||||
|
||||
db.insert(campaignCharactersTable).values({
|
||||
id: campaign_id,
|
||||
character: id,
|
||||
}).onConflictDoNothing().run();
|
||||
|
||||
return setResponseStatus(e, 200);
|
||||
});
|
||||
|
|
@ -3,6 +3,10 @@ import type { User } from "~/types/auth";
|
|||
|
||||
export default defineWebSocketHandler({
|
||||
message(peer, message) {
|
||||
const id = new URL(peer.request.url).pathname.split('/').slice(-1)[0];
|
||||
if(!id) return peer.close();
|
||||
|
||||
const topic = `campaigns/${id}`;
|
||||
const data = message.json<SocketMessage>();
|
||||
switch(data.type)
|
||||
{
|
||||
|
|
@ -10,6 +14,10 @@ export default defineWebSocketHandler({
|
|||
peer.send(JSON.stringify({ type: 'PONG' }));
|
||||
return;
|
||||
|
||||
case 'character':
|
||||
peer.publish(topic, data);
|
||||
peer.send(data);
|
||||
|
||||
default: return;
|
||||
}
|
||||
},
|
||||
|
|
@ -23,14 +31,14 @@ export default defineWebSocketHandler({
|
|||
|
||||
const topic = `campaigns/${id}`;
|
||||
peer.subscribe(topic);
|
||||
peer.publish(topic, { type: 'user', data: [{ user: (peer.context.user as User).id, status: true }] });
|
||||
peer.send({ type: 'user', data: peer.peers.values().filter(e => e.topics.has(topic)).map(e => ({ user: (e.context.user as User).id, status: true })).toArray() })
|
||||
peer.publish(topic, { type: 'status', data: [{ user: (peer.context.user as User).id, status: true }] });
|
||||
peer.send({ type: 'status', data: peer.peers.values().filter(e => e.topics.has(topic)).map(e => ({ user: (e.context.user as User).id, status: true })).toArray() })
|
||||
},
|
||||
close(peer, details) {
|
||||
const id = new URL(peer.request.url).pathname.split('/').slice(-1)[0];
|
||||
if(!id) return peer.close();
|
||||
|
||||
peer.publish(`campaigns/${id}`, { type: 'user', data: [{ user: (peer.context.user as User).id, status: false }] });
|
||||
peer.publish(`campaigns/${id}`, { type: 'status', data: [{ user: (peer.context.user as User).id, status: false }] });
|
||||
peer.unsubscribe(`campaigns/${id}`);
|
||||
}
|
||||
});
|
||||
|
|
@ -2,9 +2,9 @@ import { z } from "zod/v4";
|
|||
import type { User } from "~/types/auth";
|
||||
import type { Campaign, CampaignLog } from "~/types/campaign";
|
||||
import { div, dom, icon, span, svg, text } from "#shared/dom.util";
|
||||
import { button, loading, tabgroup } from "#shared/components.util";
|
||||
import { button, loading, tabgroup, Toaster } from "#shared/components.util";
|
||||
import { CharacterCompiler } from "#shared/character.util";
|
||||
import { tooltip } from "#shared/floating.util";
|
||||
import { modal, tooltip } from "#shared/floating.util";
|
||||
import markdown from "#shared/markdown.util";
|
||||
import { preview } from "#shared/proses";
|
||||
import { format } from "#shared/general.util";
|
||||
|
|
@ -86,6 +86,9 @@ export class CampaignSheet
|
|||
private dm!: PlayerState;
|
||||
private players!: Array<PlayerState>;
|
||||
private characters!: Array<CharacterPrinter>;
|
||||
private characterList!: HTMLElement;
|
||||
|
||||
private tab: string = 'campaign';
|
||||
|
||||
ws?: Socket;
|
||||
|
||||
|
|
@ -102,7 +105,7 @@ export class CampaignSheet
|
|||
this.players = campaign.members.map(e => defaultPlayerState(e.member));
|
||||
this.characters = campaign.characters.map(e => new CharacterPrinter(e.character!.id, e.character!.name));
|
||||
this.ws = new Socket(`/ws/campaign/${id}`, true);
|
||||
this.ws.handleMessage<{ user: number, status: boolean }[]>('user', (users) => {
|
||||
this.ws.handleMessage<{ user: number, status: boolean }[]>('status', (users) => {
|
||||
users.forEach(user => {
|
||||
if(this.dm.user.id === user.user)
|
||||
{
|
||||
|
|
@ -121,6 +124,30 @@ export class CampaignSheet
|
|||
}
|
||||
})
|
||||
});
|
||||
this.ws.handleMessage<{ id: number, name: string, action: 'ADD' | 'REMOVE' }>('character', (character) => {
|
||||
if(character.action === 'ADD')
|
||||
{
|
||||
const printer = new CharacterPrinter(character.id, character.name);
|
||||
this.characters.push(printer);
|
||||
this.characterList.appendChild(printer.container);
|
||||
}
|
||||
else if(character.action === 'REMOVE')
|
||||
{
|
||||
const idx = this.characters.findIndex(e => e.compiler?.character.id !== character.id);
|
||||
|
||||
if(idx !== -1)
|
||||
{
|
||||
this.characters[idx]!.container.remove();
|
||||
this.characters.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.ws.handleMessage<{ id: number, name: string, action: 'ADD' | 'REMOVE' }>('player', () => {
|
||||
this.render();
|
||||
});
|
||||
this.ws.handleMessage<void>('hardsync', () => {
|
||||
this.render();
|
||||
});
|
||||
|
||||
document.title = `d[any] - Campagne ${campaign.name}`;
|
||||
this.render();
|
||||
|
|
@ -151,6 +178,7 @@ export class CampaignSheet
|
|||
if(!campaign)
|
||||
return;
|
||||
|
||||
this.characterList = div('flex flex-col gap-2', this.characters.map(e => e.container));
|
||||
this.container.replaceChildren(div('grid grid-cols-3 gap-2', [
|
||||
div('flex flex-row gap-2 items-center py-2', [
|
||||
this.dm.dom,
|
||||
|
|
@ -171,7 +199,31 @@ export class CampaignSheet
|
|||
div('flex flex-row gap-4 flex-1 h-0', [
|
||||
div('flex flex-col gap-2', [
|
||||
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') ]),
|
||||
...this.characters.map(e => e.container),
|
||||
this.characterList,
|
||||
div('px-8 py-4 w-full flex', [
|
||||
button([
|
||||
icon('radix-icons:plus-circled', { width: 24, height: 24 }),
|
||||
span('text-sm', 'Ajouter un personnage'),
|
||||
], () => {
|
||||
const load = loading('normal');
|
||||
let characters: HTMLElement[] = [];
|
||||
const close = modal([
|
||||
div('flex flex-col gap-4 items-center min-w-[480px] min-h-24', [
|
||||
span('text-xl font-bold', 'Mes personnages'),
|
||||
load,
|
||||
]),
|
||||
], { closeWhenOutside: true, priority: true, class: { container: 'max-w-[560px]' } }).close;
|
||||
useRequestFetch()(`/api/character`).then((list) => {
|
||||
characters = list?.map(e => div('border border-light-40 dark:border-dark-40 p-2 flex flex-col w-[140px]', [
|
||||
span('font-bold', e.name),
|
||||
span('', `Niveau ${e.level}`),
|
||||
button(text('Ajouter'), () => useRequestFetch()(`/api/character/${e.id}/campaign/${this.campaign!.id}`, { method: 'POST' }).then(() => this.ws!.send('character', { id: e.id, name: e.name, action: 'ADD', })).catch(e => Toaster.add({ duration: 15000, content: e.message ?? e, title: 'Une erreur est survenue', type: 'error' })).finally(close)),
|
||||
])) ?? [];
|
||||
}).catch(e => Toaster.add({ duration: 15000, content: e.message ?? e, title: 'Une erreur est survenue', type: 'error' })).finally(() => {
|
||||
load.replaceWith(div('grid grid-cols-3 gap-2', characters.length > 0 ? characters : [span('text-light-60 dark:text-dark-60 text-sm italic', 'Vous n\'avez pas de personnage disponible')]));
|
||||
});
|
||||
}, 'flex flex-col flex-1 gap-2 p-4 items-center justify-center text-light-60 dark:text-dark-60'),
|
||||
])
|
||||
]),
|
||||
div('flex h-full border-l border-light-40 dark:border-dark-40'),
|
||||
div('flex flex-col', [
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1303,6 +1303,7 @@ export class CharacterSheet
|
|||
private character?: CharacterCompiler;
|
||||
container: HTMLElement = div('flex flex-1 h-full w-full items-start justify-center');
|
||||
private tabs?: HTMLDivElement & { refresh: () => void };
|
||||
private tab: string = 'abilities';
|
||||
|
||||
ws?: Socket;
|
||||
constructor(id: string, user: ComputedRef<User | null>)
|
||||
|
|
@ -1405,7 +1406,7 @@ export class CharacterSheet
|
|||
div('flex flex-col gap-2', [ span('text-lg font-bold', 'Notes privés'), div('border border-light-35 dark:border-dark-35 bg-light20 dark:bg-dark-20 p-1 h-64', [ privateNotes.dom ]) ]),
|
||||
])
|
||||
] },
|
||||
], { focused: 'abilities', class: { container: 'flex-1 gap-4 px-4 w-[960px] h-full', content: 'overflow-auto' } });
|
||||
], { focused: this.tab, class: { container: 'flex-1 gap-4 px-4 w-[960px] h-full', content: 'overflow-auto' }, switch: v => { this.tab = v; } });
|
||||
this.container.replaceChildren(div('flex flex-col justify-start gap-1 h-full', [
|
||||
div("flex flex-row gap-4 justify-between", [
|
||||
div(),
|
||||
|
|
@ -1753,43 +1754,53 @@ export class CharacterSheet
|
|||
{
|
||||
let debounceId: NodeJS.Timeout | undefined;
|
||||
//TODO: Recompile values on "equip" checkbox change
|
||||
const items = (character.variables.items.map(e => ({ ...e, item: config.items[e.id] })).filter(e => !!e.item) as Array<ItemState & { item: ItemConfig }>).map(e => div('flex flex-row justify-between', [
|
||||
div('flex flex-row items-center gap-4', [
|
||||
div('flex flex-col gap-1', [ e.item.equippable ? checkbox({ defaultValue: e.equipped, change: v => {
|
||||
e.equipped = v;
|
||||
const items = (character.variables.items.map(e => ({ ...e, item: config.items[e.id], ref: e })).filter(e => !!e.item) as Array<ItemState & { item: ItemConfig, ref: ItemState }>).map(e => {
|
||||
const price = div(['flex flex-row min-w-16 gap-2 justify-between items-center px-2', { 'cursor-help': e.amount > 1 }], [ icon('ph:coin', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span({ 'underline decoration-1 decoration-dotted underline-offset-2': e.amount > 1 }, e.item.price ? `${e.item.price * e.amount}` : '-') ]);
|
||||
const weight = div(['flex flex-row min-w-16 gap-2 justify-between items-center px-2', { 'cursor-help': e.amount > 1 }], [ icon('mdi:weight', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span({ 'underline decoration-1 decoration-dotted underline-offset-2': e.amount > 1 }, e.item.weight ? `${e.item.weight * e.amount}` : '-') ]);
|
||||
return foldable(() => [
|
||||
markdown(getText(e.item.description)),
|
||||
div('flex flex-row justify-center', [
|
||||
this.character?.character.campaign ? button(text('Partager'), () => {
|
||||
|
||||
this.character!.variable('items', this.character!.character.variables.items);
|
||||
}, 'p-1') : undefined,
|
||||
button(icon('radix-icons:trash'), () => {
|
||||
const idx = this.character!.character.variables.items.findIndex(_e => _e.id === e.id);
|
||||
if(idx === -1) return;
|
||||
|
||||
debounceId && clearTimeout(debounceId);
|
||||
debounceId = setTimeout(() => this.character?.saveVariables(), 2000);
|
||||
this.character!.character.variables.items[idx]!.amount--;
|
||||
if(this.character!.character.variables.items[idx]!.amount >= 0) this.character!.character.variables.items.splice(idx, 1);
|
||||
|
||||
this.tabs?.refresh();
|
||||
}, class: { container: '!w-5 !h-5' } }) : checkbox({ disabled: true, class: { container: '!w-5 !h-5' } }), button(icon('radix-icons:trash', { width: 16, height: 17 }), () => {
|
||||
const idx = this.character!.character.variables.items.findIndex(_e => _e.id === e.id);
|
||||
if(idx === -1) return;
|
||||
this.character!.variable('items', this.character!.character.variables.items);
|
||||
|
||||
this.character!.character.variables.items[idx]!.amount--;
|
||||
if(this.character!.character.variables.items[idx]!.amount >= 0) this.character!.character.variables.items.splice(idx, 1);
|
||||
debounceId && clearTimeout(debounceId);
|
||||
debounceId = setTimeout(() => this.character?.saveVariables(), 2000);
|
||||
|
||||
this.character!.variable('items', this.character!.character.variables.items);
|
||||
this.tabs?.refresh();
|
||||
}, 'p-1'),
|
||||
]) ], [div('flex flex-row justify-between', [
|
||||
div('flex flex-row items-center gap-4', [
|
||||
e.item.equippable ? checkbox({ defaultValue: e.equipped, change: v => {
|
||||
e.equipped = e.ref.equipped = v;
|
||||
|
||||
debounceId && clearTimeout(debounceId);
|
||||
debounceId = setTimeout(() => this.character?.saveVariables(), 2000);
|
||||
this.character!.variable('items', this.character!.character.variables.items);
|
||||
|
||||
this.tabs?.refresh();
|
||||
}, 'p-px') ]),
|
||||
div('flex flex-col gap-1', [ span([colorByRarity[e.item.rarity], 'text-lg'], e.item.name), div('flex flex-row gap-4 text-light-60 dark:text-dark-60 text-sm italic', subnameFactory(e.item, e).map(text)) ]),
|
||||
]),
|
||||
div('grid grid-cols-2 row-gap-2 col-gap-8', [
|
||||
div('flex flex-row w-16 gap-2 justify-between items-center', [ icon('game-icons:bolt-drop', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('flex-1', (e.item.powercost || (e.enchantments && e.enchantments.length > 0)) && e.item.capacity ? `${(e.item?.powercost ?? 0) + (e.enchantments?.reduce((p, v) => (config.enchantments[v]?.power ?? 0) + p, 0) ?? 0)}/${e.item.capacity}` : '-') ]),
|
||||
div('flex flex-row w-16 gap-2 justify-between items-center', [ icon('mdi:weight', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('flex-1', e.item.weight?.toString() ?? '-') ]),
|
||||
div('flex flex-row w-16 gap-2 justify-between items-center', [ icon('game-icons:battery-pack', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('flex-1', e.charges && e.item.charge ? `${e.charges}/${e.item.charge}` : '-') ]),
|
||||
div('flex flex-row w-16 gap-2 justify-between items-center', [ icon('radix-icons:cross-2', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('flex-1', e.amount?.toString() ?? '-') ])
|
||||
])
|
||||
]));
|
||||
debounceId && clearTimeout(debounceId);
|
||||
debounceId = setTimeout(() => this.character?.saveVariables(), 2000);
|
||||
}, class: { container: '!w-5 !h-5' } }) : undefined,
|
||||
div('flex flex-row items-center gap-4', [ span([colorByRarity[e.item.rarity], 'text-lg'], e.item.name), div('flex flex-row gap-2 text-light-60 dark:text-dark-60 text-sm italic', subnameFactory(e.item).map(e => span('', e))) ]),
|
||||
]),
|
||||
div('flex flex-row items-center divide-x divide-light-50 dark:divide-dark-50 divide-dashed px-2', [
|
||||
e.amount > 1 ? tooltip(price, `Prix unitaire: ${e.item.price}`, 'bottom') : price,
|
||||
div('flex flex-row min-w-16 gap-2 justify-between items-center px-2', [ icon('radix-icons:cross-2', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', e.amount?.toString() ?? '-') ]),
|
||||
div('flex flex-row min-w-16 gap-2 justify-between items-center px-2', [ icon('game-icons:bolt-drop', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', e.item.powercost || e.item.capacity ? `${e.item.powercost ?? 0}/${e.item.capacity ?? 0}` : '-') ]),
|
||||
e.amount > 1 ? tooltip(weight, `Poids unitaire: ${e.item.weight}`, 'bottom') : weight,
|
||||
div('flex flex-row min-w-16 gap-2 justify-between items-center px-2', [ icon('game-icons:battery-pack', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', e.item.charge ? `${e.item.charge}` : '-') ]),
|
||||
]),
|
||||
])], { open: false, class: { icon: 'px-2', container: 'p-1 gap-2', content: 'px-4 pb-1 flex flex-col' } })
|
||||
});
|
||||
|
||||
const power = character.variables.items.filter(e => config.items[e.id]?.equippable && e.equipped).reduce((p, v) => p + (config.items[v.id]?.powercost ?? 0) + (v.enchantments?.reduce((_p, _v) => (config.enchantments[_v]?.power ?? 0) + _p, 0) ?? 0), 0);
|
||||
const weight = character.variables.items.reduce((p, v) => p + (config.items[v.id]?.weight ?? 0), 0);
|
||||
const power = character.variables.items.filter(e => config.items[e.id]?.equippable && e.equipped).reduce((p, v) => p + ((config.items[v.id]?.powercost ?? 0) + (v.enchantments?.reduce((_p, _v) => (config.enchantments[_v]?.power ?? 0) + _p, 0) ?? 0) * v.amount), 0);
|
||||
const weight = character.variables.items.reduce((p, v) => p + (config.items[v.id]?.weight ?? 0) * v.amount, 0);
|
||||
|
||||
return [
|
||||
div('flex flex-col gap-2', [
|
||||
|
|
@ -1798,7 +1809,7 @@ export class CharacterSheet
|
|||
dom('span', { class: ['italic text-sm', { 'text-light-red dark:text-dark-red': power > (character.capacity === false ? 0 : character.capacity) }], text: `Puissance magique: ${power}/${character.capacity}` }),
|
||||
button(text('Modifier'), () => this.itemsPanel(character), 'py-1 px-4'),
|
||||
]),
|
||||
div('grid grid-cols-2 flex-1 gap-4', items)
|
||||
div('flex flex-col flex-1 divide-y divide-light-35 dark:divide-dark-35', items)
|
||||
])
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export function async(size: 'small' | 'normal' | 'large' = 'normal', fn: Promise
|
|||
|
||||
return state;
|
||||
}
|
||||
export function button(content: Node, onClick?: (this: HTMLElement) => void, cls?: Class)
|
||||
export function button(content: Node | NodeChildren, onClick?: (this: HTMLElement) => void, cls?: Class)
|
||||
{
|
||||
/*
|
||||
text-light-100 dark:text-dark-100 font-semibold hover:bg-light-30 dark:hover:bg-dark-30 inline-flex items-center justify-center bg-light-25 dark:bg-dark-25 leading-none outline-none
|
||||
|
|
@ -46,7 +46,7 @@ export function button(content: Node, onClick?: (this: HTMLElement) => void, cls
|
|||
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
|
||||
hover:bg-light-25 dark:hover:bg-dark-25 hover:border-light-50 dark:hover:border-dark-50
|
||||
focus:bg-light-30 dark:focus:bg-dark-30 focus:border-light-50 dark:focus:border-dark-50 focus:shadow-raw focus:shadow-light-50 dark:focus:shadow-dark-50
|
||||
disabled:text-light-50 dark:disabled:text-dark-50 disabled:bg-light-10 dark:disabled:bg-dark-10 disabled:border-dashed disabled:border-light-40 dark:disabled:border-dark-40`, cls], listeners: { click: () => disabled || (onClick && onClick.bind(btn)()) } }, [ content ]);
|
||||
disabled:text-light-50 dark:disabled:text-dark-50 disabled:bg-light-10 dark:disabled:bg-dark-10 disabled:border-dashed disabled:border-light-40 dark:disabled:border-dark-40`, cls], listeners: { click: () => disabled || (onClick && onClick.bind(btn)()) } }, Array.isArray(content) ? content : [content]);
|
||||
let disabled = false;
|
||||
Object.defineProperty(btn, 'disabled', {
|
||||
get: () => disabled,
|
||||
|
|
@ -495,13 +495,16 @@ export function checkbox(settings?: { defaultValue?: boolean, change?: (this: HT
|
|||
}, [ icon('radix-icons:check', { width: 14, height: 14, class: ['hidden group-data-[state="checked"]:block data-[disabled]:text-light-50 dark:data-[disabled]:text-dark-50', settings?.class?.icon] }), ]);
|
||||
return element;
|
||||
}
|
||||
export function tabgroup(tabs: Array<{ id: string, title: NodeChildren, content: NodeChildren | (() => NodeChildren) }>, settings?: { focused?: string, class?: { container?: Class, tabbar?: Class, title?: Class, content?: Class } }): HTMLDivElement & { refresh: () => void }
|
||||
export function tabgroup(tabs: Array<{ id: string, title: NodeChildren, content: NodeChildren | (() => NodeChildren) }>, settings?: { focused?: string, class?: { container?: Class, tabbar?: Class, title?: Class, content?: Class }, switch?: (tab: string) => void | boolean }): HTMLDivElement & { refresh: () => void }
|
||||
{
|
||||
let focus = settings?.focused ?? tabs[0]?.id;
|
||||
const titles = tabs.map((e, i) => dom('div', { class: ['px-2 py-1 border-b border-transparent hover:border-accent-blue data-[focus]:border-accent-blue data-[focus]:border-b-[3px] cursor-pointer', settings?.class?.title], attributes: { 'data-focus': e.id === focus }, listeners: { click: function() {
|
||||
const titles = tabs.map(e => dom('div', { class: ['px-2 py-1 border-b border-transparent hover:border-accent-blue data-[focus]:border-accent-blue data-[focus]:border-b-[3px] cursor-pointer', settings?.class?.title], attributes: { 'data-focus': e.id === focus }, listeners: { click: function() {
|
||||
if(this.hasAttribute('data-focus'))
|
||||
return;
|
||||
|
||||
if(settings?.switch && settings.switch(e.id) === false)
|
||||
return;
|
||||
|
||||
titles.forEach(e => e.toggleAttribute('data-focus', false));
|
||||
this.toggleAttribute('data-focus', true);
|
||||
focus = e.id;
|
||||
|
|
@ -716,7 +719,7 @@ export class Toaster
|
|||
|
||||
static init()
|
||||
{
|
||||
Toaster._container = dom('div', { attributes: { id: 'toaster' }, class: 'fixed bottom-0 right-0 flex flex-col p-6 gap-2 max-w-[512px] z-50 outline-none min-w-72' });
|
||||
Toaster._container = dom('div', { attributes: { id: 'toaster' }, class: 'fixed bottom-0 right-0 flex flex-col p-6 gap-2 max-w-[512px] z-50 outline-none min-w-72 empty:hidden' });
|
||||
document.body.appendChild(Toaster._container);
|
||||
}
|
||||
static add(_config: ToastConfig)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ type Listener<K extends keyof HTMLElementEventMap> = | ((this: HTMLElement, ev:
|
|||
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any;
|
||||
} | undefined;
|
||||
|
||||
export type DOMList = Node[] & {
|
||||
remove(predicate: (item: Node, index: number, array: Node[]) => boolean): Node[];
|
||||
};
|
||||
|
||||
export interface NodeProperties
|
||||
{
|
||||
attributes?: Record<string, string | undefined | boolean | number>;
|
||||
|
|
@ -21,7 +25,7 @@ export interface NodeProperties
|
|||
}
|
||||
|
||||
export const cancelPropagation = (e: Event) => e.stopImmediatePropagation();
|
||||
export function dom<K extends keyof HTMLElementTagNameMap>(tag: K, properties?: NodeProperties, children?: NodeChildren): HTMLElementTagNameMap[K]
|
||||
export function dom<K extends keyof HTMLElementTagNameMap>(tag: K, properties?: NodeProperties, children?: NodeChildren | DOMList): HTMLElementTagNameMap[K]
|
||||
{
|
||||
const element = document.createElement(tag);
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ export class HomebrewBuilder
|
|||
{
|
||||
this._config = config as CharacterConfig;
|
||||
this._featureEditor = new FeaturePanel();
|
||||
ItemPanel.config = this._config;
|
||||
this._container = container;
|
||||
|
||||
this._tabs = tabgroup([
|
||||
|
|
@ -665,7 +664,7 @@ export class FeaturePanel
|
|||
const _feature = JSON.parse(JSON.stringify(feature)) as Feature;
|
||||
const effectContainer = div('grid grid-cols-2 gap-4 px-2', _feature.effect.map(e => new FeatureEditor(_feature.effect!, e.id, false).container));
|
||||
MarkdownEditor.singleton.content = getText(_feature.description);
|
||||
MarkdownEditor.singleton.onChange = (value) => ItemPanel.config.texts[_feature.description]!.default = value;
|
||||
MarkdownEditor.singleton.onChange = (value) => config.texts[_feature.description]!.default = value;
|
||||
return dom('div', { attributes: { 'data-state': 'inactive' }, class: 'border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 border-l absolute top-0 bottom-0 right-0 w-[10%] data-[state=active]:w-1/2 flex flex-col gap-2 text-light-100 dark:text-dark-100 p-8 transition-[width] transition-delay-[150ms]' }, [
|
||||
div('flex flex-row justify-between items-center', [
|
||||
tooltip(button(icon('radix-icons:check', { width: 20, height: 20 }), () => {
|
||||
|
|
@ -722,12 +721,11 @@ export class FeaturePanel
|
|||
}
|
||||
export class ItemPanel
|
||||
{
|
||||
static config: CharacterConfig;
|
||||
static render(item: ItemConfig, success: (item: ItemConfig) => void, failure: (item: ItemConfig) => void)
|
||||
{
|
||||
const _item = JSON.parse(JSON.stringify(item)) as ItemConfig;
|
||||
MarkdownEditor.singleton.content = getText(_item.description);
|
||||
MarkdownEditor.singleton.onChange = (value) => ItemPanel.config.texts[_item.description]!.default = value;
|
||||
MarkdownEditor.singleton.onChange = (value) => config.texts[_item.description]!.default = value;
|
||||
const effectContainer = div('grid grid-cols-2 gap-4 px-2 flex-1', _item.effects?.map(e => new FeatureEditor(_item.effects!, e.id, false).container));
|
||||
return dom('div', { attributes: { 'data-state': 'inactive' }, class: 'border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 border-l absolute top-0 bottom-0 right-0 w-[10%] data-[state=active]:w-1/2 flex flex-col gap-2 text-light-100 dark:text-dark-100 p-8 transition-[width] transition-delay-[150ms]' }, [
|
||||
div('flex flex-row justify-between items-center', [
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export interface PopperProperties extends FloatingProperties
|
|||
export interface ModalProperties
|
||||
{
|
||||
priority?: boolean;
|
||||
class?: { blocker?: Class, popup?: Class },
|
||||
closeWhenOutside?: boolean;
|
||||
onClose?: () => boolean | void;
|
||||
}
|
||||
|
|
@ -381,16 +382,16 @@ export function fullblocker(content: NodeChildren, properties?: ModalProperties)
|
|||
return { close: () => {} };
|
||||
|
||||
const close = () => (!properties?.onClose || properties.onClose() !== false) && _modal.remove();
|
||||
const _modalBlocker = dom('div', { class: [' absolute top-0 left-0 bottom-0 right-0 z-0', { 'bg-light-0 dark:bg-dark-0 opacity-70': properties?.priority ?? false }], listeners: { click: properties?.closeWhenOutside ? (close) : undefined } });
|
||||
const _modal = dom('div', { class: 'fixed flex justify-center items-center top-0 left-0 bottom-0 right-0 inset-0 z-40' }, [ _modalBlocker, ...content]);
|
||||
const _modalBlocker = dom('div', { class: [' absolute top-0 left-0 bottom-0 right-0 z-0', { 'bg-light-0 dark:bg-dark-0 opacity-70': properties?.priority ?? false }, properties?.class?.blocker], listeners: { click: properties?.closeWhenOutside ? (close) : undefined } });
|
||||
const _modal = dom('div', { class: ['fixed flex justify-center items-center top-0 left-0 bottom-0 right-0 inset-0 z-40', properties?.class?.blocker] }, [ _modalBlocker, ...content]);
|
||||
|
||||
teleport.appendChild(_modal);
|
||||
|
||||
return { close };
|
||||
}
|
||||
export function modal(content: NodeChildren, properties?: ModalProperties)
|
||||
export function modal(content: NodeChildren, properties?: ModalProperties & { class?: { container?: Class } })
|
||||
{
|
||||
return fullblocker([ dom('div', { class: 'max-h-[85vh] max-w-[450px] bg-light-10 dark:bg-dark-10 border border-light-30 dark:border-dark-30 p-6 text-light-100 dark:text-dark-100 z-10 relative' }, content) ], properties);
|
||||
return fullblocker([ dom('div', { class: ['max-h-[85vh] max-w-[450px] bg-light-10 dark:bg-dark-10 border border-light-30 dark:border-dark-30 p-6 text-light-100 dark:text-dark-100 z-10 relative', properties?.class?.container] }, content) ], properties);
|
||||
}
|
||||
|
||||
export function confirm(title: string): Promise<boolean>
|
||||
|
|
|
|||
|
|
@ -61,6 +61,10 @@ export class Socket
|
|||
{
|
||||
this._handlers.set(type, callback);
|
||||
}
|
||||
public send(type: string, data: any)
|
||||
{
|
||||
this._ws.readyState === WebSocket.OPEN && this._ws.send(JSON.stringify({ type, data }));
|
||||
}
|
||||
public close()
|
||||
{
|
||||
this._ws.close(1000);
|
||||
|
|
|
|||
Loading…
Reference in New Issue