Some more stuff
Sorry, I’m in a rush, can’t be bothered to properly commit
This commit is contained in:
parent
dd4ebefedc
commit
2871eec4b5
BIN
public/assets/fonts/gdrico.eot
Normal file
BIN
public/assets/fonts/gdrico.eot
Normal file
Binary file not shown.
26
public/assets/fonts/gdrico.svg
Normal file
26
public/assets/fonts/gdrico.svg
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Generated by IcoMoon</metadata>
|
||||
<defs>
|
||||
<font id="gdrico" horiz-adv-x="1024">
|
||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
||||
<missing-glyph horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" glyph-name="sun" horiz-adv-x="1022" d="M512.032 704c-141.376 0-256-114.624-256-256s114.624-256 256-256c141.376 0 255.968 114.624 255.968 256s-114.592 256-255.968 256v0zM448 896c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64s-64 28.654-64 64zM128 768c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64s-64 28.654-64 64zM64 512c35.36 0 64-28.64 64-64 0-35.424-28.64-64-64-64s-64 28.576-64 64c0 35.36 28.64 64 64 64zM128 128c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64s-64 28.654-64 64zM448 0c0 35.488 28.64 64 64 64 35.456 0 64-28.512 64-64 0-35.264-28.544-64-64-64-35.36 0-64 28.736-64 64zM768 128c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64s-64 28.654-64 64zM960 384c-35.328 0-64 28.672-64 64 0 35.424 28.672 64 64 64s64-28.576 64-64c0-35.328-28.672-64-64-64zM768 768c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64s-64 28.654-64 64z" />
|
||||
<glyph unicode="" glyph-name="moon" d="M788.256 250.112c-262.016 0-474.24 212.384-474.24 474.24 0 86.24 24.736 166.016 64.992 235.616-218.368-62.976-379.008-261.984-379.008-500.608 0-288.992 234.24-523.36 523.264-523.36 238.624 0 437.76 160.736 500.736 379.008-69.76-40.128-149.504-64.896-235.744-64.896z" />
|
||||
<glyph unicode="" glyph-name="image" d="M959.884 832c0.040-0.034 0.082-0.076 0.116-0.116v-767.77c-0.034-0.040-0.076-0.082-0.116-0.116h-895.77c-0.040 0.034-0.082 0.076-0.114 0.116v767.772c0.034 0.040 0.076 0.082 0.114 0.114h895.77zM960 896h-896c-35.2 0-64-28.8-64-64v-768c0-35.2 28.8-64 64-64h896c35.2 0 64 28.8 64 64v768c0 35.2-28.8 64-64 64v0zM832 672c0-53.020-42.98-96-96-96s-96 42.98-96 96 42.98 96 96 96 96-42.98 96-96zM896 128h-768v128l224 384 256-320h64l224 192z" />
|
||||
<glyph unicode="" glyph-name="floppy-disk" d="M896 960h-896v-1024h1024v896l-128 128zM512 832h128v-256h-128v256zM896 64h-768v768h64v-320h576v320h74.978l53.022-53.018v-714.982z" />
|
||||
<glyph unicode="" glyph-name="user" d="M576 253.388v52.78c70.498 39.728 128 138.772 128 237.832 0 159.058 0 288-192 288s-192-128.942-192-288c0-99.060 57.502-198.104 128-237.832v-52.78c-217.102-17.748-384-124.42-384-253.388h896c0 128.968-166.898 235.64-384 253.388z" />
|
||||
<glyph unicode="" glyph-name="users" horiz-adv-x="1152" d="M768 189.388v52.78c70.498 39.728 128 138.772 128 237.832 0 159.058 0 288-192 288s-192-128.942-192-288c0-99.060 57.502-198.104 128-237.832v-52.78c-217.102-17.748-384-124.42-384-253.388h896c0 128.968-166.898 235.64-384 253.388zM327.196 164.672c55.31 36.15 124.080 63.636 199.788 80.414-15.054 17.784-28.708 37.622-40.492 59.020-30.414 55.234-46.492 116.058-46.492 175.894 0 86.042 0 167.31 30.6 233.762 29.706 64.504 83.128 104.496 159.222 119.488-16.914 76.48-61.94 126.75-181.822 126.75-192 0-192-128.942-192-288 0-99.060 57.502-198.104 128-237.832v-52.78c-217.102-17.748-384-124.42-384-253.388h279.006c14.518 12.91 30.596 25.172 48.19 36.672z" />
|
||||
<glyph unicode="" glyph-name="user-tie" d="M320 768c0 106.039 85.961 192 192 192s192-85.961 192-192c0-106.039-85.961-192-192-192s-192 85.961-192 192zM768.078 512h-35.424l-199.104-404.244 74.45 372.244-96 96-96-96 74.45-372.244-199.102 404.244h-35.424c-127.924 0-127.924-85.986-127.924-192v-320h768v320c0 106.014 0 192-127.922 192z" />
|
||||
<glyph unicode="" glyph-name="settings" d="M448 832v16c0 26.4-21.6 48-48 48h-160c-26.4 0-48-21.6-48-48v-16h-192v-128h192v-16c0-26.4 21.6-48 48-48h160c26.4 0 48 21.6 48 48v16h576v128h-576zM256 704v128h128v-128h-128zM832 528c0 26.4-21.6 48-48 48h-160c-26.4 0-48-21.6-48-48v-16h-576v-128h576v-16c0-26.4 21.6-48 48-48h160c26.4 0 48 21.6 48 48v16h192v128h-192v16zM640 384v128h128v-128h-128zM448 208c0 26.4-21.6 48-48 48h-160c-26.4 0-48-21.6-48-48v-16h-192v-128h192v-16c0-26.4 21.6-48 48-48h160c26.4 0 48 21.6 48 48v16h576v128h-576v16zM256 64v128h128v-128h-128z" />
|
||||
<glyph unicode="" glyph-name="bin" d="M192-64h640l64 704h-768zM640 832v128h-256v-128h-320v-192l64 64h768l64-64v192h-320zM576 832h-128v64h128v-64z" />
|
||||
<glyph unicode="" glyph-name="download" d="M736 512l-256-256-256 256h160v384h192v-384zM480 256h-480v-256h960v256h-480zM896 128h-128v64h128v-64z" />
|
||||
<glyph unicode="" glyph-name="upload" d="M480 256h-480v-256h960v256h-480zM896 128h-128v64h128v-64zM224 640l256 256 256-256h-160v-320h-192v320z" />
|
||||
<glyph unicode="" glyph-name="warning" d="M512 867.226l429.102-855.226h-858.206l429.104 855.226zM512 960c-22.070 0-44.14-14.882-60.884-44.648l-437.074-871.112c-33.486-59.532-5-108.24 63.304-108.24h869.308c68.3 0 96.792 48.708 63.3 108.24h0.002l-437.074 871.112c-16.742 29.766-38.812 44.648-60.882 44.648v0zM576 128c0-35.346-28.654-64-64-64s-64 28.654-64 64c0 35.346 28.654 64 64 64s64-28.654 64-64zM512 256c-35.346 0-64 28.654-64 64v192c0 35.346 28.654 64 64 64s64-28.654 64-64v-192c0-35.346-28.654-64-64-64z" />
|
||||
<glyph unicode="" glyph-name="enter" d="M384 448h-320v128h320v128l192-192-192-192zM1024 960v-832l-384-192v192h-384v256h64v-192h320v576l256 128h-576v-256h-64v320z" />
|
||||
<glyph unicode="" glyph-name="exit" d="M768 320v128h-320v128h320v128l192-192zM704 384v-256h-320v-192l-384 192v832h704v-320h-64v256h-512l256-128v-576h256v192z" />
|
||||
<glyph unicode="" glyph-name="back" d="M32 448l480-480v288h512v384h-512v288z" />
|
||||
<glyph unicode="" glyph-name="git" d="M1004.692 493.606l-447.096 447.080c-25.738 25.754-67.496 25.754-93.268 0l-103.882-103.876 78.17-78.17c12.532 5.996 26.564 9.36 41.384 9.36 53.020 0 96-42.98 96-96 0-14.82-3.364-28.854-9.362-41.386l127.976-127.974c12.532 5.996 26.566 9.36 41.386 9.36 53.020 0 96-42.98 96-96s-42.98-96-96-96-96 42.98-96 96c0 14.82 3.364 28.854 9.362 41.386l-127.976 127.974c-3.042-1.456-6.176-2.742-9.384-3.876v-266.968c37.282-13.182 64-48.718 64-90.516 0-53.020-42.98-96-96-96s-96 42.98-96 96c0 41.796 26.718 77.334 64 90.516v266.968c-37.282 13.18-64 48.72-64 90.516 0 14.82 3.364 28.852 9.36 41.384l-78.17 78.17-295.892-295.876c-25.75-25.776-25.75-67.534 0-93.288l447.12-447.080c25.738-25.75 67.484-25.75 93.268 0l445.006 445.006c25.758 25.762 25.758 67.54-0.002 93.29z" />
|
||||
</font></defs></svg>
|
After Width: | Height: | Size: 6.4 KiB |
BIN
public/assets/fonts/gdrico.ttf
Normal file
BIN
public/assets/fonts/gdrico.ttf
Normal file
Binary file not shown.
BIN
public/assets/fonts/gdrico.woff
Normal file
BIN
public/assets/fonts/gdrico.woff
Normal file
Binary file not shown.
@ -18,7 +18,7 @@ main {
|
||||
margin: 5rem;
|
||||
align-content: center;
|
||||
padding-top: 4.5rem !important;
|
||||
padding-bottom: 7rem !important;
|
||||
padding-bottom: 10rem !important;
|
||||
}
|
||||
|
||||
header {
|
||||
|
76
src/assets/_fonts.less
Normal file
76
src/assets/_fonts.less
Normal file
@ -0,0 +1,76 @@
|
||||
@font-face {
|
||||
font-family: 'gdrico';
|
||||
src: url('assets/fonts/gdrico.eot?lbue9x');
|
||||
src:
|
||||
url('assets/fonts/gdrico.eot?lbue9x#iefix') format('embedded-opentype'),
|
||||
url('assets/fonts/gdrico.ttf?lbue9x') format('truetype'),
|
||||
url('assets/fonts/gdrico.woff?lbue9x') format('woff'),
|
||||
url('assets/fonts/gdrico.svg?lbue9x#gdrico') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
|
||||
i {
|
||||
/* use !important to prevent issues with browser extensions that change fonts */
|
||||
font-family: 'gdrico' !important;
|
||||
speak: never;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
|
||||
/* Better Font Rendering =========== */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.gdrico-sun:before {
|
||||
content: '\e900';
|
||||
}
|
||||
.gdrico-moon:before {
|
||||
content: '\e901';
|
||||
}
|
||||
.gdrico-image:before {
|
||||
content: '\e90d';
|
||||
}
|
||||
.gdrico-floppy-disk:before {
|
||||
content: '\e962';
|
||||
}
|
||||
.gdrico-user:before {
|
||||
content: '\e971';
|
||||
}
|
||||
.gdrico-users:before {
|
||||
content: '\e972';
|
||||
}
|
||||
.gdrico-user-tie:before {
|
||||
content: '\e976';
|
||||
}
|
||||
.gdrico-settings:before {
|
||||
content: '\e992';
|
||||
}
|
||||
.gdrico-bin:before {
|
||||
content: '\e9ad';
|
||||
}
|
||||
.gdrico-download:before {
|
||||
content: '\e9c7';
|
||||
}
|
||||
.gdrico-upload:before {
|
||||
content: '\e9c8';
|
||||
}
|
||||
.gdrico-warning:before {
|
||||
content: '\ea07';
|
||||
}
|
||||
.gdrico-enter:before {
|
||||
content: '\ea13';
|
||||
}
|
||||
.gdrico-exit:before {
|
||||
content: '\ea14';
|
||||
}
|
||||
.gdrico-back:before {
|
||||
content: '\ea38';
|
||||
}
|
||||
.gdrico-git:before {
|
||||
content: '\eae7';
|
||||
}
|
@ -17,8 +17,14 @@
|
||||
figure,
|
||||
img {
|
||||
grid-area: figure;
|
||||
max-width: 100%;
|
||||
max-height: 10rem;
|
||||
max-width: min(10rem, 25%);
|
||||
max-height: 100%;
|
||||
border-radius: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 400px) {
|
||||
.two-col-img-text {
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,6 @@
|
||||
.flex-size-even {
|
||||
* {
|
||||
flex: 1;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,3 +90,11 @@
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ul-no-style {
|
||||
list-style: none;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
@ -4,6 +4,11 @@
|
||||
border-radius: 2rem;
|
||||
padding: 2rem;
|
||||
.themed(background-color, @light-background-100, @dark-background-100);
|
||||
.themed(color, @light-text, @dark-text);
|
||||
|
||||
&.less {
|
||||
.themed(background-color, @light-background-50, @dark-background-50);
|
||||
}
|
||||
|
||||
&.more {
|
||||
.themed(background-color, @light-background-200, @dark-background-200);
|
||||
@ -17,6 +22,16 @@
|
||||
&.primary {
|
||||
.themed(background-color, @light-primary, @dark-primary);
|
||||
.themed(color, @light-background, @dark-background);
|
||||
|
||||
&.less {
|
||||
.themed(background-color, @light-primary-600, @dark-primary-600);
|
||||
.themed(color, @light-background-50, @dark-background-50);
|
||||
}
|
||||
|
||||
&.more {
|
||||
.themed(background-color, @light-primary-800, @dark-primary-800);
|
||||
.themed(color, @light-background-100, @dark-background-100);
|
||||
}
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
|
@ -17,39 +17,35 @@ h5 {
|
||||
.title;
|
||||
}
|
||||
|
||||
.tag-font-size(@tag, @base-size, @min-size: 1rem) {
|
||||
.@{tag},
|
||||
@{tag} {
|
||||
font-size: (@base-size * 1rem);
|
||||
}
|
||||
|
||||
@media all and (max-width: 400px) {
|
||||
.@{tag},
|
||||
@{tag} {
|
||||
font-size: max((@base-size * 0.75rem), @min-size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
@h1-size: 4.21rem;
|
||||
.h1,
|
||||
h1 {
|
||||
font-size: @h1-size;
|
||||
}
|
||||
|
||||
@h2-size: 3.158rem;
|
||||
.h2,
|
||||
h2 {
|
||||
font-size: @h2-size;
|
||||
}
|
||||
|
||||
@h3-size: 2.369rem;
|
||||
.h3,
|
||||
h3 {
|
||||
font-size: @h3-size;
|
||||
}
|
||||
|
||||
@h4-size: 1.777rem;
|
||||
.h4,
|
||||
h4 {
|
||||
font-size: @h4-size;
|
||||
}
|
||||
|
||||
@h5-size: 1.333rem;
|
||||
.h5,
|
||||
h5 {
|
||||
font-size: @h5-size;
|
||||
}
|
||||
|
||||
.tag-font-size(h1, @h1-size);
|
||||
.tag-font-size(h2, @h2-size);
|
||||
.tag-font-size(h3, @h3-size);
|
||||
.tag-font-size(h4, @h4-size);
|
||||
.tag-font-size(h5, 1.333rem);
|
||||
|
||||
.small,
|
||||
small {
|
||||
|
0
src/assets/forms.less
Normal file
0
src/assets/forms.less
Normal file
@ -2,6 +2,7 @@
|
||||
@import '@fontsource/roboto';
|
||||
@import '@fontsource/poppins';
|
||||
@import '_theme';
|
||||
@import '_fonts';
|
||||
@import '_mixins';
|
||||
@import '_layouts';
|
||||
|
||||
|
@ -24,6 +24,7 @@ const version = __APP_VERSION__;
|
||||
|
||||
footer {
|
||||
margin: 2rem;
|
||||
margin-top: 2rem;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
@ -2,9 +2,18 @@
|
||||
<header class="flex-row-center flex-spread">
|
||||
<RouterLink class="title h4" :to="{ name: 'home' }">{{ appTitle }}</RouterLink>
|
||||
<div class="buttons gap-1rem">
|
||||
<button @click="toggleDark()" class="secondary">{{ isDark ? 'Sombre' : 'Clair' }}</button>
|
||||
<button @click="toggleDark()" class="secondary">
|
||||
<i v-if="isDark" class="gdrico-moon"></i>
|
||||
<i v-else class="gdrico-sun"></i>
|
||||
</button>
|
||||
|
||||
<button v-if="!loggedIn" @click="login()" class="secondary">Connexion</button>
|
||||
<RouterLink v-else :to="{ name: 'account' }" class="button secondary">Compte</RouterLink>
|
||||
<RouterLink v-else :to="{ name: 'account' }" class="button secondary">
|
||||
<div id="account" class="flex-row">
|
||||
<i class="gdrico-user"></i>
|
||||
<div>Compte</div>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
@ -20,7 +29,6 @@ const appTitle = import.meta.env.VITE_NAME;
|
||||
|
||||
const isDark = useDark();
|
||||
const toggleDark = useToggle(isDark);
|
||||
|
||||
const pbStore = usePocketbaseStore();
|
||||
const loggedIn = ref(pbStore.auth.loggedIn);
|
||||
|
||||
@ -45,4 +53,8 @@ header {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
#account {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
@ -6,13 +6,33 @@
|
||||
<h2 class="card">
|
||||
L'application web pour vous accompagner pour votre JDR avec Gégé, le bot discord.
|
||||
</h2>
|
||||
<div class="flex-col-center flex-size-even gap-1rem">
|
||||
<div>
|
||||
<h3>Pourquoi ?</h3>
|
||||
<ul class="flex-row gap-1rem no-style">
|
||||
<li class="card more text-center">Créer ses personnages</li>
|
||||
<li class="card more text-center">Gérer ses fiche personnage</li>
|
||||
<li class="card more text-center">Faire évoluer ses personnage</li>
|
||||
<ul class="features">
|
||||
<li class="feature">Créer ses personnages</li>
|
||||
<li class="feature">Gérer ses fiche personnage</li>
|
||||
<li class="feature">Faire évoluer ses personnage</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button class="h4 raise margin-3rem">Se connecter avec Discord</button>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import '@/assets/main';
|
||||
|
||||
.features {
|
||||
.flex-row-center;
|
||||
.flex-size-even;
|
||||
.gap-1rem;
|
||||
.ul-no-style;
|
||||
flex-wrap: wrap;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.feature {
|
||||
.card;
|
||||
.more;
|
||||
.text-center;
|
||||
min-width: 15rem;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,22 +1,30 @@
|
||||
<template>
|
||||
<div class="card primary flex-col gap-1rem">
|
||||
<div class="h4" id="name">
|
||||
{{ props.campaign.name }}
|
||||
<RouterLink :to="{ name: 'edit-campaign', params: { campaignId: props.campaign.id } }">
|
||||
<div class="campaign">
|
||||
<div class="campaign-title">
|
||||
{{ props.campaign.name }}
|
||||
</div>
|
||||
<div class="flex-col gamemaster">
|
||||
<div>Maître jeu</div>
|
||||
<div class="user">
|
||||
<UserAvatarAndName :user="props.campaign.expand!.game_master!" :align="'right'" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-col gap-1rem players">
|
||||
<div><i class="gdrico-users"></i> Joueurs</div>
|
||||
<ul class="player-list">
|
||||
<li v-for="player in props.campaign.expand!.players" :key="player.id" class="player">
|
||||
<UserAvatarAndName :user="player" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="dm">
|
||||
{{ displayName(props.campaign.expand!.game_master!) }}
|
||||
</div>
|
||||
<ul id="players" class="no-style">
|
||||
<li v-for="player in props.campaign.expand!.players" :key="player.id">
|
||||
{{ displayName(player) }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import UserAvatarAndName from './UserAvatarAndName.vue';
|
||||
import type { Campaign } from '@/models/Campaign';
|
||||
import { displayName } from '@/models/User';
|
||||
|
||||
const props = defineProps<{
|
||||
campaign: Campaign;
|
||||
@ -24,7 +32,48 @@ const props = defineProps<{
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.card {
|
||||
@import '@/assets/main';
|
||||
|
||||
.campaign {
|
||||
min-width: 20rem;
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
'title gamemaster'
|
||||
'players players';
|
||||
width: 30rem;
|
||||
.card;
|
||||
.more;
|
||||
.gap-1rem;
|
||||
}
|
||||
|
||||
.campaign-title {
|
||||
grid-area: title;
|
||||
.h4;
|
||||
}
|
||||
|
||||
.gamemaster {
|
||||
grid-area: gamemaster;
|
||||
text-align: right;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.players {
|
||||
grid-area: players;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.player-list {
|
||||
flex-wrap: wrap;
|
||||
.ul-no-style;
|
||||
.flex-row;
|
||||
.gap-1rem;
|
||||
.flex-size-even;
|
||||
}
|
||||
|
||||
.player {
|
||||
text-align: center;
|
||||
min-width: 10rem;
|
||||
.card;
|
||||
.primary;
|
||||
}
|
||||
</style>
|
||||
|
41
src/components/UserAvatar.vue
Normal file
41
src/components/UserAvatar.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div v-if="avatar" class="img-container">
|
||||
<img :alt="altText" :src="avatar" :height="size" :width="size" />
|
||||
</div>
|
||||
<i v-else :class="`gdrico-${icon}`"></i>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SimpleUser } from '@/models/User';
|
||||
import { usePocketbaseStore } from '@/stores/pocketbase';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
user: SimpleUser;
|
||||
size?: number; // in px
|
||||
icon?: string;
|
||||
}>();
|
||||
|
||||
const pbStore = usePocketbaseStore();
|
||||
const icon = computed(() => props.icon ?? 'user');
|
||||
const altText = computed(() => 'Avatar de ' + (props.user.name ?? props.user.username));
|
||||
const size = computed(() => props.size ?? 20);
|
||||
const avatar = ref<string | null>(null);
|
||||
|
||||
onMounted(() => {
|
||||
props.user.avatarLink(pbStore).subscribe({
|
||||
next: (link) => (avatar.value = link),
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.img-container {
|
||||
border-radius: 1rem;
|
||||
|
||||
img {
|
||||
object-fit: cover;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
33
src/components/UserAvatarAndName.vue
Normal file
33
src/components/UserAvatarAndName.vue
Normal file
@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div class="flex-row flex-center user" :style="`justify-content: ${align}`">
|
||||
<UserAvatar
|
||||
:user="props.user"
|
||||
:icon="type === 'gamemaster' ? 'user-tie' : 'user'"
|
||||
:size="avatarSize"
|
||||
:style="'width: ${avatarSize}px'" />
|
||||
<span>{{ props.user.displayName() }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import UserAvatar from './UserAvatar.vue';
|
||||
import type { SimpleUser } from '@/models/User';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
user: SimpleUser;
|
||||
type?: 'gamemaster' | 'player';
|
||||
avatarSize?: number;
|
||||
align?: 'right' | 'center' | 'left';
|
||||
}>();
|
||||
|
||||
const type = computed(() => props.type ?? 'player');
|
||||
const avatarSize = computed(() => props.avatarSize ?? 20);
|
||||
const align = computed(() => props.align ?? 'center');
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.user {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
</style>
|
18
src/models/Base.ts
Normal file
18
src/models/Base.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import type { RecordModel } from "pocketbase"
|
||||
|
||||
export class CRecordModel implements RecordModel {
|
||||
[key: string]: any;
|
||||
id: string;
|
||||
created: string;
|
||||
updated: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
|
||||
constructor(from: RecordModel) {
|
||||
this.id = from.id;
|
||||
this.created = from.created;
|
||||
this.updated = from.updated;
|
||||
this.collectionId = from.collectionId;
|
||||
this.collectionName = from.collectionName;
|
||||
}
|
||||
}
|
@ -1,18 +1,49 @@
|
||||
import { type RecordModel } from 'pocketbase';
|
||||
import { type User } from './User';
|
||||
import { User, type IUser } from './User';
|
||||
import { CRecordModel } from './Base';
|
||||
|
||||
interface CampaignDetails {
|
||||
game_master?: User;
|
||||
players?: User[];
|
||||
interface ICampaignDetails {
|
||||
game_master?: IUser;
|
||||
players?: IUser[];
|
||||
}
|
||||
|
||||
export interface Campaign extends RecordModel {
|
||||
export interface ICampaign extends RecordModel {
|
||||
expand?: CampaignDetails;
|
||||
game_master: string;
|
||||
name: string;
|
||||
players: string[];
|
||||
}
|
||||
|
||||
class CampaignDetails implements ICampaignDetails {
|
||||
game_master?: User;
|
||||
players: User[] = [];
|
||||
|
||||
constructor(from: ICampaignDetails) {
|
||||
if (from.game_master) {
|
||||
this.game_master = new User(from.game_master);
|
||||
}
|
||||
if (from.players) {
|
||||
from.players.forEach((player) => this.players.push(new User(player)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Campaign extends CRecordModel implements ICampaign {
|
||||
expand?: CampaignDetails;
|
||||
game_master: string;
|
||||
name: string;
|
||||
players: string[];
|
||||
|
||||
constructor(from: ICampaign) {
|
||||
super(from);
|
||||
this.expand = undefined;
|
||||
this.game_master = from.game_master;
|
||||
this.name = from.name;
|
||||
this.players = from.players;
|
||||
this.expand = from.expand ? new CampaignDetails(from.expand) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export interface NewCampaign {
|
||||
game_master: string | null;
|
||||
name: string | null;
|
||||
|
@ -1,20 +1,58 @@
|
||||
import type { RecordModel } from 'pocketbase';
|
||||
import { type RecordModel } from 'pocketbase';
|
||||
import { CRecordModel } from './Base';
|
||||
import { of, type Observable } from 'rxjs';
|
||||
|
||||
export interface SimpleUser extends RecordModel {
|
||||
export interface ISimpleUser extends RecordModel {
|
||||
username: string;
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
expand?: { [key: string]: any };
|
||||
avatarLink: (pbStore: any) => Observable<string | null>;
|
||||
}
|
||||
|
||||
export interface User extends SimpleUser {
|
||||
avatar?: string;
|
||||
export interface IUser extends SimpleUser {
|
||||
email?: string;
|
||||
emailVisibility: boolean;
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
export function displayName(user: SimpleUser): string {
|
||||
if (user.name && user.name.trim() !== '') {
|
||||
return user.name;
|
||||
export class SimpleUser extends CRecordModel implements ISimpleUser {
|
||||
avatar?: string;
|
||||
username: string;
|
||||
name?: string;
|
||||
expand?: { [key: string]: any };
|
||||
|
||||
constructor(from: ISimpleUser) {
|
||||
super(from);
|
||||
this.username = from.username;
|
||||
this.name = from.name;
|
||||
this.expand = from.expand;
|
||||
this.avatar = from.avatar;
|
||||
}
|
||||
|
||||
displayName(): string {
|
||||
if (this.name && this.name.trim() !== '') {
|
||||
return this.name;
|
||||
}
|
||||
return this.username;
|
||||
}
|
||||
|
||||
avatarLink(pbStore: any, thumbSize: number = 100): Observable<string | null> {
|
||||
return this.avatar
|
||||
? (pbStore.users.avatar(this.id, thumbSize) as Observable<string | null>)
|
||||
: of(null);
|
||||
}
|
||||
}
|
||||
|
||||
export class User extends SimpleUser implements IUser {
|
||||
email?: string;
|
||||
emailVisibility: boolean;
|
||||
verified: boolean;
|
||||
|
||||
constructor(from: IUser) {
|
||||
super(from);
|
||||
this.email = from.email;
|
||||
this.emailVisibility = from.emailVisibility;
|
||||
this.verified = from.verified;
|
||||
}
|
||||
return user.username;
|
||||
}
|
||||
|
@ -16,13 +16,18 @@ const router = createRouter({
|
||||
{
|
||||
path: '/campaigns',
|
||||
name: 'campaigns',
|
||||
component: () => import('@/views/CampaignsView.vue'),
|
||||
component: () => import('@/views/ListCampaignsView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/new-campaign',
|
||||
name: 'new-campaign',
|
||||
component: () => import('@/views/CreateCampaignView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/campaign/:campaignId',
|
||||
name: 'edit-campaign',
|
||||
component: () => import('@/views/CampaignView.vue'),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
@ -3,11 +3,12 @@ import { defineStore } from 'pinia';
|
||||
import { from, map, Observable, tap } from 'rxjs';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import type { Campaign, NewCampaign } from '@/models/Campaign';
|
||||
import type { SimpleUser } from '@/models/User';
|
||||
import { Campaign, type ICampaign, type NewCampaign } from '@/models/Campaign';
|
||||
import { SimpleUser, type IUser } from '@/models/User';
|
||||
|
||||
export const usePocketbaseStore = defineStore('pocketbase', () => {
|
||||
const pb = new PocketBase(import.meta.env.VITE_PB_URL);
|
||||
pb.autoCancellation(false);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Authentication //
|
||||
@ -50,15 +51,23 @@ export const usePocketbaseStore = defineStore('pocketbase', () => {
|
||||
|
||||
function listCampaigns(): Observable<Campaign[]> {
|
||||
return from(
|
||||
pb.collection('campaigns_simple_view').getFullList<Campaign>({
|
||||
pb.collection('campaigns_simple_view').getFullList<ICampaign>({
|
||||
sort: 'name',
|
||||
expand: 'players,game_master',
|
||||
})
|
||||
).pipe(map((campaigns) => campaigns.map((campaign) => new Campaign(campaign))));
|
||||
}
|
||||
|
||||
function createCampaign(campaign: NewCampaign): Observable<Campaign> {
|
||||
return from(pb.collection<ICampaign>('campaign').create(campaign)).pipe(
|
||||
map((campaign) => new Campaign(campaign))
|
||||
);
|
||||
}
|
||||
|
||||
function createCampaign(campaign: NewCampaign): Observable<RecordModel> {
|
||||
return from(pb.collection('campaign').create(campaign));
|
||||
function getCampaignById(id: string): Observable<Campaign> {
|
||||
return from(pb.collection<ICampaign>('campaign').getOne(id)).pipe(
|
||||
map((campaign) => new Campaign(campaign))
|
||||
);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
@ -70,10 +79,21 @@ export const usePocketbaseStore = defineStore('pocketbase', () => {
|
||||
pb.collection('public_users').getFullList<SimpleUser>({
|
||||
sort: 'username',
|
||||
})
|
||||
).pipe(map((users) => users.map((user) => new SimpleUser(user))));
|
||||
}
|
||||
|
||||
function userAvatar(userId: string, thumbSize: number = 100): Observable<string | null> {
|
||||
return from(pb.collection<IUser>('users').getOne(userId)).pipe(
|
||||
map((user) => {
|
||||
return user.avatar
|
||||
? pb.files.getUrl(user, user.avatar, { thumb: `${thumbSize}x${thumbSize}` })
|
||||
: null;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
pb,
|
||||
auth: {
|
||||
authStore,
|
||||
loggedIn,
|
||||
@ -84,12 +104,14 @@ export const usePocketbaseStore = defineStore('pocketbase', () => {
|
||||
logout,
|
||||
deleteAccount,
|
||||
},
|
||||
campaign: {
|
||||
listCampaigns,
|
||||
createCampaign,
|
||||
campaigns: {
|
||||
list: listCampaigns,
|
||||
create: createCampaign,
|
||||
get: getCampaignById,
|
||||
},
|
||||
users: {
|
||||
allUsersSimple,
|
||||
listSimple: allUsersSimple,
|
||||
avatar: userAvatar,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -3,7 +3,7 @@
|
||||
<h1>Hello {{ username }} !</h1>
|
||||
<div>
|
||||
<h2>Actions</h2>
|
||||
<ul class="no-style flex-col-center gap-1rem">
|
||||
<ul class="ul-no-style flex-col-center gap-1rem">
|
||||
<li>
|
||||
<button @click="logout()">Logout</button>
|
||||
</li>
|
||||
|
23
src/views/CampaignView.vue
Normal file
23
src/views/CampaignView.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div id="campaign">
|
||||
<h1>Gestion de campagne</h1>
|
||||
<h2>{{ campaign?.name }}</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Campaign } from '@/models/Campaign';
|
||||
import { usePocketbaseStore } from '@/stores/pocketbase';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const route = useRoute();
|
||||
const pbStore = usePocketbaseStore();
|
||||
const campaign = ref<Campaign>();
|
||||
|
||||
onMounted(() => {
|
||||
pbStore.campaigns.get(route.params.campaignId as string).subscribe({
|
||||
next: (result) => (campaign.value = result),
|
||||
});
|
||||
});
|
||||
</script>
|
@ -11,7 +11,7 @@
|
||||
<label class="flex-col gap-1rem" for="players" autocomplete="off">Joueurs (2 à 10)</label>
|
||||
<select id="players" name="players" multiple v-model="campaign.players">
|
||||
<option v-for="user in users" :key="user.id" :value="user.id">
|
||||
{{ displayName(user) }}
|
||||
{{ user.displayName() }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
@ -27,7 +27,7 @@ import { onMounted, ref } from 'vue';
|
||||
import router from '@/router';
|
||||
import { usePocketbaseStore } from '@/stores/pocketbase';
|
||||
import { type NewCampaign } from '@/models/Campaign';
|
||||
import { type SimpleUser, displayName } from '@/models/User';
|
||||
import { type SimpleUser } from '@/models/User';
|
||||
|
||||
const pbStore = usePocketbaseStore();
|
||||
const users = ref<SimpleUser[]>([]);
|
||||
@ -39,7 +39,7 @@ const campaign = ref<NewCampaign>({
|
||||
});
|
||||
|
||||
const createCampaign = () => {
|
||||
pbStore.campaign.createCampaign(campaign.value).subscribe({
|
||||
pbStore.campaigns.create(campaign.value).subscribe({
|
||||
next: () => {
|
||||
router.push({ name: 'home' });
|
||||
},
|
||||
@ -47,8 +47,10 @@ const createCampaign = () => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
pbStore.users.allUsersSimple().subscribe({
|
||||
next: (results) => (users.value = results.filter((user) => user.id !== pbStore.auth.userId)),
|
||||
pbStore.users.listSimple().subscribe({
|
||||
next: (results) => {
|
||||
users.value = results.filter((user) => user.id !== pbStore.auth.userId);
|
||||
},
|
||||
error: (err) => console.warn('Failed to fetch all users', err),
|
||||
});
|
||||
});
|
||||
|
@ -1,34 +1,26 @@
|
||||
<template>
|
||||
<h1>Campagnes</h1>
|
||||
<div class="flex-col flex-center">
|
||||
<RouterLink :to="{ name: 'new-campaign' }" class="button">Créer une campagne</RouterLink>
|
||||
</div>
|
||||
<div class="flex-col gap-2rem">
|
||||
<div class="flex-col flex-center">
|
||||
<RouterLink :to="{ name: 'new-campaign' }" class="button">Créer une campagne</RouterLink>
|
||||
</div>
|
||||
<h2>Campagnes que je gère</h2>
|
||||
<div>
|
||||
<ul
|
||||
v-if="campaignsGameMaster.length > 0"
|
||||
class="no-style center flex-wrap flex-size-even flex-even gap-1rem">
|
||||
<li v-for="campaign in campaignsGameMaster" :key="campaign.id">
|
||||
<SmallCampaignCard :campaign="campaign" />
|
||||
</li>
|
||||
</ul>
|
||||
<ul v-if="campaignsGameMaster.length > 0" class="campaign-list">
|
||||
<li v-for="campaign in campaignsGameMaster" :key="campaign.id" class="campaign">
|
||||
<SmallCampaignCard :campaign="campaign" />
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div v-else>Pas de campagne pour l’instant</div>
|
||||
</div>
|
||||
<div v-else>Pas de campagne pour l’instant</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Campagnes où je joue</h2>
|
||||
<div>
|
||||
<ul
|
||||
v-if="campaignsPlayer.length > 0"
|
||||
class="no-style center flex-wrap flex-size-even flex-even gap-1rem">
|
||||
<li v-for="campaign in campaignsGameMaster" :key="campaign.id">
|
||||
<SmallCampaignCard :campaign="campaign" />
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else class="card">Pas de campagne pour l’instant</div>
|
||||
</div>
|
||||
<ul v-if="campaignsPlayer.length > 0" class="campaign-list">
|
||||
<li v-for="campaign in campaignsPlayer" :key="campaign.id">
|
||||
<SmallCampaignCard :campaign="campaign" />
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else class="card">Pas de campagne pour l’instant</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -52,10 +44,26 @@ const campaignsPlayer = computed<Campaign[]>(() =>
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
pbStore.campaign.listCampaigns().subscribe({
|
||||
pbStore.campaigns.list().subscribe({
|
||||
next: (result) => (campaigns.value = result),
|
||||
error: (err) => console.warn(err),
|
||||
complete: () => console.log('List campaigns completed'),
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import '@/assets/_mixins';
|
||||
|
||||
.campaign-list {
|
||||
.ul-no-style;
|
||||
.flex-wrap;
|
||||
.flex-row;
|
||||
.gap-2rem;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.campaign {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user