Add public characters and visibility flag
This commit is contained in:
parent
1ee895ab42
commit
871861e66e
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
BIN
db.sqlite-wal
BIN
db.sqlite-wal
Binary file not shown.
|
|
@ -59,6 +59,7 @@ export const characterTable = sqliteTable("character", {
|
||||||
owner: int().notNull().references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
owner: int().notNull().references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||||
progress: text({ mode: 'json' }).notNull(),
|
progress: text({ mode: 'json' }).notNull(),
|
||||||
values: text({ mode: 'json' }).notNull().default({}),
|
values: text({ mode: 'json' }).notNull().default({}),
|
||||||
|
visibility: text({ enum: ['private', 'public'] }).notNull().default('private'),
|
||||||
thumbnail: blob(),
|
thumbnail: blob(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE `character` ADD `visibility` text DEFAULT 'private' NOT NULL;
|
||||||
|
|
@ -0,0 +1,434 @@
|
||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "bffde16c-d716-40ec-9d92-cb49814815d7",
|
||||||
|
"prevId": "eb68cf2f-c7e2-4111-910d-a26b0fc438cc",
|
||||||
|
"tables": {
|
||||||
|
"character": {
|
||||||
|
"name": "character",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"name": "owner",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"progress": {
|
||||||
|
"name": "progress",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"values": {
|
||||||
|
"name": "values",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'{}'"
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"name": "visibility",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'private'"
|
||||||
|
},
|
||||||
|
"thumbnail": {
|
||||||
|
"name": "thumbnail",
|
||||||
|
"type": "blob",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"character_owner_users_id_fk": {
|
||||||
|
"name": "character_owner_users_id_fk",
|
||||||
|
"tableFrom": "character",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"owner"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"email_validation": {
|
||||||
|
"name": "email_validation",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"name": "timestamp",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"explorer_content": {
|
||||||
|
"name": "explorer_content",
|
||||||
|
"columns": {
|
||||||
|
"path": {
|
||||||
|
"name": "path",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"name": "owner",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"name": "content",
|
||||||
|
"type": "blob",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"navigable": {
|
||||||
|
"name": "navigable",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"private": {
|
||||||
|
"name": "private",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"order": {
|
||||||
|
"name": "order",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"visit": {
|
||||||
|
"name": "visit",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"name": "timestamp",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"explorer_content_owner_users_id_fk": {
|
||||||
|
"name": "explorer_content_owner_users_id_fk",
|
||||||
|
"tableFrom": "explorer_content",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"owner"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"user_permissions": {
|
||||||
|
"name": "user_permissions",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"permission": {
|
||||||
|
"name": "permission",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"user_permissions_id_users_id_fk": {
|
||||||
|
"name": "user_permissions_id_users_id_fk",
|
||||||
|
"tableFrom": "user_permissions",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"user_permissions_id_permission_pk": {
|
||||||
|
"columns": [
|
||||||
|
"id",
|
||||||
|
"permission"
|
||||||
|
],
|
||||||
|
"name": "user_permissions_id_permission_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"user_sessions": {
|
||||||
|
"name": "user_sessions",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"name": "timestamp",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"user_sessions_user_id_users_id_fk": {
|
||||||
|
"name": "user_sessions_user_id_users_id_fk",
|
||||||
|
"tableFrom": "user_sessions",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"user_sessions_id_user_id_pk": {
|
||||||
|
"columns": [
|
||||||
|
"id",
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"name": "user_sessions_id_user_id_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"users_data": {
|
||||||
|
"name": "users_data",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"signin": {
|
||||||
|
"name": "signin",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"lastTimestamp": {
|
||||||
|
"name": "lastTimestamp",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"logCount": {
|
||||||
|
"name": "logCount",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"users_data_id_users_id_fk": {
|
||||||
|
"name": "users_data_id_users_id_fk",
|
||||||
|
"tableFrom": "users_data",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "users",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"hash": {
|
||||||
|
"name": "hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"name": "state",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"users_username_unique": {
|
||||||
|
"name": "users_username_unique",
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"users_email_unique": {
|
||||||
|
"name": "users_email_unique",
|
||||||
|
"columns": [
|
||||||
|
"email"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"users_hash_unique": {
|
||||||
|
"name": "users_hash_unique",
|
||||||
|
"columns": [
|
||||||
|
"hash"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -64,6 +64,13 @@
|
||||||
"when": 1745675022171,
|
"when": 1745675022171,
|
||||||
"tag": "0008_glorious_johnny_blaze",
|
"tag": "0008_glorious_johnny_blaze",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 9,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1745920443528,
|
||||||
|
"tag": "0009_thin_omega_sentinel",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -15,8 +15,7 @@
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-8 max-md:hidden">
|
<div class="flex items-center gap-8 max-md:hidden">
|
||||||
<Tooltip message="Developpement en cours" side="bottom"><NuxtLink href="#" class="text-light-70 dark:text-dark-70">Parcourir les projets</NuxtLink></Tooltip>
|
<NuxtLink :href="{ name: 'character' }" class="text-light-70 dark:text-dark-70" active-class="!text-accent-blue"><span class="pl-3 py-1 flex-1 truncate">Mes personnages</span></NuxtLink>
|
||||||
<Tooltip message="Developpement en cours" side="bottom"><NuxtLink href="#" class="text-light-70 dark:text-dark-70">Créer du contenu</NuxtLink></Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center px-2 gap-4">
|
<div class="flex items-center px-2 gap-4">
|
||||||
<template v-if="!loggedIn">
|
<template v-if="!loggedIn">
|
||||||
|
|
@ -33,9 +32,6 @@
|
||||||
<div class="bg-light-0 dark:bg-dark-0 z-40 w-screen md:w-[18rem] border-r border-light-30 dark:border-dark-30 flex flex-col justify-between my-2 max-md:data-[state=closed]:hidden">
|
<div class="bg-light-0 dark:bg-dark-0 z-40 w-screen md:w-[18rem] border-r border-light-30 dark:border-dark-30 flex flex-col justify-between my-2 max-md:data-[state=closed]:hidden">
|
||||||
<div class="flex-1 px-2 max-w-full max-h-full overflow-y-auto overflow-x-hidden">
|
<div class="flex-1 px-2 max-w-full max-h-full overflow-y-auto overflow-x-hidden">
|
||||||
<div v-if="user" class="flex flex-1 py-4 px-2 flex-row flex-1 justify-between items-center">
|
<div v-if="user" class="flex flex-1 py-4 px-2 flex-row flex-1 justify-between items-center">
|
||||||
<NuxtLink :href="{ name: 'character' }" class="flex flex-1 font-bold text-lg items-center border-light-35 dark:border-dark-35 hover:border-accent-blue" active-class="text-accent-blue border-s-2 !border-accent-blue">
|
|
||||||
<span class="pl-3 py-1 flex-1 truncate">Mes personnages</span>
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink v-if="hasPermissions(user.permissions, ['admin', 'editor'])" :to="{ name: 'explore-edit' }"><Button icon><Icon icon="radix-icons:pencil-2" /></Button></NuxtLink>
|
<NuxtLink v-if="hasPermissions(user.permissions, ['admin', 'editor'])" :to="{ name: 'explore-edit' }"><Button icon><Icon icon="radix-icons:pencil-2" /></Button></NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
<Tree v-if="pages" v-model="pages" :getKey="(item) => item.path" class="ps-4">
|
<Tree v-if="pages" v-model="pages" :getKey="(item) => item.path" class="ps-4">
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,12 @@ const data = ref<Character>({
|
||||||
spells: [],
|
spells: [],
|
||||||
notes: "",
|
notes: "",
|
||||||
},
|
},
|
||||||
|
values: {
|
||||||
|
hp: 0,
|
||||||
|
armor: 0,
|
||||||
|
mana: 0,
|
||||||
|
},
|
||||||
|
visibility: "private"
|
||||||
});
|
});
|
||||||
const spellFilter = ref<{
|
const spellFilter = ref<{
|
||||||
ranks: Array<1 | 2 | 3>,
|
ranks: Array<1 | 2 | 3>,
|
||||||
|
|
@ -111,6 +117,8 @@ if(id !== 'new')
|
||||||
data.value = { name: character.name, progress: Object.assign(data.value.progress, character.progress) } as Character;
|
data.value = { name: character.name, progress: Object.assign(data.value.progress, character.progress) } as Character;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(data.value.progress);
|
||||||
|
|
||||||
function selectRaceOption(level: Level, choice: number)
|
function selectRaceOption(level: Level, choice: number)
|
||||||
{
|
{
|
||||||
const character = data.value;
|
const character = data.value;
|
||||||
|
|
@ -265,6 +273,13 @@ useShortcuts({
|
||||||
<NumberFieldInput class="tabular-nums w-20 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
|
<NumberFieldInput class="tabular-nums w-20 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
|
||||||
</NumberFieldRoot>
|
</NumberFieldRoot>
|
||||||
</Label>
|
</Label>
|
||||||
|
<Label class="flex items-start justify-between flex-col gap-2">
|
||||||
|
<span class="pb-1 mx-6 md:p-0">Visibilité</span>
|
||||||
|
<Select class="!my-0" v-model="data.visibility">
|
||||||
|
<SelectItem label="Privé" value="private" />
|
||||||
|
<SelectItem label="Public" value="public" />
|
||||||
|
</Select>
|
||||||
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<div class="self-center">
|
<div class="self-center">
|
||||||
<Tooltip side="right" message="Ctrl+S"><Button @click="() => save(true)">Enregistrer</Button></Tooltip>
|
<Tooltip side="right" message="Ctrl+S"><Button @click="() => save(true)">Enregistrer</Button></Tooltip>
|
||||||
|
|
@ -277,7 +292,7 @@ useShortcuts({
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<div class="m-2 overflow-auto">
|
<div class="m-2 overflow-auto">
|
||||||
<Combobox label="Peuple de votre personnage" :v-model="data.progress.race.index!" :default-value="data.progress.race.index" :options="config.peoples.map((people, index) => [people.name, index])" @update:model-value="(index) => { data.progress.race.index = index as number | undefined; data.progress.race.progress = [[1, 0]]}" />
|
<Combobox label="Peuple de votre personnage" v-model="data.progress.race.index" :options="config.peoples.map((people, index) => [people.name, index])" @update:model-value="(index) => { data.progress.race.index = index as number | undefined; data.progress.race.progress = [[1, 0]]}" />
|
||||||
<template v-if="data.progress.race.index !== undefined">
|
<template v-if="data.progress.race.index !== undefined">
|
||||||
<div class="w-full border-b border-light-30 dark:border-dark-30 pb-4">
|
<div class="w-full border-b border-light-30 dark:border-dark-30 pb-4">
|
||||||
<span class="text-sm text-light-70 dark:text-dark-70">{{ characterConfig.peoples[data.progress.race.index].description }}</span>
|
<span class="text-sm text-light-70 dark:text-dark-70">{{ characterConfig.peoples[data.progress.race.index].description }}</span>
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,27 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Progression } from '~/types/character';
|
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
guestsGoesTo: '/user/login',
|
guestsGoesTo: '/user/login',
|
||||||
})
|
})
|
||||||
const { add } = useToast();
|
const { add } = useToast();
|
||||||
const { user } = useUserSession();
|
const { user } = useUserSession();
|
||||||
const loading = ref(true);
|
|
||||||
const characters = ref<Array<{ id: number, name: string, progress: Progression }>>([]);
|
const { data: characters, error, status } = await useFetch(`/api/character`);
|
||||||
characters.value = await useRequestFetch()('/api/character');
|
|
||||||
loading.value = false;
|
|
||||||
|
|
||||||
async function deleteCharacter(id: number)
|
async function deleteCharacter(id: number)
|
||||||
{
|
{
|
||||||
loading.value = true;
|
status.value = "pending";
|
||||||
await useRequestFetch()(`/api/character/${id}`, { method: 'delete' });
|
await useRequestFetch()(`/api/character/${id}`, { method: 'delete' });
|
||||||
loading.value = false;
|
status.value = "success";
|
||||||
add({ content: 'Personnage supprimé', type: 'info', duration: 25000, timer: true, });
|
add({ content: 'Personnage supprimé', type: 'info', duration: 25000, timer: true, });
|
||||||
characters.value = characters.value?.filter(e => e.id !== id);
|
characters.value = characters.value?.filter(e => e.id !== id);
|
||||||
}
|
}
|
||||||
async function duplicateCharacter(id: number)
|
async function duplicateCharacter(id: number)
|
||||||
{
|
{
|
||||||
loading.value = true;
|
status.value = "pending";
|
||||||
const newId = await useRequestFetch()(`/api/character/${id}/duplicate`, { method: 'post' });
|
const newId = await useRequestFetch()(`/api/character/${id}/duplicate`, { method: 'post' });
|
||||||
loading.value = false;
|
status.value = "success";
|
||||||
add({ content: 'Personnage dupliqué', type: 'info', duration: 25000, timer: true, });
|
add({ content: 'Personnage dupliqué', type: 'info', duration: 25000, timer: true, });
|
||||||
useRouter().push({ name: 'character-id', params: { id: newId } });
|
useRouter().push({ name: 'character-id', params: { id: newId } });
|
||||||
}
|
}
|
||||||
|
|
@ -34,22 +32,47 @@ async function duplicateCharacter(id: number)
|
||||||
<Title>d[any] - Mes personnages</Title>
|
<Title>d[any] - Mes personnages</Title>
|
||||||
</Head>
|
</Head>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<NuxtLink v-if="user?.state === 1" :to="{ name: 'character-id-edit', params: { id: 'new' } }" class="flex align-center justify-center"><Button>Nouveau personnage</Button></NuxtLink>
|
<div class="flex align-center justify-center">
|
||||||
|
<NuxtLink v-if="user?.state === 1" :to="{ name: 'character-id-edit', params: { id: 'new' } }"><Button>Nouveau personnage</Button></NuxtLink>
|
||||||
<Tooltip v-else side="top" message="Veuillez valider votre email avant de pouvoir créer un personnage."><Button disabled>Nouveau personnage</Button></Tooltip>
|
<Tooltip v-else side="top" message="Veuillez valider votre email avant de pouvoir créer un personnage."><Button disabled>Nouveau personnage</Button></Tooltip>
|
||||||
<div v-if="loading" class="flex flex-1 justify-center align-center">
|
</div>
|
||||||
|
<div v-if="status === 'pending'" class="flex flex-1 justify-center align-center">
|
||||||
<Loading size="large" />
|
<Loading size="large" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="grid p-6 grid-cols-4 gap-4">
|
<div v-else-if="status === 'success'" class="grid p-6 2xl:grid-cols-3 lg:grid-cols-2 grid-cols-1 gap-4 w-full">
|
||||||
<div class="border border-light-30 dark:border-dark-30 p-1 flex flex-row gap-4" v-for="character of characters">
|
<div class="border border-light-30 dark:border-dark-30 p-3 flex flex-row gap-4" v-for="character of characters">
|
||||||
<Avatar size="large" icon="radix-icons:person" src="" class="m-2" />
|
<Avatar size="large" icon="radix-icons:person" src="" />
|
||||||
<div class="flex flex-col justify-between w-64">
|
<div class="flex flex-1 flex-shrink flex-col truncate">
|
||||||
<NuxtLink class="flex-1 text-xl font-bold hover:text-accent-blue truncate" :to="{ name: 'character-id', params: { id: character.id } }" :title="character.name">{{ character.name }}</NuxtLink>
|
<NuxtLink class="text-xl font-bold hover:text-accent-blue truncate" :to="{ name: 'character-id', params: { id: character.id } }" :title="character.name">{{ character.name }}</NuxtLink>
|
||||||
<span class="flex-1 text-sm truncate">Niveau {{ character.progress.level }}</span>
|
<span class="text-sm truncate">Niveau {{ character.progress.level }}</span>
|
||||||
<div class="flex flex-row gap-8">
|
</div>
|
||||||
<NuxtLink class="text-accent-blue hover:text-opacity-50" :to="{ name: 'character-id-edit', params: { id: character.id } }">Editer</NuxtLink>
|
|
||||||
<span class="text-accent-blue hover:text-opacity-50 cursor-pointer" @click="duplicateCharacter(character.id)">Dupliquer</span>
|
|
||||||
<AlertDialogRoot>
|
<AlertDialogRoot>
|
||||||
<AlertDialogTrigger asChild><span class="text-light-red dark:text-dark-red hover:text-opacity-50 cursor-pointer">Supprimer</span></AlertDialogTrigger>
|
<DropdownMenuRoot>
|
||||||
|
<DropdownMenuTrigger class="self-start">
|
||||||
|
<Button icon><Icon icon="radix-icons:dots-vertical" /></Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
|
||||||
|
<DropdownMenuPortal>
|
||||||
|
<DropdownMenuContent align="end" side="bottom" class="z-50 outline-none bg-light-20 dark:bg-dark-20 will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade border border-light-35 dark:border-dark-35">
|
||||||
|
<DropdownMenuItem @select="useRouter().push({ name: 'character-id-edit', params: { id: character.id } })" class="cursor-pointer text-base text-light-100 dark:text-dark-100 leading-none flex items-baseline py-1.5 relative ps-7 pe-4 select-none outline-none data-[disabled]:text-light-60 dark:data-[disabled]:text-dark-60 data-[disabled]:pointer-events-none data-[highlighted]:bg-light-35 dark:data-[highlighted]:bg-dark-35">
|
||||||
|
<Icon icon="radix-icons:pencil-1" class="absolute left-1.5" />
|
||||||
|
<span>Editer</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem @select="duplicateCharacter(character.id)" class="cursor-pointer text-base text-light-100 dark:text-dark-100 leading-none flex items-center py-1.5 relative ps-7 pe-4 select-none outline-none data-[disabled]:text-light-60 dark:data-[disabled]:text-dark-60 data-[disabled]:pointer-events-none data-[highlighted]:bg-light-35 dark:data-[highlighted]:bg-dark-35">
|
||||||
|
<Icon icon="radix-icons:clipboard-copy" class="absolute left-1.5" />
|
||||||
|
<span>Dupliquer</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<AlertDialogTrigger>
|
||||||
|
<DropdownMenuItem class="cursor-pointer text-base text-light-red dark:text-dark-red leading-none flex items-center py-1.5 relative ps-7 pe-4 select-none outline-none data-[disabled]:text-light-60 dark:data-[disabled]:text-dark-60 data-[disabled]:pointer-events-none data-[highlighted]:bg-light-red dark:data-[highlighted]:bg-dark-red data-[highlighted]:bg-opacity-30 dark:data-[highlighted]:bg-opacity-30">
|
||||||
|
<Icon icon="radix-icons:trash" class="absolute left-1.5" />
|
||||||
|
<span>Supprimer</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
|
||||||
|
<DropdownMenuArrow class="fill-light-35 dark:fill-dark-35" />
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenuPortal>
|
||||||
|
</DropdownMenuRoot>
|
||||||
<AlertDialogPortal>
|
<AlertDialogPortal>
|
||||||
<AlertDialogOverlay class="bg-light-0 dark:bg-dark-0 opacity-70 fixed inset-0 z-40" />
|
<AlertDialogOverlay class="bg-light-0 dark:bg-dark-0 opacity-70 fixed inset-0 z-40" />
|
||||||
<AlertDialogContent
|
<AlertDialogContent
|
||||||
|
|
@ -64,7 +87,9 @@ async function duplicateCharacter(id: number)
|
||||||
</AlertDialogRoot>
|
</AlertDialogRoot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div v-else>
|
||||||
|
<span>Erreur de chargement</span>
|
||||||
|
<span>{{ error?.message }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { data: characters, error, status } = await useFetch(`/api/character`, { params: { visibility: "public" } });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Head>
|
||||||
|
<Title>d[any] - Liste des personnages</Title>
|
||||||
|
</Head>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div v-if="status === 'pending'" class="flex flex-1 justify-center align-center">
|
||||||
|
<Loading size="large" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="status === 'success'" class="grid p-6 2xl:grid-cols-3 lg:grid-cols-2 grid-cols-1 gap-4 w-full">
|
||||||
|
<div class="border border-light-30 dark:border-dark-30 p-3 flex flex-row gap-4" v-for="character of characters">
|
||||||
|
<Avatar size="large" icon="radix-icons:person" src="" />
|
||||||
|
<div class="flex flex-1 flex-shrink flex-col truncate">
|
||||||
|
<NuxtLink class="text-xl font-bold hover:text-accent-blue truncate" :to="{ name: 'character-id', params: { id: character.id } }" :title="character.name">{{ character.name }}</NuxtLink>
|
||||||
|
<span class="text-sm truncate">Niveau {{ character.progress.level }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<span>Erreur de chargement</span>
|
||||||
|
<span>{{ error?.message }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -61,7 +61,7 @@ export default defineEventHandler(async (e): Promise<Return> => {
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const hash = await Bun.password.hash(body.data.password);
|
const hash = await Bun.password.hash(body.data.password);
|
||||||
db.insert(usersTable).values({ username: sql.placeholder('username'), email: sql.placeholder('email'), hash: sql.placeholder('hash'), state: sql.placeholder('state') }).prepare().run({ username: body.data.username, email: body.data.email, hash, state: 1 });
|
db.insert(usersTable).values({ username: sql.placeholder('username'), email: sql.placeholder('email'), hash: sql.placeholder('hash'), state: sql.placeholder('state') }).prepare().run({ username: body.data.username, email: body.data.email, hash, state: 0 });
|
||||||
const id = db.select({ id: usersTable.id }).from(usersTable).where(eq(usersTable.username, sql.placeholder('username'))).prepare().get({ username: body.data.username });
|
const id = db.select({ id: usersTable.id }).from(usersTable).where(eq(usersTable.username, sql.placeholder('username'))).prepare().get({ username: body.data.username });
|
||||||
|
|
||||||
if(!id || !id.id)
|
if(!id || !id.id)
|
||||||
|
|
@ -72,7 +72,7 @@ export default defineEventHandler(async (e): Promise<Return> => {
|
||||||
|
|
||||||
db.insert(usersDataTable).values({ id: sql.placeholder('id') }).prepare().run({ id: id.id });
|
db.insert(usersDataTable).values({ id: sql.placeholder('id') }).prepare().run({ id: id.id });
|
||||||
|
|
||||||
logSession(e, await setUserSession(e, { user: { id: id.id, username: body.data.username, email: body.data.email, state: 1, signin: new Date(), permissions: [], lastTimestamp: new Date(), logCount: 1 } }) as UserSessionRequired);
|
logSession(e, await setUserSession(e, { user: { id: id.id, username: body.data.username, email: body.data.email, state: 0, signin: new Date(), permissions: [], lastTimestamp: new Date(), logCount: 1 } }) as UserSessionRequired);
|
||||||
|
|
||||||
const emailId = Bun.hash('register' + id.id + hash, Date.now());
|
const emailId = Bun.hash('register' + id.id + hash, Date.now());
|
||||||
const timestamp = Date.now() + 1000 * 60 * 60;
|
const timestamp = Date.now() + 1000 * 60 * 60;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,20 @@
|
||||||
import { and, eq, sql } from 'drizzle-orm';
|
import { and, eq, sql } from 'drizzle-orm';
|
||||||
import useDatabase from '~/composables/useDatabase';
|
import useDatabase from '~/composables/useDatabase';
|
||||||
import { characterTable } from '~/db/schema';
|
import { characterTable, userPermissionsTable } from '~/db/schema';
|
||||||
|
import { hasPermissions } from '~/shared/auth.util';
|
||||||
import type { Character } from '~/types/character';
|
import type { Character } from '~/types/character';
|
||||||
|
|
||||||
export default defineEventHandler(async (e) => {
|
export default defineEventHandler(async (e) => {
|
||||||
const session = await getUserSession(e);
|
let { visibility } = getQuery(e) as { visibility?: "public" | "own" | "admin" };
|
||||||
|
|
||||||
|
if(!visibility)
|
||||||
|
{
|
||||||
|
visibility = "own";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(visibility === "own")
|
||||||
|
{
|
||||||
|
const session = await getUserSession(e);
|
||||||
if(!session.user)
|
if(!session.user)
|
||||||
{
|
{
|
||||||
setResponseStatus(e, 401);
|
setResponseStatus(e, 401);
|
||||||
|
|
@ -13,15 +22,61 @@ export default defineEventHandler(async (e) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = useDatabase();
|
const db = useDatabase();
|
||||||
const character = db.select({
|
const characters = db.select({
|
||||||
id: characterTable.id,
|
id: characterTable.id,
|
||||||
name: characterTable.name,
|
name: characterTable.name,
|
||||||
progress: characterTable.progress,
|
progress: characterTable.progress,
|
||||||
|
visibility: characterTable.visibility,
|
||||||
}).from(characterTable).where(eq(characterTable.owner, session.user.id)).all();
|
}).from(characterTable).where(eq(characterTable.owner, session.user.id)).all();
|
||||||
|
|
||||||
if(character !== undefined)
|
if(characters !== undefined)
|
||||||
{
|
{
|
||||||
return character as Character[];
|
return characters as Character[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(visibility === 'public')
|
||||||
|
{
|
||||||
|
const db = useDatabase();
|
||||||
|
const characters = db.select({
|
||||||
|
id: characterTable.id,
|
||||||
|
name: characterTable.name,
|
||||||
|
progress: characterTable.progress,
|
||||||
|
visibility: characterTable.visibility,
|
||||||
|
}).from(characterTable).where(eq(characterTable.visibility, "public")).all();
|
||||||
|
|
||||||
|
if(characters !== undefined)
|
||||||
|
{
|
||||||
|
return characters as Character[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(visibility === 'admin')
|
||||||
|
{
|
||||||
|
const session = await getUserSession(e);
|
||||||
|
if(!session.user)
|
||||||
|
{
|
||||||
|
setResponseStatus(e, 401);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const db = useDatabase();
|
||||||
|
|
||||||
|
const rights = db.select({ right: userPermissionsTable.permission }).from(userPermissionsTable).where(eq(userPermissionsTable.id, session.user.id)).all();
|
||||||
|
if(rights.length === 0 || !hasPermissions(rights.map(e => e.right), ['admin']))
|
||||||
|
{
|
||||||
|
setResponseStatus(e, 403);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const characters = db.select({
|
||||||
|
id: characterTable.id,
|
||||||
|
name: characterTable.name,
|
||||||
|
progress: characterTable.progress,
|
||||||
|
visibility: characterTable.visibility,
|
||||||
|
}).from(characterTable).all();
|
||||||
|
|
||||||
|
if(characters !== undefined)
|
||||||
|
{
|
||||||
|
return characters as Character[];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setResponseStatus(e, 404);
|
setResponseStatus(e, 404);
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,8 @@ export default defineEventHandler(async (e) => {
|
||||||
id: characterTable.id,
|
id: characterTable.id,
|
||||||
name: characterTable.name,
|
name: characterTable.name,
|
||||||
progress: characterTable.progress,
|
progress: characterTable.progress,
|
||||||
owner: characterTable.owner
|
owner: characterTable.owner,
|
||||||
|
visibility: characterTable.visibility,
|
||||||
}).from(characterTable).where(and(eq(characterTable.id, id), eq(characterTable.owner, session.user.id))).get();
|
}).from(characterTable).where(and(eq(characterTable.id, id), eq(characterTable.owner, session.user.id))).get();
|
||||||
|
|
||||||
if(character !== undefined)
|
if(character !== undefined)
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ export const elementTexts: Record<SpellElement, { class: string, text: string }>
|
||||||
light: { class: 'text-light-yellow dark:text-dark-yellow', text: 'Lumière' },
|
light: { class: 'text-light-yellow dark:text-dark-yellow', text: 'Lumière' },
|
||||||
psyche: { class: 'text-light-purple dark:text-dark-purple', text: 'Psy' },
|
psyche: { class: 'text-light-purple dark:text-dark-purple', text: 'Psy' },
|
||||||
}
|
}
|
||||||
export const spellTypeTexts: Record<SpellType, string> = { "instinct": "Instinct", "knowledge": "Savoir", "precision": "Précision" };
|
export const spellTypeTexts: Record<SpellType, string> = { "instinct": "Instinct", "knowledge": "Savoir", "precision": "Précision", "arts": "Oeuvres" };
|
||||||
|
|
||||||
export type Progression = {
|
export type Progression = {
|
||||||
training: Record<MainStat, DoubleIndex<TrainingLevel>[]>;
|
training: Record<MainStat, DoubleIndex<TrainingLevel>[]>;
|
||||||
|
|
@ -50,6 +50,7 @@ export type Character = {
|
||||||
progress: Progression;
|
progress: Progression;
|
||||||
values: CharacterValues;
|
values: CharacterValues;
|
||||||
owner?: number;
|
owner?: number;
|
||||||
|
visibility: "private" | "public";
|
||||||
};
|
};
|
||||||
export type CharacterValues = {
|
export type CharacterValues = {
|
||||||
hp: number;
|
hp: number;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue