diff --git a/public/assets/fonts/gdrico.eot b/public/assets/fonts/gdrico.eot
new file mode 100644
index 0000000..9e91735
Binary files /dev/null and b/public/assets/fonts/gdrico.eot differ
diff --git a/public/assets/fonts/gdrico.svg b/public/assets/fonts/gdrico.svg
new file mode 100644
index 0000000..7544752
--- /dev/null
+++ b/public/assets/fonts/gdrico.svg
@@ -0,0 +1,26 @@
+
+
+
\ No newline at end of file
diff --git a/public/assets/fonts/gdrico.ttf b/public/assets/fonts/gdrico.ttf
new file mode 100644
index 0000000..2f4f4ef
Binary files /dev/null and b/public/assets/fonts/gdrico.ttf differ
diff --git a/public/assets/fonts/gdrico.woff b/public/assets/fonts/gdrico.woff
new file mode 100644
index 0000000..8af4ae1
Binary files /dev/null and b/public/assets/fonts/gdrico.woff differ
diff --git a/src/App.vue b/src/App.vue
index 58763c1..239a592 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -18,7 +18,7 @@ main {
margin: 5rem;
align-content: center;
padding-top: 4.5rem !important;
- padding-bottom: 7rem !important;
+ padding-bottom: 10rem !important;
}
header {
diff --git a/src/assets/_fonts.less b/src/assets/_fonts.less
new file mode 100644
index 0000000..0a3b944
--- /dev/null
+++ b/src/assets/_fonts.less
@@ -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';
+}
diff --git a/src/assets/_layouts.less b/src/assets/_layouts.less
index a3d050c..60943e3 100644
--- a/src/assets/_layouts.less
+++ b/src/assets/_layouts.less
@@ -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;
+ }
+}
diff --git a/src/assets/_mixins.less b/src/assets/_mixins.less
index 687b484..bfccb07 100644
--- a/src/assets/_mixins.less
+++ b/src/assets/_mixins.less
@@ -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;
+}
diff --git a/src/assets/components/cards.less b/src/assets/components/cards.less
index 96a51db..b2f99d2 100644
--- a/src/assets/components/cards.less
+++ b/src/assets/components/cards.less
@@ -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 {
diff --git a/src/assets/components/titles.less b/src/assets/components/titles.less
index 4cb492b..0861a01 100644
--- a/src/assets/components/titles.less
+++ b/src/assets/components/titles.less
@@ -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 {
diff --git a/src/assets/forms.less b/src/assets/forms.less
new file mode 100644
index 0000000..e69de29
diff --git a/src/assets/main.less b/src/assets/main.less
index 23c485b..a4f373a 100644
--- a/src/assets/main.less
+++ b/src/assets/main.less
@@ -2,6 +2,7 @@
@import '@fontsource/roboto';
@import '@fontsource/poppins';
@import '_theme';
+@import '_fonts';
@import '_mixins';
@import '_layouts';
diff --git a/src/components/AppFooter.vue b/src/components/AppFooter.vue
index 2b90f1d..edfc86c 100644
--- a/src/components/AppFooter.vue
+++ b/src/components/AppFooter.vue
@@ -24,6 +24,7 @@ const version = __APP_VERSION__;
footer {
margin: 2rem;
+ margin-top: 2rem;
position: absolute;
left: 0;
right: 0;
diff --git a/src/components/AppHeader.vue b/src/components/AppHeader.vue
index 481e0ef..1b938ac 100644
--- a/src/components/AppHeader.vue
+++ b/src/components/AppHeader.vue
@@ -2,9 +2,18 @@
@@ -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;
+}
diff --git a/src/components/LoggedOutHome.vue b/src/components/LoggedOutHome.vue
index 4eb4412..2956775 100644
--- a/src/components/LoggedOutHome.vue
+++ b/src/components/LoggedOutHome.vue
@@ -6,13 +6,33 @@
L'application web pour vous accompagner pour votre JDR avec Gégé, le bot discord.
-
+
Pourquoi ?
-
- - Créer ses personnages
- - Gérer ses fiche personnage
- - Faire évoluer ses personnage
+
+ - Créer ses personnages
+ - Gérer ses fiche personnage
+ - Faire évoluer ses personnage
+
+
diff --git a/src/components/SmallCampaignCard.vue b/src/components/SmallCampaignCard.vue
index 6935257..42d5fc3 100644
--- a/src/components/SmallCampaignCard.vue
+++ b/src/components/SmallCampaignCard.vue
@@ -1,22 +1,30 @@
-
-
- {{ props.campaign.name }}
+
+
+
+ {{ props.campaign.name }}
+
+
+
-
- {{ displayName(props.campaign.expand!.game_master!) }}
-
-
- -
- {{ displayName(player) }}
-
-
-
+
diff --git a/src/components/UserAvatar.vue b/src/components/UserAvatar.vue
new file mode 100644
index 0000000..99c4246
--- /dev/null
+++ b/src/components/UserAvatar.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/UserAvatarAndName.vue b/src/components/UserAvatarAndName.vue
new file mode 100644
index 0000000..a30ebd7
--- /dev/null
+++ b/src/components/UserAvatarAndName.vue
@@ -0,0 +1,33 @@
+
+
+
+ {{ props.user.displayName() }}
+
+
+
+
+
+
diff --git a/src/models/Base.ts b/src/models/Base.ts
new file mode 100644
index 0000000..07cf24d
--- /dev/null
+++ b/src/models/Base.ts
@@ -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;
+ }
+}
diff --git a/src/models/Campaign.ts b/src/models/Campaign.ts
index 49bca5d..d9f89f0 100644
--- a/src/models/Campaign.ts
+++ b/src/models/Campaign.ts
@@ -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;
diff --git a/src/models/User.ts b/src/models/User.ts
index dd46889..5af1089 100644
--- a/src/models/User.ts
+++ b/src/models/User.ts
@@ -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
;
}
-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 {
+ return this.avatar
+ ? (pbStore.users.avatar(this.id, thumbSize) as Observable)
+ : 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;
}
diff --git a/src/router/index.ts b/src/router/index.ts
index 7c0dcdb..dd12e5f 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -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'),
+ },
],
});
diff --git a/src/stores/pocketbase.ts b/src/stores/pocketbase.ts
index f7ffa3e..918e346 100644
--- a/src/stores/pocketbase.ts
+++ b/src/stores/pocketbase.ts
@@ -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 {
return from(
- pb.collection('campaigns_simple_view').getFullList({
+ pb.collection('campaigns_simple_view').getFullList({
sort: 'name',
expand: 'players,game_master',
})
+ ).pipe(map((campaigns) => campaigns.map((campaign) => new Campaign(campaign))));
+ }
+
+ function createCampaign(campaign: NewCampaign): Observable {
+ return from(pb.collection('campaign').create(campaign)).pipe(
+ map((campaign) => new Campaign(campaign))
);
}
- function createCampaign(campaign: NewCampaign): Observable {
- return from(pb.collection('campaign').create(campaign));
+ function getCampaignById(id: string): Observable {
+ return from(pb.collection('campaign').getOne(id)).pipe(
+ map((campaign) => new Campaign(campaign))
+ );
}
/////////////////////////////////////////////////////////////////////////////
@@ -70,10 +79,21 @@ export const usePocketbaseStore = defineStore('pocketbase', () => {
pb.collection('public_users').getFullList({
sort: 'username',
})
+ ).pipe(map((users) => users.map((user) => new SimpleUser(user))));
+ }
+
+ function userAvatar(userId: string, thumbSize: number = 100): Observable {
+ return from(pb.collection('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,
},
};
});
diff --git a/src/views/AccountView.vue b/src/views/AccountView.vue
index b8a066f..8ea4293 100644
--- a/src/views/AccountView.vue
+++ b/src/views/AccountView.vue
@@ -3,7 +3,7 @@
Hello {{ username }} !