Compare commits
1 Commits
features/n
...
features/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ee412ee0c |
35
.eslintrc.cjs
Normal file
35
.eslintrc.cjs
Normal file
@@ -0,0 +1,35 @@
|
||||
/* eslint-env node */
|
||||
require('@rushstack/eslint-patch/modern-module-resolution')
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'plugin:vue/vue3-essential',
|
||||
'eslint:recommended',
|
||||
'@vue/eslint-config-typescript',
|
||||
'@vue/eslint-config-prettier/skip-formatting'
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
'**/__tests__/*.{cy,spec}.{js,ts,jsx,tsx}',
|
||||
'cypress/e2e/**/*.{cy,spec}.{js,ts,jsx,tsx}',
|
||||
'cypress/support/**/*.{js,ts,jsx,tsx}'
|
||||
],
|
||||
extends: ['plugin:cypress/recommended']
|
||||
}
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest'
|
||||
},
|
||||
rules: {
|
||||
indent: ['error', 2, { SwitchCase: 1 }],
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{
|
||||
endOfLine: 'auto'
|
||||
}
|
||||
],
|
||||
'vue/multi-word-component-names': 'off'
|
||||
}
|
||||
}
|
||||
44
.gitignore
vendored
44
.gitignore
vendored
@@ -1,28 +1,32 @@
|
||||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
dist
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Misc
|
||||
node_modules
|
||||
.DS_Store
|
||||
.fleet
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Supabase
|
||||
.branches
|
||||
.temp
|
||||
|
||||
3
.npmrc
3
.npmrc
@@ -1,3 +0,0 @@
|
||||
# https://pnpm.io/npmrc#shamefully-hoist
|
||||
# Fixes: WARN Failed to resolve dependency: @supabase/gotrue-js, present in 'optimizeDeps.include'
|
||||
shamefully-hoist=true
|
||||
7
.vscode/extensions.json
vendored
Normal file
7
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"Vue.volar",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"eslint.experimental.useFlatConfig": true,
|
||||
"editor.indentSize": "tabSize",
|
||||
"editor.tabSize": 2,
|
||||
}
|
||||
78
README.md
78
README.md
@@ -1,75 +1,5 @@
|
||||
# Nuxt 3 Minimal Starter
|
||||
# Leim Tools
|
||||
A congregate of tools used to visualize TTRPG elements and help content creators (mostly me for now tbh) build their worlds.
|
||||
|
||||
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure to install the dependencies:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install
|
||||
|
||||
# pnpm
|
||||
pnpm install
|
||||
|
||||
# yarn
|
||||
yarn install
|
||||
|
||||
# bun
|
||||
bun install
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on `http://localhost:3000`:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run dev
|
||||
|
||||
# pnpm
|
||||
pnpm run dev
|
||||
|
||||
# yarn
|
||||
yarn dev
|
||||
|
||||
# bun
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Build the application for production:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run build
|
||||
|
||||
# pnpm
|
||||
pnpm run build
|
||||
|
||||
# yarn
|
||||
yarn build
|
||||
|
||||
# bun
|
||||
bun run build
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run preview
|
||||
|
||||
# pnpm
|
||||
pnpm run preview
|
||||
|
||||
# yarn
|
||||
yarn preview
|
||||
|
||||
# bun
|
||||
bun run preview
|
||||
```
|
||||
|
||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||
## Timeline
|
||||
Visual helper for time-related data, such as events, eras and characters. Helpful to know how old a character is while something is happening in the world, or how far two events are separated in the timeline.
|
||||
|
||||
42
app.vue
42
app.vue
@@ -1,42 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ConfigProvider } from 'radix-vue'
|
||||
|
||||
useHead({
|
||||
titleTemplate: (titleChunk) => {
|
||||
return titleChunk ? `${titleChunk} — TTTools` : 'TTTools';
|
||||
},
|
||||
meta: [
|
||||
{ name: "charset", content: "UTF-8" },
|
||||
{ name: "viewport", content: "width=device-width, initial-scale=1.0" },
|
||||
{ name: "author", content: "Alexis Pelé" },
|
||||
{ name: "description", content: "Tools destined to players and game master, helping them visualize their worlds better." },
|
||||
{ name: "generator", content: "Nuxt" },
|
||||
{ name: "msapplication-TileColor", content: "00aba9" },
|
||||
{ name: "theme-color", content: "00aba9" },
|
||||
{ name: "og:type", content: "tabletop-tools" },
|
||||
{ name: "og:url", content: "ttt.alexcreates.fr" },
|
||||
{ name: 'robots', content: 'noindex, nofollow'}
|
||||
],
|
||||
link: [
|
||||
{ rel: 'apple-touch-icon', sizes: "76x76", href: '/apple-touch-icon.png' },
|
||||
{ rel: 'icon', sizes: "32x32", type: 'image/png', href: '/favicon-32x32.png' },
|
||||
{ rel: 'icon', sizes: "16x16", type: 'image/png', href: '/favicon-16x16.png' },
|
||||
{ rel: 'manifest', href: '/site.webmanifest' },
|
||||
{ rel: 'mask-icon', href: '/safari-pinned-tab.svg', color: '#6595b4' },
|
||||
]
|
||||
})
|
||||
|
||||
const useIdFunction = () => useId()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-screen">
|
||||
<NuxtLoadingIndicator />
|
||||
|
||||
<NuxtLayout>
|
||||
<ConfigProvider :use-id="useIdFunction">
|
||||
<NuxtPage/>
|
||||
</ConfigProvider>
|
||||
</NuxtLayout>
|
||||
</div>
|
||||
</template>
|
||||
@@ -4,11 +4,11 @@
|
||||
"typescript": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "assets/main.css",
|
||||
"css": "src/assets/main.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true
|
||||
},
|
||||
"framework": "nuxt",
|
||||
"framework": "vite",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { useCalendar } from '@/stores/CalendarStore'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { areDatesIdentical } from '@/models/Date'
|
||||
|
||||
const { defaultDate } = useCalendar()
|
||||
const { selectedDate } = storeToRefs(useCalendar())
|
||||
const { jumpToDefaultDate, getFormattedDateTitle } = useCalendar()
|
||||
|
||||
const defaultDateFormatted: string = getFormattedDateTitle(defaultDate, true)
|
||||
|
||||
const isDefaultDate: ComputedRef<boolean> = computed<boolean>(() => areDatesIdentical(selectedDate.value, defaultDate))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiTooltipProvider :delay-duration="250">
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<ClientOnly>
|
||||
<UiButton size="sm" :disabled="isDefaultDate" @click="jumpToDefaultDate">
|
||||
Aujourd'hui
|
||||
</UiButton>
|
||||
|
||||
<template #fallback>
|
||||
<UiButton size="sm">
|
||||
Aujourd'hui
|
||||
</UiButton>
|
||||
</template>
|
||||
</ClientOnly>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent>
|
||||
<p>{{ defaultDateFormatted }}</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</UiTooltipProvider>
|
||||
</template>
|
||||
@@ -1,32 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useCalendar } from '@/stores/CalendarStore'
|
||||
import { PhCalendarBlank } from '@phosphor-icons/vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const { currentConfig, viewTypeOptions, getViewTypeTitle, setViewType } = useCalendar()
|
||||
|
||||
const viewTypeTitle = computed(() => getViewTypeTitle(currentConfig.viewType))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiDropdownMenu>
|
||||
<UiDropdownMenuTrigger as-child>
|
||||
<UiButton size="sm" variant="secondary">
|
||||
<PhCalendarBlank size="18" weight="fill" />
|
||||
|
||||
{{ viewTypeTitle }}
|
||||
</UiButton>
|
||||
</UiDropdownMenuTrigger>
|
||||
<UiDropdownMenuContent :side="'bottom'" :collision-padding="30">
|
||||
<UiDropdownMenuLabel>Mode d'affichage</UiDropdownMenuLabel>
|
||||
<UiDropdownMenuSeparator />
|
||||
<UiDropdownMenuItem
|
||||
v-for="option in viewTypeOptions"
|
||||
:key="option"
|
||||
@click="setViewType(option)"
|
||||
>
|
||||
{{ getViewTypeTitle(option) }}
|
||||
</UiDropdownMenuItem>
|
||||
</UiDropdownMenuContent>
|
||||
</UiDropdownMenu>
|
||||
</template>
|
||||
@@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<h1 class="text-2xl font-bold flex">
|
||||
<slot />
|
||||
</h1>
|
||||
</template>
|
||||
@@ -1,77 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { PhHouse, PhList } from '@phosphor-icons/vue'
|
||||
import type { SidebarProps } from './SidebarProps';
|
||||
|
||||
defineProps<SidebarProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="w-16 py-6 border-r-[1px] border-l-slate-500 grid grid-rows-[1fr_auto] justify-center">
|
||||
<menu class="flex flex-col gap-4">
|
||||
<li class="mb-12">
|
||||
<UiButton variant="ghost" size="icon" class="rounded-full" @click="console.log">
|
||||
<PhList size="27" />
|
||||
</UiButton>
|
||||
</li>
|
||||
|
||||
<li v-if="!isHome">
|
||||
<UiTooltipProvider :delay-duration="100">
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<UiButton variant="ghost" size="icon" class="rounded-full" as-child>
|
||||
<RouterLink to="/">
|
||||
<PhHouse size="24" weight="fill" />
|
||||
</RouterLink>
|
||||
</UiButton>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent :side="'right'">
|
||||
<p>Retourner aux outils</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</UiTooltipProvider>
|
||||
</li>
|
||||
|
||||
<li v-for="(item, i) in menuItems" :key="i">
|
||||
<UiTooltipProvider :delay-duration="100">
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<UiButton v-if="item.to" variant="ghost" size="icon" class="rounded-full" as-child>
|
||||
<RouterLink :to="item.to">
|
||||
<component :is="item.phIcon" size="24" weight="fill" />
|
||||
</RouterLink>
|
||||
</UiButton>
|
||||
<UiButton v-if="item.clickHandler" variant="ghost" size="icon" class="rounded-full" @click="item.clickHandler">
|
||||
<component :is="item.phIcon" size="24" weight="fill" />
|
||||
</UiButton>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent :side="'right'">
|
||||
<p>{{ item.tooltip }}</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</UiTooltipProvider>
|
||||
</li>
|
||||
|
||||
<!-- <li>
|
||||
<UiTooltipProvider :delay-duration="100">
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<UiButton
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="rounded-full"
|
||||
@click="revealAdvancedSearch()"
|
||||
>
|
||||
<PhMagnifyingGlass size="24" weight="fill" />
|
||||
</UiButton>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent :side="'right'">
|
||||
<p>Recherche avancée</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</UiTooltipProvider>
|
||||
</li> -->
|
||||
</menu>
|
||||
|
||||
<UserCTA />
|
||||
</nav>
|
||||
</template>
|
||||
@@ -1,11 +0,0 @@
|
||||
export interface MenuItem {
|
||||
phIcon: Component
|
||||
tooltip: string
|
||||
clickHandler?: () => void
|
||||
to?: string
|
||||
}
|
||||
|
||||
export interface SidebarProps {
|
||||
menuItems: MenuItem[],
|
||||
isHome?: boolean
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { PhUserCircle } from '@phosphor-icons/vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const { auth } = useSupabaseClient()
|
||||
const user = useSupabaseUser()
|
||||
const userMeta = computed(() => user.value?.user_metadata)
|
||||
|
||||
const menuOpened = ref<boolean>(false)
|
||||
|
||||
function closeMenu() {
|
||||
menuOpened.value = false
|
||||
}
|
||||
watch(user, closeMenu)
|
||||
|
||||
async function handleGoogleLogin() {
|
||||
try {
|
||||
auth.signInWithOAuth({
|
||||
provider: 'google',
|
||||
options: {
|
||||
queryParams: {
|
||||
access_type: 'offline',
|
||||
prompt: 'consent'
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
try {
|
||||
const { error } = await auth.signOut()
|
||||
|
||||
if (error) throw error
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
function gotoProfilePage() {
|
||||
router.push({ path: '/profile' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<UiPopover v-model:open="menuOpened">
|
||||
<UiPopoverTrigger>
|
||||
<UiAvatar v-if="user" class="cursor-pointer">
|
||||
<UiAvatarImage
|
||||
:src="userMeta?.avatar_url"
|
||||
:alt="userMeta?.full_name"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<UiAvatarFallback>Me</UiAvatarFallback>
|
||||
</UiAvatar>
|
||||
<UiButton v-else variant="outline" size="icon">
|
||||
<PhUserCircle size="18" />
|
||||
</UiButton>
|
||||
</UiPopoverTrigger>
|
||||
<UiPopoverContent class="w-fit p-0" :align="'start'" :side="'top'" :collision-padding="20">
|
||||
<UiCommand>
|
||||
<UiCommandList v-if="user">
|
||||
<UiCommandGroup :heading="`Connecté en tant que ${user?.email}`">
|
||||
<UiCommandItem value="profile" @select="gotoProfilePage"> Profil </UiCommandItem>
|
||||
<UiCommandItem value="logout" @select="handleLogout"> Déconnexion </UiCommandItem>
|
||||
</UiCommandGroup>
|
||||
</UiCommandList>
|
||||
<UiCommandList v-else>
|
||||
<UiCommandItem value="logout" @select="handleGoogleLogin"> Connexion </UiCommandItem>
|
||||
</UiCommandList>
|
||||
</UiCommand>
|
||||
</UiPopoverContent>
|
||||
</UiPopover>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
@@ -1,9 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
const user = useSupabaseUser()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-8">
|
||||
<Heading>{{ user?.user_metadata.full_name }}</Heading>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,14 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface SkeletonProps {
|
||||
class?: HTMLAttributes['class']
|
||||
}
|
||||
|
||||
const props = defineProps<SkeletonProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('animate-pulse rounded-md bg-muted', props.class)" />
|
||||
</template>
|
||||
@@ -1 +0,0 @@
|
||||
export { default as Skeleton } from './Skeleton.vue'
|
||||
15
cypress.config.ts
Normal file
15
cypress.config.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { defineConfig } from 'cypress'
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}',
|
||||
baseUrl: 'http://localhost:4173'
|
||||
},
|
||||
component: {
|
||||
specPattern: 'src/**/__tests__/*.{cy,spec}.{js,ts,jsx,tsx}',
|
||||
devServer: {
|
||||
framework: 'vue',
|
||||
bundler: 'vite'
|
||||
}
|
||||
}
|
||||
})
|
||||
8
cypress/e2e/example.cy.ts
Normal file
8
cypress/e2e/example.cy.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// https://on.cypress.io/api
|
||||
|
||||
describe('My First Test', () => {
|
||||
it('visits the app root url', () => {
|
||||
cy.visit('/')
|
||||
cy.contains('h1', 'You did it!')
|
||||
})
|
||||
})
|
||||
10
cypress/e2e/tsconfig.json
Normal file
10
cypress/e2e/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["./**/*", "../support/**/*"],
|
||||
"compilerOptions": {
|
||||
"isolatedModules": false,
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"types": ["cypress"]
|
||||
}
|
||||
}
|
||||
5
cypress/fixtures/example.json
Normal file
5
cypress/fixtures/example.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
39
cypress/support/commands.ts
Normal file
39
cypress/support/commands.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************
|
||||
// This example commands.ts shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
//
|
||||
// declare global {
|
||||
// namespace Cypress {
|
||||
// interface Chainable {
|
||||
// login(email: string, password: string): Chainable<void>
|
||||
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
export {}
|
||||
12
cypress/support/component-index.html
Normal file
12
cypress/support/component-index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Components App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div data-cy-root></div>
|
||||
</body>
|
||||
</html>
|
||||
43
cypress/support/component.ts
Normal file
43
cypress/support/component.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
// ***********************************************************
|
||||
// This example support/component.ts is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
||||
// Import global styles
|
||||
import '@/assets/main.css'
|
||||
|
||||
import { mount } from 'cypress/vue'
|
||||
|
||||
// Augment the Cypress namespace to include type definitions for
|
||||
// your custom command.
|
||||
// Alternatively, can be defined in cypress/support/component.d.ts
|
||||
// with a <reference path="./component" /> at the top of your spec.
|
||||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
mount: typeof mount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cypress.Commands.add('mount', mount)
|
||||
|
||||
// Example use:
|
||||
// cy.mount(MyComponent)
|
||||
20
cypress/support/e2e.ts
Normal file
20
cypress/support/e2e.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -1,15 +0,0 @@
|
||||
import withNuxt from './.nuxt/eslint.config.mjs'
|
||||
import stylisticJs from '@stylistic/eslint-plugin-js'
|
||||
|
||||
export default withNuxt({
|
||||
plugins: {
|
||||
'@stylistic/js': stylisticJs
|
||||
},
|
||||
rules: {
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'no-unused-vars': 'warn',
|
||||
'space-in-parens': 'off',
|
||||
'computed-property-spacing': 'off',
|
||||
'@stylistic/js/indent': [ 'warn', 2, { SwitchCase: 1 } ],
|
||||
}
|
||||
})
|
||||
32
index.html
Normal file
32
index.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<meta name="author" content="Alexis Pelé" />
|
||||
<meta name="description" content="Outil de visualisation chronologique des évènements de Léim" />
|
||||
<meta name="generator" content="VueJS" />
|
||||
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
|
||||
<title>Outils — TTTools</title>
|
||||
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#6595b4">
|
||||
<meta name="msapplication-TileColor" content="#00aba9">
|
||||
<meta name="theme-color" content="#a9d1eb">
|
||||
|
||||
<meta property="og:title" content="Outils — Leim Wiki"/>
|
||||
<meta property="og:type" content="tabletop-tools" />
|
||||
<meta property="og:url" content="ttt.alexcreates.fr" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,7 +0,0 @@
|
||||
export default defineNuxtRouteMiddleware(() => {
|
||||
const user = useSupabaseUser()
|
||||
|
||||
if (!user.value) {
|
||||
return abortNavigation()
|
||||
}
|
||||
})
|
||||
@@ -1,35 +0,0 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
devtools: { enabled: true },
|
||||
|
||||
modules: [
|
||||
'@nuxtjs/supabase',
|
||||
'@pinia/nuxt',
|
||||
'@vueuse/nuxt',
|
||||
'@nuxtjs/tailwindcss',
|
||||
'@nuxtjs/color-mode',
|
||||
'@nuxt/eslint',
|
||||
'shadcn-nuxt'
|
||||
],
|
||||
css: ['~/assets/main.css'],
|
||||
|
||||
postcss: {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
},
|
||||
shadcn: {
|
||||
prefix: 'Ui',
|
||||
componentDir: './components/ui'
|
||||
},
|
||||
colorMode: {
|
||||
classSuffix: '',
|
||||
preference: 'dark',
|
||||
fallback: 'dark',
|
||||
},
|
||||
supabase: {
|
||||
redirect: false
|
||||
},
|
||||
eslint: {}
|
||||
})
|
||||
6432
package-lock.json
generated
Normal file
6432
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
67
package.json
67
package.json
@@ -1,50 +1,57 @@
|
||||
{
|
||||
"name": "nuxt-app",
|
||||
"name": "leim-tool",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"lint:prettier": "prettier . --check"
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'",
|
||||
"test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'",
|
||||
"test:unit": "cypress run --component",
|
||||
"test:unit:dev": "cypress open --component",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build --force",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/eslint": "^0.3.12",
|
||||
"@phosphor-icons/vue": "^2.2.1",
|
||||
"@pinia/nuxt": "^0.5.1",
|
||||
"@supabase/supabase-js": "^2.43.1",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"@vueuse/nuxt": "^10.9.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"clsx": "^2.1.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lucide-vue-next": "^0.364.0",
|
||||
"nuxt": "^3.7.0",
|
||||
"pinia": "^2.1.7",
|
||||
"radix-vue": "^1.7.4",
|
||||
"shadcn-nuxt": "^0.10.4",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"radix-vue": "^1.6.1",
|
||||
"tailwind-merge": "^2.2.2",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vue": "^3.4.27",
|
||||
"vue-router": "^4.3.2"
|
||||
"vue": "^3.4.21",
|
||||
"vue-router": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxtjs/color-mode": "^3.4.1",
|
||||
"@nuxtjs/supabase": "^1.2.2",
|
||||
"@nuxtjs/tailwindcss": "^6.12.0",
|
||||
"@stylistic/eslint-plugin-js": "^2.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
||||
"@typescript-eslint/parser": "^7.8.0",
|
||||
"@rushstack/eslint-patch": "^1.3.3",
|
||||
"@tsconfig/node20": "^20.1.2",
|
||||
"@types/node": "^20.11.28",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vue/eslint-config-prettier": "^8.0.0",
|
||||
"@vue/eslint-config-typescript": "^12.0.0",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"cypress": "^13.7.0",
|
||||
"eslint": "^8.49.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-vue": "^9.26.0",
|
||||
"eslint-plugin-cypress": "^2.15.1",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"npm-run-all2": "^6.1.2",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier": "^3.0.3",
|
||||
"sass": "^1.72.0",
|
||||
"start-server-and-test": "^2.0.3",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "~5.4.0"
|
||||
"typescript": "~5.4.0",
|
||||
"vite": "^5.1.6",
|
||||
"vue-tsc": "^2.0.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useCalendar } from '@/stores/CalendarStore'
|
||||
import type { MenuItem } from '~/components/global/SidebarProps';
|
||||
import { PhMagnifyingGlass } from '@phosphor-icons/vue'
|
||||
|
||||
const { revealAdvancedSearch } = useCalendar()
|
||||
const { isAdvancedSearchOpen } = storeToRefs(useCalendar())
|
||||
|
||||
useHead({
|
||||
title: 'Calendar'
|
||||
})
|
||||
|
||||
const sidebarMenu: MenuItem[] = [
|
||||
{
|
||||
phIcon: PhMagnifyingGlass,
|
||||
tooltip: 'Recherche avancée',
|
||||
clickHandler: revealAdvancedSearch
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="h-full grid grid-cols-[auto_1fr]">
|
||||
<Sidebar :menu-items="sidebarMenu" />
|
||||
<Calendar />
|
||||
|
||||
<CalendarSearch v-model:model-value="isAdvancedSearchOpen" />
|
||||
</main>
|
||||
</template>
|
||||
@@ -1,31 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { MenuItem } from '~/components/global/SidebarProps';
|
||||
|
||||
useHead({
|
||||
title: 'Dashboard'
|
||||
})
|
||||
|
||||
const sidebarMenu: MenuItem[] = []
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="h-full grid grid-cols-[auto_1fr]">
|
||||
<Sidebar :menu-items="sidebarMenu" is-home />
|
||||
|
||||
<div class="h-full grid place-items-center">
|
||||
<UiCard class="w-1/3 min-w-72" link="/calendar">
|
||||
<UiCardHeader>
|
||||
<UiCardTitle>Calendrier</UiCardTitle>
|
||||
<UiCardDescription>
|
||||
<UiBadge class="mix-blend-luminosity font-bold bg-gray-600" variant="secondary">
|
||||
chronologie
|
||||
</UiBadge>
|
||||
</UiCardDescription>
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
Chronologie complète de Léïm, rassemblant les évènements et personnages clés du monde
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
@@ -1,29 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { MenuItem } from '~/components/global/SidebarProps';
|
||||
|
||||
const user = useSupabaseUser()
|
||||
|
||||
useHead({
|
||||
title: 'Profil'
|
||||
})
|
||||
|
||||
definePageMeta({
|
||||
middleware: ['auth-guard']
|
||||
})
|
||||
|
||||
// Redirect user back home when they log out on the page
|
||||
watch(user, (n, _o) => {
|
||||
if (!n) {
|
||||
navigateTo('/')
|
||||
}
|
||||
})
|
||||
const sidebarMenu: MenuItem[] = []
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="h-full grid grid-cols-[auto_1fr]">
|
||||
<Sidebar :menu-items="sidebarMenu" />
|
||||
|
||||
<ProfileLayout />
|
||||
</main>
|
||||
</template>
|
||||
10204
pnpm-lock.yaml
generated
10204
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -1,252 +0,0 @@
|
||||
export default defineEventHandler(() => {
|
||||
return [
|
||||
/**
|
||||
* Player characters
|
||||
*/
|
||||
// "Les Milles Cages"
|
||||
{
|
||||
name: 'Sulvan Trois-Barbes',
|
||||
birth: { day: 20, month: 3, year: 3169 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Sulvan_Trois-Barbes',
|
||||
category: 'joueur',
|
||||
secondaryCategories: ['criminel', 'étincelle']
|
||||
},
|
||||
{
|
||||
name: 'Vascylly',
|
||||
birth: { day: 3, month: 5, year: 3181 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Vascylly',
|
||||
category: 'joueur',
|
||||
secondaryCategories: ['mage']
|
||||
},
|
||||
{
|
||||
name: 'Tara Belyus',
|
||||
birth: { day: 14, month: 9, year: 3186 },
|
||||
category: 'joueur',
|
||||
secondaryCategories: ['mage']
|
||||
},
|
||||
// "Les Cloches de Cantane"
|
||||
{
|
||||
name: 'Malik',
|
||||
birth: { day: 22, month: 8, year: 3181 },
|
||||
category: 'joueur',
|
||||
secondaryCategories: ['scientifique']
|
||||
},
|
||||
{
|
||||
name: 'Elie',
|
||||
birth: { day: 5, month: 6, year: 3192 },
|
||||
category: 'joueur',
|
||||
secondaryCategories: ['ecclésiastique']
|
||||
},
|
||||
{
|
||||
name: 'Clayron',
|
||||
birth: { day: 3, month: 5, year: 3178 },
|
||||
category: 'joueur',
|
||||
secondaryCategories: ['sentinelle']
|
||||
},
|
||||
|
||||
/**
|
||||
* NPC
|
||||
*/
|
||||
// Counts of the Alliance
|
||||
{
|
||||
name: 'Alfrid de Lagarde',
|
||||
birth: { day: 29, month: 7, year: 3193 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Alfrid_de_Lagarde',
|
||||
category: 'comte',
|
||||
secondaryCategories: ['sentinelle']
|
||||
},
|
||||
{
|
||||
name: 'Alaric de Lagarde',
|
||||
birth: { day: 23, month: 8, year: 3192 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Alaric_de_Lagarde',
|
||||
category: 'comte',
|
||||
secondaryCategories: ['sentinelle']
|
||||
},
|
||||
{
|
||||
name: 'Anastael III de Quillon',
|
||||
birth: { day: 1, month: 5, year: 3184 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Anastael_III_de_Quillon',
|
||||
category: 'comte',
|
||||
secondaryCategories: ['sentinelle', 'buse blanche']
|
||||
},
|
||||
{
|
||||
name: 'Antoine de Mireloin',
|
||||
birth: { day: 31, month: 5, year: 3190 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Antoine_de_Mireloin',
|
||||
category: 'comte'
|
||||
},
|
||||
{
|
||||
name: 'Armance Ronchère',
|
||||
birth: { day: 1, month: 1, year: 3132 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Armance_Ronchère',
|
||||
category: 'comte'
|
||||
},
|
||||
{
|
||||
name: 'Baranne Rougefer',
|
||||
birth: { day: 1, month: 0, year: 3175 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Baranne_Rougefer',
|
||||
category: 'comte',
|
||||
secondaryCategories: ['sentinelle']
|
||||
},
|
||||
{
|
||||
name: 'Béatrice II de Grandlac',
|
||||
birth: { day: 21, month: 4, year: 3158 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Béatrice_II_de_Grandlac',
|
||||
category: 'comte',
|
||||
secondaryCategories: ['mage']
|
||||
},
|
||||
{
|
||||
name: 'Favio Asharos-Losantelle',
|
||||
birth: { day: 17, month: 3, year: 3091 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Favio_Asharos-Losantelle',
|
||||
category: 'comte',
|
||||
secondaryCategories: ['mage']
|
||||
},
|
||||
{
|
||||
name: 'Firmin de Montardieu',
|
||||
birth: { day: 9, month: 2, year: 3203 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Firmin_de_Montardieu',
|
||||
category: 'comte'
|
||||
},
|
||||
{
|
||||
name: 'Laura de Montardieu',
|
||||
birth: { day: 32, month: 3, year: 3167 },
|
||||
death: { day: 1, month: 4, year: 3217 },
|
||||
hiddenDeath: true,
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Laura_de_Montardieu',
|
||||
category: 'comte'
|
||||
},
|
||||
{
|
||||
name: 'Lazarus Tymos',
|
||||
birth: { day: 29, month: 9, year: 3145 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Lazarus_Tymos',
|
||||
category: 'comte',
|
||||
secondaryCategories: ['mage', 'buse blanche']
|
||||
},
|
||||
{
|
||||
name: 'Marion de Corambre',
|
||||
birth: { day: 14, month: 7, year: 3190 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Marion_de_Corambre',
|
||||
category: 'comte',
|
||||
secondaryCategories: ['scientifique', 'ecclésiastique']
|
||||
},
|
||||
{
|
||||
name: 'Relforg Pergaré',
|
||||
birth: { day: 18, month: 9, year: 3182 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Relforg_Pergaré',
|
||||
category: 'comte'
|
||||
},
|
||||
{
|
||||
name: 'Vilgarde de Ternâcre',
|
||||
birth: { day: 3, month: 3, year: 2998 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Vilgarde_de_Ternâcre',
|
||||
category: 'comte'
|
||||
},
|
||||
{
|
||||
name: 'Ysildy Milopée',
|
||||
birth: { day: 3, month: 1, year: 3187 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Ysildy_Milopée',
|
||||
category: 'comte',
|
||||
secondaryCategories: ['mage', 'scientifique', 'professeur']
|
||||
},
|
||||
|
||||
// Sparks
|
||||
{
|
||||
name: 'Izàc Tymos',
|
||||
birth: { day: 13, month: 6, year: 3192 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Izàc_Tymos',
|
||||
category: 'mage',
|
||||
secondaryCategories: ['étincelle', 'criminel', 'professeur', 'scientifique']
|
||||
},
|
||||
{
|
||||
name: 'Tvernée',
|
||||
birth: { day: 19, month: 2, year: 3205 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Tvernée',
|
||||
category: 'étincelle',
|
||||
secondaryCategories: ['criminel']
|
||||
},
|
||||
|
||||
// Pirates
|
||||
{
|
||||
name: 'Räzal',
|
||||
birth: { day: 13, month: 8, year: 3178 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Räzal'
|
||||
},
|
||||
|
||||
// Legends
|
||||
{
|
||||
name: 'Jorhas Kirendre',
|
||||
birth: { day: 2, month: 9, year: -452 },
|
||||
death: { day: 2, month: 2, year: -419 },
|
||||
category: 'sentinelle',
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Jorhas_Kirendre'
|
||||
},
|
||||
|
||||
// "Les Milles Cages"
|
||||
{
|
||||
name: 'Ernestin Pomel',
|
||||
birth: { day: 11, month: 2, year: 3179 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Ernestin_Pomel',
|
||||
category: 'mage'
|
||||
},
|
||||
{
|
||||
name: 'Quacille Lévios',
|
||||
birth: { day: 3, month: 6, year: 3162 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Quacille_Lévios',
|
||||
category: 'professeur',
|
||||
secondaryCategories: ['mage']
|
||||
},
|
||||
{
|
||||
name: 'Morel Lévios',
|
||||
birth: { day: 26, month: 3, year: 3157 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Morel_Lévios',
|
||||
category: 'sentinelle',
|
||||
secondaryCategories: ['mage', 'buse blanche']
|
||||
},
|
||||
{
|
||||
name: 'Anastael Simon',
|
||||
birth: { day: 32, month: 2, year: 3166 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Anastael_Simon',
|
||||
category: 'étincelle',
|
||||
secondaryCategories: ['mage']
|
||||
},
|
||||
{
|
||||
name: 'Grestain',
|
||||
birth: { day: 9, month: 2, year: 3162 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Grestain',
|
||||
category: 'criminel',
|
||||
secondaryCategories: ['mage']
|
||||
},
|
||||
{
|
||||
name: 'Lucien Malanth',
|
||||
birth: { day: 31, month: 4, year: 3167 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Lucien_Malanth',
|
||||
category: 'criminel',
|
||||
secondaryCategories: ['mage']
|
||||
},
|
||||
{
|
||||
name: 'Tivian Rodhus',
|
||||
birth: { day: 13, month: 3, year: 3157 },
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Tivian_Rodhus',
|
||||
category: 'professeur',
|
||||
secondaryCategories: ['mage', 'criminel']
|
||||
},
|
||||
|
||||
// "Les Cloches de Cantane"
|
||||
{
|
||||
name: 'Bénédicte Vaht',
|
||||
description: "Moine d'Ikami ayant entrepris la construction du Pilier Blanc de Cantane",
|
||||
birth: { day: 28, month: 6, year: 3139 },
|
||||
category: 'ecclésiastique'
|
||||
},
|
||||
{
|
||||
name: 'Taleb Vaht',
|
||||
description:
|
||||
"Fils de Bénédicte, il s'est engagé auprès des Étincelles comme alchimiste artificier.",
|
||||
birth: { day: 9, month: 1, year: 3180 },
|
||||
death: { day: 28, month: 7, year: 3209 },
|
||||
category: 'étincelle',
|
||||
secondaryCategories: ['scientifique']
|
||||
}
|
||||
]
|
||||
})
|
||||
@@ -1,217 +0,0 @@
|
||||
export default defineEventHandler(() => {
|
||||
return [
|
||||
// Histoire Antique
|
||||
{
|
||||
title: "Laurdieu devient la première cité de l'empire de Kaliatos",
|
||||
startDate: { day: 3, month: 4, year: -1932 },
|
||||
description:
|
||||
"L'empire de Kaliatos établi sa capitale dans la cité de Laurdieu, qui connaitra une prospérité nouvelle au sein d'Aldys.",
|
||||
category: 'législation',
|
||||
secondaryCategories: ['inauguration'],
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Laurdieu',
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
title: "Apparition d'Asménys",
|
||||
startDate: { day: 19, month: 7, year: -1358 },
|
||||
description:
|
||||
"La défunte chanteuse Asménys apparaît à un barde pendant son jeune public, démarrant la religion des Prêtresses d'Asménys.",
|
||||
category: 'religion',
|
||||
secondaryCategories: ['bénédiction'],
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Pr%C3%AAtresses_d%27Asm%C3%A9nys',
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
title: 'La Rupture',
|
||||
startDate: { day: 26, month: 5, year: -756 },
|
||||
endDate: { day: 4, month: 9, year: 29 },
|
||||
description:
|
||||
"Les Abysses se déversent à la surface de Léim, à travers plusieurs brèches. Plusieurs hordes de démons se rapprochent des villes, et ce qu'on appellera l'Âge des Abysses commencent alors sur la planète entière.",
|
||||
category: 'catastrophe',
|
||||
secondaryCategories: ['catastrophe naturelle'],
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Seconde_Rupture',
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
title: 'Marche du sang',
|
||||
startDate: { day: 18, month: 9, year: -420 },
|
||||
endDate: { day: 27, month: 1, year: -419 },
|
||||
description:
|
||||
"L'empereur de Kaliatos ordonne personnellement la traque de Jorhas Kirendre pour connivence avec les démons. Plusieurs bataillons sont affectés à la chasse de Jorhas, qui se terminera par son incarcération ainsi que la mort de plusieurs centaines de soldats.",
|
||||
category: 'criminalité',
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Jorhas_Kirendre',
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
title: 'Exécution de Tyhos',
|
||||
startDate: { day: 1, month: 0, year: 0 },
|
||||
description:
|
||||
"Le léviathan Tyhos rend l'âme après un combat de plusieurs années contre Lystos, le dieu du Soleil.",
|
||||
category: 'bénédiction',
|
||||
secondaryCategories: ['mort'],
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Tyhos',
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
title: 'Traité de Kadel',
|
||||
startDate: { day: 29, month: 4, year: 100 },
|
||||
description: '',
|
||||
category: 'inauguration',
|
||||
secondaryCategories: ['législation'],
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Alliance_Kald%C3%A9lienne#Trait%C3%A9_de_Kadel'
|
||||
},
|
||||
{
|
||||
title: 'Découverte des Plaines de Poussières',
|
||||
startDate: { day: 17, month: 7, year: 305 },
|
||||
description:
|
||||
"Les troupes de la reconquête aldienne explorent le littoral d'une immense étendue grise et inerte.",
|
||||
category: 'découverte',
|
||||
secondaryCategories: ['exploration'],
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Plaines_de_poussi%C3%A8re',
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
title: 'Construction du Rempart de Laurdieu',
|
||||
startDate: { day: 30, month: 2, year: 340 },
|
||||
endDate: { day: 27, month: 9, year: 355 },
|
||||
description:
|
||||
"Le Grand Conseil Kaldélien ordonne la construction d'une muraille autour des Plaines de Poussières, afin de contenir les démons y sortant.",
|
||||
category: 'construction',
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Plaines_de_poussi%C3%A8re',
|
||||
hidden: true
|
||||
},
|
||||
|
||||
// Histoire Récente
|
||||
{
|
||||
title: "Inauguration de l'Académie Artistique Arcanique",
|
||||
startDate: { day: 11, month: 6, year: 2545 },
|
||||
description:
|
||||
"Scäd Sceni ouvre son institut artistique dédié à l'apprentissage des arts arcaniques",
|
||||
category: 'inauguration',
|
||||
secondaryCategories: ['arcanologie'],
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Buse_(arme)'
|
||||
},
|
||||
{
|
||||
title: 'Création de la buse kaldélienne',
|
||||
startDate: { day: 4, month: 2, year: 3113 },
|
||||
description:
|
||||
'Sophia de Rougefer invente la buse kaldélienne, une arme à feu souple à deux canons.',
|
||||
category: 'invention',
|
||||
secondaryCategories: ['science'],
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Buse_(arme)'
|
||||
},
|
||||
{
|
||||
title: 'Feux dans les champs de Bamast',
|
||||
startDate: { day: 9, month: 5, year: 3200 },
|
||||
description:
|
||||
"Plusieurs incendies criminels se propagent à travers les champs de sérille des fermiers des littoraux de Bamast. Aucun suspect ni coupable ne sera trouvé et l'enquête sera baclée.",
|
||||
category: 'catastrophe',
|
||||
secondaryCategories: ['criminalité'],
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Vascylly'
|
||||
},
|
||||
{
|
||||
title: 'Meurtre de Darléon Typhos',
|
||||
startDate: { day: 21, month: 6, year: 3200 },
|
||||
description:
|
||||
'Darléon Typhos surprend Vascylly fouillant sa demeure peu après la tombée de la nuit. Le majordome de la famille Typhos découvre le corps sans vie de son maître le lendemain.',
|
||||
category: 'criminalité',
|
||||
secondaryCategories: ['joueurs'],
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Vascylly'
|
||||
},
|
||||
{
|
||||
title: 'Scandale Rodhus',
|
||||
startDate: { day: 25, month: 9, year: 3208 },
|
||||
description:
|
||||
"Tivian Rodhus, un professeur estimé de l'Académie Centrale Kaldélienne, est emprisonné pour corruption et aggression sexuelle. Le corps enseignant y est remanié sur ordre de Lazarus Tymos, comte de Nacride.",
|
||||
category: 'criminalité',
|
||||
secondaryCategories: ['scandale'],
|
||||
wiki: 'https://alexcreates.fr/leim/index.php/Tivian_Rodhus'
|
||||
},
|
||||
|
||||
// "Les Milles Cages"
|
||||
{
|
||||
title: "Arrivée d'aventuriers à Borélis",
|
||||
startDate: { day: 12, month: 7, year: 3209 },
|
||||
description:
|
||||
'Tara Belyus, Vascylly et Adol Sulvan livrent 3 condamnés à Handany. Ils partent pour la mer durant la journée.',
|
||||
category: 'joueurs'
|
||||
},
|
||||
{
|
||||
title: "Naufrage de l'Éclipse",
|
||||
description:
|
||||
"L'Éclipse, le navire de la garde contenant des condamnés à destination des Cages Handaniennes, s'échoue au large des côtes montagneuses de la Lance d'Aldys.",
|
||||
startDate: { day: 14, month: 7, year: 3209 },
|
||||
category: 'catastrophe'
|
||||
},
|
||||
{
|
||||
title: 'Emprisonnement de Tivian Rodhus',
|
||||
description: "Celui qu'on surnomme la Bête d'Ambrose arrive à Handany, où il purgera sa peine.",
|
||||
startDate: { day: 14, month: 7, year: 3209 },
|
||||
category: 'législation'
|
||||
},
|
||||
{
|
||||
title: 'Sulvan et Anastael atteignent Bamast',
|
||||
startDate: { day: 19, month: 2, year: 3210 },
|
||||
category: 'joueurs'
|
||||
},
|
||||
{
|
||||
title: 'Jugement de Bormis Griloup',
|
||||
description:
|
||||
"Bromis Griloup est jugé coupable d'escroquerie et sabotage aux Cours d'Acier de Tourgrise. Il purgera une peine de 10 ans au sein des prisons royales.",
|
||||
startDate: { day: 4, month: 8, year: 3209 },
|
||||
category: 'législation'
|
||||
},
|
||||
|
||||
// "Les Cloches de Cantane"
|
||||
{
|
||||
title: 'Inauguration de la Cloche du Pilier',
|
||||
description: "Le Moine Premier inaugure la grande cloche d'argent au sommet du Pilier d'Ikami.",
|
||||
startDate: { day: 29, month: 5, year: 3209 },
|
||||
category: 'religion',
|
||||
secondaryCategories: ['inauguration']
|
||||
},
|
||||
{
|
||||
title: '1ère disparation à Cantane',
|
||||
description: "Taleb Vaht décède dans une grotte à la suite d'une attaque d'ischiels enragées.",
|
||||
startDate: { day: 28, month: 7, year: 3209 },
|
||||
category: 'mort',
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
title: '2ème disparation à Cantane',
|
||||
description: 'Donovane le mineur kéturien disparait sans laisser de traces.',
|
||||
startDate: { day: 32, month: 7, year: 3209 },
|
||||
category: 'mort',
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
title: '3ème disparation à Cantane',
|
||||
description: 'Disparition de Sébastien, gredin sauride.',
|
||||
startDate: { day: 10, month: 8, year: 3209 },
|
||||
category: 'mort',
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
title: '4ème disparation à Cantane',
|
||||
description: 'Disparition de Thérence, patrouilleur sauride de la Vieille Garde.',
|
||||
startDate: { day: 19, month: 8, year: 3209 },
|
||||
category: 'mort',
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
title: '5ème disparation à Cantane',
|
||||
description: 'Disparition de Mathilda Boulais, vendeuse de pierres.',
|
||||
startDate: { day: 22, month: 8, year: 3209 },
|
||||
category: 'mort',
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
title: 'Grande Banque Minérale de Cantane',
|
||||
description:
|
||||
'Les artisans et mineurs de Rougefer se réunissent à Cantane pour vendre le fruit de leur dur labeur.',
|
||||
startDate: { day: 23, month: 8, year: 3209 },
|
||||
endDate: { day: 26, month: 8, year: 3209 },
|
||||
category: 'commerce'
|
||||
}
|
||||
]
|
||||
})
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "../.nuxt/tsconfig.server.json"
|
||||
}
|
||||
14
src/App.vue
Normal file
14
src/App.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { useDark } from '@vueuse/core'
|
||||
import { RouterView } from 'vue-router'
|
||||
import { useAuth } from './stores/AuthStore'
|
||||
|
||||
useDark()
|
||||
useAuth()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-screen">
|
||||
<RouterView />
|
||||
</div>
|
||||
</template>
|
||||
@@ -2,6 +2,7 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
@@ -35,7 +36,7 @@
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
|
||||
@@ -65,6 +66,7 @@
|
||||
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
20
src/components/auth/Auth.vue
Normal file
20
src/components/auth/Auth.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts" setup>
|
||||
import { supabase } from '@/supabase/client'
|
||||
import Button from '../ui/button/Button.vue'
|
||||
|
||||
async function handleGoogleLogin() {
|
||||
supabase.auth.signInWithOAuth({
|
||||
provider: 'google',
|
||||
options: {
|
||||
queryParams: {
|
||||
access_type: 'offline',
|
||||
prompt: 'consent'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button @click="handleGoogleLogin"> Google </Button>
|
||||
</template>
|
||||
11
src/components/auth/Profile.vue
Normal file
11
src/components/auth/Profile.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { useAuth } from '@/stores/AuthStore'
|
||||
|
||||
const { sessionToken } = useAuth()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<pre class="text-[.6em]">
|
||||
{{ sessionToken }}
|
||||
</pre>
|
||||
</template>
|
||||
@@ -10,10 +10,6 @@ import DecadeLayout from './state/decennially/Layout.vue'
|
||||
import YearLayout from './state/yearly/Layout.vue'
|
||||
|
||||
const { currentConfig } = useCalendar()
|
||||
const { selectedDate, jumpToDate } = useCalendar()
|
||||
|
||||
const { fetchEvents } = useCalendarEvents()
|
||||
fetchEvents()
|
||||
|
||||
const currentViewComponent: ComputedRef<Component> = computed<Component>(() => {
|
||||
switch (currentConfig.viewType) {
|
||||
@@ -31,16 +27,11 @@ const currentViewComponent: ComputedRef<Component> = computed<Component>(() => {
|
||||
return CenturyLayout
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
jumpToDate(selectedDate)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full grid grid-rows-[auto,1fr]">
|
||||
<CalendarMenu />
|
||||
|
||||
<KeepAlive>
|
||||
<component :is="currentViewComponent" />
|
||||
</KeepAlive>
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { getRelativeString } from '@/models/Date'
|
||||
import { useCalendar } from '@/stores/CalendarStore'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
|
||||
@@ -14,19 +15,10 @@ const dateDifference = computed(() => getRelativeString(defaultDate, selectedDat
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<h1 class="text-2xl font-bold flex items-center gap-1">
|
||||
<PhMapPin size="26" weight="bold" /> {{ mainDateTitle }}
|
||||
</h1>
|
||||
<h2 class="text-lg italic opacity-75">
|
||||
{{ dateDifference }}
|
||||
</h2>
|
||||
|
||||
<template #fallback>
|
||||
<div class="grid gap-1">
|
||||
<UiSkeleton class="h-8 w-64" />
|
||||
<UiSkeleton class="h-6 w-28" />
|
||||
</div>
|
||||
</template>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
@@ -1,10 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { areDatesIdentical, type LeimDate } from '@/models/Date'
|
||||
|
||||
import type { CalendarEvent } from '@/models/Events'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import CalendarEventDetails from './CalendarEventDetails.vue'
|
||||
import { Popover, PopoverTrigger } from '@/components/ui/popover'
|
||||
|
||||
const props = defineProps<{
|
||||
event: CalendarEvent
|
||||
@@ -25,8 +26,8 @@ function handleClosePopover() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiPopover v-model:open="isPopoverOpen">
|
||||
<UiPopoverTrigger as-child>
|
||||
<Popover v-model:open="isPopoverOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<button
|
||||
class="text-xs px-2 py-1 block w-full text-left rounded-sm whitespace-nowrap overflow-hidden text-ellipsis"
|
||||
:class="
|
||||
@@ -57,15 +58,15 @@ function handleClosePopover() {
|
||||
>
|
||||
{{ event.title }}
|
||||
</button>
|
||||
</UiPopoverTrigger>
|
||||
</PopoverTrigger>
|
||||
|
||||
<CalendarEventDetails
|
||||
v-once
|
||||
:event
|
||||
:spans-multiple-days
|
||||
:is-start-event
|
||||
:is-end-event
|
||||
@query:close-popover="handleClosePopover"
|
||||
v-once
|
||||
/>
|
||||
</UiPopover>
|
||||
</Popover>
|
||||
</template>
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
PhArrowBendDoubleUpLeft,
|
||||
PhArrowBendDoubleUpRight
|
||||
} from '@phosphor-icons/vue'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { PopoverContent } from '@/components/ui/popover'
|
||||
|
||||
const { defaultDate, getFormattedDateTitle, jumpToDate } = useCalendar()
|
||||
const { getRelativeEventFromEvent } = useCalendarEvents()
|
||||
@@ -49,8 +51,8 @@ function handleGotoRelativeEvent(position: 'next' | 'prev' = 'next') {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiPopoverContent
|
||||
class="event-details w-96 bg-slate-900"
|
||||
<PopoverContent
|
||||
class="event-details w-96"
|
||||
:align="'center'"
|
||||
:align-offset="50"
|
||||
:side="'left'"
|
||||
@@ -109,21 +111,21 @@ function handleGotoRelativeEvent(position: 'next' | 'prev' = 'next') {
|
||||
<template v-if="event.category || event.secondaryCategories">
|
||||
<ul class="flex gap-1">
|
||||
<li v-if="event.category">
|
||||
<UiBadge class="mix-blend-luminosity font-bold bg-gray-600" variant="secondary">
|
||||
<Badge class="mix-blend-luminosity font-bold bg-gray-600" variant="secondary">
|
||||
{{ event.category }}
|
||||
</UiBadge>
|
||||
</Badge>
|
||||
</li>
|
||||
|
||||
<li v-for="cat in event.secondaryCategories" :key="cat">
|
||||
<UiBadge class="mix-blend-luminosity bg-gray-600" variant="secondary">
|
||||
<Badge class="mix-blend-luminosity bg-gray-600" variant="secondary">
|
||||
{{ cat }}
|
||||
</UiBadge>
|
||||
</Badge>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<template v-if="event.description">
|
||||
<hr class="border-slate-500 mt-2" >
|
||||
<hr class="border-slate-500 mt-2" />
|
||||
|
||||
<div class="mt-2 text-sm text-slate-300">
|
||||
{{ event.description }}
|
||||
@@ -132,47 +134,47 @@ function handleGotoRelativeEvent(position: 'next' | 'prev' = 'next') {
|
||||
</div>
|
||||
|
||||
<nav v-if="event.startDate && event.endDate" class="absolute -top-2 right-2 flex gap-2">
|
||||
<UiBadge class="hover:opacity-100 hover:bg-slate-300" as-child>
|
||||
<Badge class="hover:opacity-100 hover:bg-slate-300" as-child>
|
||||
<button
|
||||
class="flex gap-1"
|
||||
title="Naviguer au début"
|
||||
@click="handleJumpToDate(event.startDate!)"
|
||||
title="Naviguer au début"
|
||||
>
|
||||
<PhHourglassHigh size="16" weight="fill" /> Début
|
||||
</button>
|
||||
</UiBadge>
|
||||
<UiBadge class="hover:opacity-100 hover:bg-slate-300" as-child title="Naviguer à la fin">
|
||||
</Badge>
|
||||
<Badge class="hover:opacity-100 hover:bg-slate-300" as-child title="Naviguer à la fin">
|
||||
<button
|
||||
class="flex gap-1"
|
||||
title="Naviguer à la fin"
|
||||
@click="handleJumpToDate(event.endDate!)"
|
||||
title="Naviguer à la fin"
|
||||
>
|
||||
<PhHourglassLow size="16" weight="fill" /> Fin
|
||||
</button>
|
||||
</UiBadge>
|
||||
</Badge>
|
||||
</nav>
|
||||
|
||||
<nav class="mt-2 flex gap-2">
|
||||
<UiBadge class="hover:opacity-100 hover:bg-slate-300" as-child>
|
||||
<Badge class="hover:opacity-100 hover:bg-slate-300" as-child>
|
||||
<button
|
||||
class="flex gap-1"
|
||||
title="Évènement précédent"
|
||||
@click="handleGotoRelativeEvent('prev')"
|
||||
title="Évènement précédent"
|
||||
>
|
||||
<PhArrowBendDoubleUpLeft size="16" weight="fill" /> Précédent
|
||||
</button>
|
||||
</UiBadge>
|
||||
<UiBadge class="hover:opacity-100 hover:bg-slate-300" as-child title="Naviguer à la fin">
|
||||
</Badge>
|
||||
<Badge class="hover:opacity-100 hover:bg-slate-300" as-child title="Naviguer à la fin">
|
||||
<button
|
||||
class="flex gap-1"
|
||||
title="Évènement suivant"
|
||||
@click="handleGotoRelativeEvent('next')"
|
||||
title="Évènement suivant"
|
||||
>
|
||||
<PhArrowBendDoubleUpRight size="16" weight="fill" /> Suivant
|
||||
</button>
|
||||
</UiBadge>
|
||||
</Badge>
|
||||
</nav>
|
||||
</UiPopoverContent>
|
||||
</PopoverContent>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { useCalendar } from '@/stores/CalendarStore'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { PhMagnifyingGlass } from '@phosphor-icons/vue'
|
||||
import CalendarMenuNav from './CalendarMenuNav.vue'
|
||||
import CalendarMenuSubnav from './CalendarMenuSubnav.vue'
|
||||
@@ -28,10 +29,10 @@ const { revealAdvancedSearch } = useCalendar()
|
||||
|
||||
<menu class="flex items-center gap-2">
|
||||
<li>
|
||||
<UiButton search-slash @click="revealAdvancedSearch()">
|
||||
<Button search-slash @click="revealAdvancedSearch()">
|
||||
<PhMagnifyingGlass size="20" weight="light" />
|
||||
Recherche avancée
|
||||
</UiButton>
|
||||
</Button>
|
||||
</li>
|
||||
<li>
|
||||
<CalendarSwitch />
|
||||
@@ -2,12 +2,14 @@
|
||||
import { computed, type ComputedRef } from 'vue'
|
||||
import { useCalendar } from '@/stores/CalendarStore'
|
||||
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import {
|
||||
PhCaretDoubleLeft,
|
||||
PhCaretDoubleRight,
|
||||
PhCaretLeft,
|
||||
PhCaretRight
|
||||
} from '@phosphor-icons/vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
|
||||
interface DirectionLabels {
|
||||
pastFar: string
|
||||
@@ -143,56 +145,56 @@ function toFutureFar(): void {
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-2">
|
||||
<UiTooltipProvider :delay-duration="250">
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<UiButton variant="outline" size="icon" @click="toPastFar()">
|
||||
<TooltipProvider :delayDuration="250">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="outline" size="icon" @click="toPastFar()">
|
||||
<PhCaretDoubleLeft size="18" />
|
||||
</UiButton>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{{ activeDirectionLabels.pastFar }}</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</UiTooltipProvider>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<UiTooltipProvider :delay-duration="250">
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<UiButton variant="outline" size="icon" @click="toPastNear()">
|
||||
<TooltipProvider :delayDuration="250">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="outline" size="icon" @click="toPastNear()">
|
||||
<PhCaretLeft size="18" />
|
||||
</UiButton>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{{ activeDirectionLabels.pastNear }}</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</UiTooltipProvider>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<UiTooltipProvider :delay-duration="250">
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<UiButton variant="outline" size="icon" @click="toFutureNear()">
|
||||
<TooltipProvider :delayDuration="250">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="outline" size="icon" @click="toFutureNear()">
|
||||
<PhCaretRight size="18" />
|
||||
</UiButton>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{{ activeDirectionLabels.futureNear }}</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</UiTooltipProvider>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<UiTooltipProvider :delay-duration="250">
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<UiButton variant="outline" size="icon" @click="toFutureFar()">
|
||||
<TooltipProvider :delayDuration="250">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="outline" size="icon" @click="toFutureFar()">
|
||||
<PhCaretDoubleRight size="18" />
|
||||
</UiButton>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{{ activeDirectionLabels.futureFar }}</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</UiTooltipProvider>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</template>
|
||||
@@ -3,7 +3,9 @@ import { daysPerMonth, monthsPerYear, type LeimDate } from '@/models/Date'
|
||||
import { useCalendar } from '@/stores/CalendarStore'
|
||||
import { useCalendarEvents } from '@/stores/EventStore'
|
||||
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { PhArrowLineLeft, PhArrowLineRight } from '@phosphor-icons/vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
|
||||
const { currentDate, currentConfig, jumpToDate } = useCalendar()
|
||||
const { getRelativeEventFromDate } = useCalendarEvents()
|
||||
@@ -60,54 +62,46 @@ function handleGotoPreviousEventPage(position: 'next' | 'prev' = 'next') {
|
||||
|
||||
<template>
|
||||
<div class="flex gap-2">
|
||||
<div class="grid items-end w-40 px-4 py-2 border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm text-sm">
|
||||
<ClientOnly>
|
||||
<span>{{ currentDate.currentDateTitle }}</span>
|
||||
|
||||
<template #fallback>
|
||||
<span class="inline-block">
|
||||
<UiSkeleton class="h-[19px] w-full rounded-sm" />
|
||||
</span>
|
||||
</template>
|
||||
</ClientOnly>
|
||||
<div class="w-40 px-4 py-2 border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm">
|
||||
<span class="text-sm">{{ currentDate.currentDateTitle }}</span>
|
||||
</div>
|
||||
<div class="border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm">
|
||||
<UiTooltipProvider :delay-duration="250">
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<UiButton
|
||||
<TooltipProvider :delayDuration="250">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="rounded-t-sm rounded-b-none"
|
||||
@click="handleGotoPreviousEventPage('prev')"
|
||||
>
|
||||
<PhArrowLineLeft size="22" />
|
||||
</UiButton>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Précédente page à évènements</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</UiTooltipProvider>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<div class="border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm">
|
||||
<UiTooltipProvider :delay-duration="250">
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<UiButton
|
||||
<TooltipProvider :delayDuration="250">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="rounded-t-sm rounded-b-none"
|
||||
@click="handleGotoPreviousEventPage('next')"
|
||||
>
|
||||
<PhArrowLineRight size="22" />
|
||||
</UiButton>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Prochaine page à évènements</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</UiTooltipProvider>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
33
src/components/calendar/CalendarMenuToday.vue
Normal file
33
src/components/calendar/CalendarMenuToday.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<script lang="ts" setup>
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useCalendar } from '@/stores/CalendarStore'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import Button from '../ui/button/Button.vue'
|
||||
import { areDatesIdentical } from '@/models/Date'
|
||||
|
||||
const { defaultDate, selectedDate } = storeToRefs(useCalendar())
|
||||
const { jumpToDefaultDate, getFormattedDateTitle } = useCalendar()
|
||||
|
||||
const defaultDateFormatted = getFormattedDateTitle(defaultDate.value, true)
|
||||
|
||||
const isDefaultDate = computed(() => {
|
||||
return areDatesIdentical(selectedDate.value, defaultDate.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TooltipProvider :delayDuration="250">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button @click="jumpToDefaultDate" size="sm" :disabled="isDefaultDate">
|
||||
Aujourd'hui
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{{ defaultDateFormatted }}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</template>
|
||||
42
src/components/calendar/CalendarSwitch.vue
Normal file
42
src/components/calendar/CalendarSwitch.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import { useCalendar } from '@/stores/CalendarStore'
|
||||
import { PhCalendarBlank } from '@phosphor-icons/vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import Button from '../ui/button/Button.vue'
|
||||
|
||||
const { currentConfig, viewTypeOptions, getViewTypeTitle, setViewType } = useCalendar()
|
||||
|
||||
const viewTypeTitle = computed(() => getViewTypeTitle(currentConfig.viewType))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button size="sm" variant="secondary">
|
||||
<PhCalendarBlank size="18" weight="fill" />
|
||||
|
||||
{{ viewTypeTitle }}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent :side="'bottom'" :collision-padding="30">
|
||||
<DropdownMenuLabel>Mode d'affichage</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
v-for="option in viewTypeOptions"
|
||||
:key="option"
|
||||
@click="setViewType(option)"
|
||||
>
|
||||
{{ getViewTypeTitle(option) }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
61
src/components/calendar/Sidebar.vue
Normal file
61
src/components/calendar/Sidebar.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<script lang="ts" setup>
|
||||
import { useCalendar } from '@/stores/CalendarStore'
|
||||
|
||||
import { PhList, PhHouse, PhMagnifyingGlass } from '@phosphor-icons/vue'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import Button from '../ui/button/Button.vue'
|
||||
import UserCTA from '../global/user/CTA.vue'
|
||||
|
||||
const { revealAdvancedSearch } = useCalendar()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="w-16 py-6 border-r-[1px] border-l-slate-500 grid grid-rows-[1fr_auto] justify-center">
|
||||
<menu class="flex flex-col gap-4">
|
||||
<li class="mb-12">
|
||||
<Button variant="ghost" size="icon" @click="console.log" class="rounded-full">
|
||||
<PhList size="27" />
|
||||
</Button>
|
||||
</li>
|
||||
<li>
|
||||
<TooltipProvider :delayDuration="100">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="ghost" size="icon" class="rounded-full" as-child>
|
||||
<RouterLink to="/">
|
||||
<PhHouse size="24" weight="fill" />
|
||||
</RouterLink>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent :side="'right'">
|
||||
<p>Retourner aux outils</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</li>
|
||||
<li>
|
||||
<TooltipProvider :delayDuration="100">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="rounded-full"
|
||||
@click="revealAdvancedSearch()"
|
||||
>
|
||||
<PhMagnifyingGlass size="24" weight="fill" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent :side="'right'">
|
||||
<p>Recherche avancée</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</li>
|
||||
</menu>
|
||||
|
||||
<div>
|
||||
<UserCTA />
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
@@ -17,8 +17,31 @@ import { useCalendarEvents } from '@/stores/EventStore'
|
||||
import { capitalize } from '@/utils/Strings'
|
||||
import { useMagicKeys, useScroll, useStorage, whenever } from '@vueuse/core'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { searchUnifier, type SearchMode } from '../SearchMode'
|
||||
import { searchUnifier, type SearchMode } from '../Search'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { CommandEmpty, CommandGroup, CommandItem, CommandList } from '@/components/ui/command'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import {
|
||||
Pagination,
|
||||
PaginationEllipsis,
|
||||
PaginationFirst,
|
||||
PaginationLast,
|
||||
PaginationList,
|
||||
PaginationListItem,
|
||||
PaginationNext,
|
||||
PaginationPrev
|
||||
} from '@/components/ui/pagination'
|
||||
import {
|
||||
TagsInput,
|
||||
TagsInputInput,
|
||||
TagsInputItem,
|
||||
TagsInputItemDelete,
|
||||
TagsInputItemText
|
||||
} from '@/components/ui/tags-input'
|
||||
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { PhClockClockwise, PhClockCounterClockwise, PhMagnifyingGlass } from '@phosphor-icons/vue'
|
||||
import {
|
||||
ComboboxAnchor,
|
||||
@@ -30,10 +53,10 @@ import {
|
||||
|
||||
import SearchList from './lists/SearchList.vue'
|
||||
|
||||
const { characters } = storeToRefs(useCharacters())
|
||||
const { allEvents } = storeToRefs(useCalendarEvents())
|
||||
const { characters } = useCharacters()
|
||||
const { allEvents } = useCalendarEvents()
|
||||
|
||||
const modalOpen = defineModel<boolean>({ default: false })
|
||||
const modalOpen = defineModel({ default: false })
|
||||
|
||||
const searchQuery = ref<string>('')
|
||||
// const searchEnough = computed<boolean>(() => searchQuery.value.length >= 2)
|
||||
@@ -73,11 +96,11 @@ const searchResults = computed<(Character | CalendarEvent)[]>(() => {
|
||||
// Assign data to loop over and filter
|
||||
// They are assigned this way for readability
|
||||
if (selectedEntity.value === 'events') {
|
||||
dataToFilter = allEvents.value
|
||||
dataToFilter = allEvents
|
||||
} else if (selectedEntity.value === 'characters') {
|
||||
dataToFilter = characters.value
|
||||
dataToFilter = characters
|
||||
} else {
|
||||
dataToFilter = [...allEvents.value, ...characters.value]
|
||||
dataToFilter = [...allEvents, ...characters]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +128,7 @@ const searchResults = computed<(Character | CalendarEvent)[]>(() => {
|
||||
|
||||
// Handle categories logic
|
||||
let hitCategories: boolean = false
|
||||
const allCategories: CalendarEventCategory[] = []
|
||||
let allCategories: CalendarEventCategory[] = []
|
||||
|
||||
if (item.category) {
|
||||
allCategories.push(item.category)
|
||||
@@ -139,7 +162,7 @@ const searchResults = computed<(Character | CalendarEvent)[]>(() => {
|
||||
|
||||
// Handle categories logic
|
||||
let hitCategories: boolean = false
|
||||
const allCategories: CharacterCategory[] = []
|
||||
let allCategories: CharacterCategory[] = []
|
||||
|
||||
if (item.category) {
|
||||
allCategories.push(item.category)
|
||||
@@ -170,16 +193,16 @@ function resetSearch() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the search Uidialog
|
||||
* Opens the search dialog
|
||||
*/
|
||||
function openUiDialog() {
|
||||
function openDialog() {
|
||||
modalOpen.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the search Uidialog
|
||||
* Closes the search dialog
|
||||
*/
|
||||
function closeUiDialog() {
|
||||
function closeDialog() {
|
||||
modalOpen.value = false
|
||||
}
|
||||
|
||||
@@ -195,13 +218,13 @@ function handleEntitySwitch() {
|
||||
const keys = useMagicKeys()
|
||||
|
||||
whenever(keys.control_period, () => {
|
||||
openUiDialog()
|
||||
openDialog()
|
||||
})
|
||||
|
||||
const searchResultsRef = ref<HTMLElement | null>(null)
|
||||
const { y: searchResultsY } = useScroll(searchResultsRef)
|
||||
|
||||
watch([currentPage, selectedEntity], () => {
|
||||
watch(currentPage, () => {
|
||||
searchResultsY.value = 0
|
||||
})
|
||||
|
||||
@@ -227,10 +250,10 @@ const filteredCategories = computed(() =>
|
||||
*
|
||||
* @param e Radix Change Event
|
||||
*/
|
||||
function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
|
||||
if (typeof e === 'string') {
|
||||
function handleCategorySelect(e: any) {
|
||||
if (typeof e.detail.value === 'string') {
|
||||
searchCategory.value = ''
|
||||
selectedCategories.value.push(e)
|
||||
selectedCategories.value.push(e.detail.value)
|
||||
}
|
||||
|
||||
if (filteredCategories.value.length === 0) {
|
||||
@@ -240,31 +263,31 @@ function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiDialog v-model:open="modalOpen" @update:open="resetSearch()">
|
||||
<UiDialogContent
|
||||
<Dialog v-model:open="modalOpen" @update:open="resetSearch()">
|
||||
<DialogContent
|
||||
class="flex flex-col flex-nowrap top-16 -translate-y-0 data-[state=closed]:slide-out-to-top-[5%] data-[state=open]:slide-in-from-top-[5%]"
|
||||
:class="{
|
||||
'bottom-16': searchResults.length > 0
|
||||
}"
|
||||
>
|
||||
<VisuallyHidden>
|
||||
<UiDialogTitle> Recherche avancée </UiDialogTitle>
|
||||
<DialogTitle> Recherche avancée </DialogTitle>
|
||||
</VisuallyHidden>
|
||||
<VisuallyHidden>
|
||||
<UiDialogDescription>
|
||||
<DialogDescription>
|
||||
Rechercher les données disponibles sur le calendrier
|
||||
</UiDialogDescription>
|
||||
</DialogDescription>
|
||||
</VisuallyHidden>
|
||||
|
||||
<!-- UiDialog header -->
|
||||
<div id="searchForm" class="grid gap-3">
|
||||
<!-- Dialog header -->
|
||||
<div class="grid gap-3" id="searchForm">
|
||||
<div class="relative w-full h-fit">
|
||||
<UiInput
|
||||
<Input
|
||||
id="search"
|
||||
v-model:model-value="searchQuery"
|
||||
type="text"
|
||||
placeholder="Rechercher le calendrier"
|
||||
class="pl-10 py-6 text-lg"
|
||||
v-model:model-value="searchQuery"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<span class="absolute start-1 inset-y-0 flex items-center justify-center px-2 opacity-50">
|
||||
@@ -274,28 +297,28 @@ function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<UiToggleGroup
|
||||
v-model="selectedEntity"
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
class="justify-start"
|
||||
v-model="selectedEntity"
|
||||
@update:model-value="handleEntitySwitch()"
|
||||
>
|
||||
<UiToggleGroupItem value="events" aria-label="Uniquement les évènements">
|
||||
<ToggleGroupItem value="events" aria-label="Uniquement les évènements" v-once>
|
||||
Évènements
|
||||
</UiToggleGroupItem>
|
||||
<UiToggleGroupItem value="characters" aria-label="Uniquement les personnages">
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value="characters" aria-label="Uniquement les personnages" v-once>
|
||||
Personnages
|
||||
</UiToggleGroupItem>
|
||||
</UiToggleGroup>
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1">
|
||||
<UiTagsInput class="px-0 gap-0 w-72" :model-value="selectedCategories">
|
||||
<TagsInput class="px-0 gap-0 w-72" :model-value="selectedCategories">
|
||||
<div class="flex gap-2 flex-wrap items-center px-3">
|
||||
<UiTagsInputItem v-for="item in selectedCategories" :key="item" :value="item">
|
||||
<UiTagsInputItemText class="capitalize" />
|
||||
<UiTagsInputItemDelete />
|
||||
</UiTagsInputItem>
|
||||
<TagsInputItem v-for="item in selectedCategories" :key="item" :value="item">
|
||||
<TagsInputItemText class="capitalize" />
|
||||
<TagsInputItemDelete />
|
||||
</TagsInputItem>
|
||||
</div>
|
||||
|
||||
<ComboboxRoot
|
||||
@@ -306,7 +329,7 @@ function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
|
||||
>
|
||||
<ComboboxAnchor as-child>
|
||||
<ComboboxInput placeholder="Catégories" as-child>
|
||||
<UiTagsInputInput
|
||||
<TagsInputInput
|
||||
class="w-full px-3"
|
||||
:class="selectedCategories.length > 0 ? 'mt-2' : ''"
|
||||
@keydown.enter.prevent
|
||||
@@ -315,79 +338,79 @@ function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
|
||||
</ComboboxAnchor>
|
||||
|
||||
<ComboboxPortal :to="'#searchForm'">
|
||||
<UiCommandList
|
||||
<CommandList
|
||||
position="popper"
|
||||
class="w-[--radix-popper-anchor-width] rounded-md mt-2 border bg-popover text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50"
|
||||
:dismissable="true"
|
||||
>
|
||||
<UiCommandEmpty />
|
||||
<UiCommandGroup>
|
||||
<UiCommandItem
|
||||
v-for="cat in filteredCategories"
|
||||
:key="cat"
|
||||
:value="cat"
|
||||
@select.prevent="handleCategorySelect(cat)"
|
||||
<CommandEmpty />
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
v-for="framework in filteredCategories"
|
||||
:key="framework"
|
||||
:value="framework"
|
||||
@select.prevent="handleCategorySelect"
|
||||
>
|
||||
{{ capitalize(cat) }}
|
||||
</UiCommandItem>
|
||||
</UiCommandGroup>
|
||||
</UiCommandList>
|
||||
{{ capitalize(framework) }}
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</ComboboxPortal>
|
||||
</ComboboxRoot>
|
||||
</UiTagsInput>
|
||||
</TagsInput>
|
||||
|
||||
<UiTooltipProvider :delay-duration="250">
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<UiButton
|
||||
<TooltipProvider :delayDuration="250">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button
|
||||
:variant="selectedOrder === 'desc' ? 'secondary' : 'outline'"
|
||||
size="icon"
|
||||
@click="setOrderDesc()"
|
||||
>
|
||||
<PhClockCounterClockwise size="18" />
|
||||
</UiButton>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Plus ancien</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</UiTooltipProvider>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<UiTooltipProvider :delay-duration="250">
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<UiButton
|
||||
<TooltipProvider :delayDuration="250">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button
|
||||
:variant="selectedOrder === 'asc' ? 'secondary' : 'outline'"
|
||||
size="icon"
|
||||
@click="setOrderAsc()"
|
||||
>
|
||||
<PhClockClockwise size="18" />
|
||||
</UiButton>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Plus récent</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</UiTooltipProvider>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr >
|
||||
<hr />
|
||||
|
||||
<div v-if="searchResults.length > 0" ref="searchResultsRef" class="grow overflow-y-auto">
|
||||
<div v-if="searchResults.length > 0" class="grow overflow-y-auto" ref="searchResultsRef">
|
||||
<SearchList
|
||||
:results="searchResults"
|
||||
:current-entity="selectedEntity"
|
||||
:order="selectedOrder"
|
||||
:start-at="startOfList"
|
||||
:end-at="endOfList"
|
||||
@jumped-to-date="closeUiDialog()"
|
||||
@jumped-to-date="closeDialog()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<UiPagination
|
||||
<Pagination
|
||||
v-model:page="currentPage"
|
||||
:total="searchResults.length"
|
||||
:items-per-page="itemsPerPage"
|
||||
@@ -395,32 +418,32 @@ function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
|
||||
show-edges
|
||||
:default-page="1"
|
||||
>
|
||||
<UiPaginationList v-slot="{ items }" class="flex items-center gap-1">
|
||||
<UiPaginationFirst />
|
||||
<UiPaginationPrev />
|
||||
<PaginationList v-slot="{ items }" class="flex items-center gap-1">
|
||||
<PaginationFirst />
|
||||
<PaginationPrev />
|
||||
|
||||
<template v-for="(item, index) in items">
|
||||
<UiPaginationListItem
|
||||
<PaginationListItem
|
||||
v-if="item.type === 'page'"
|
||||
:key="index"
|
||||
:value="item.value"
|
||||
as-child
|
||||
>
|
||||
<UiButton
|
||||
<Button
|
||||
class="w-10 h-10 p-0"
|
||||
:variant="item.value === currentPage ? 'default' : 'outline'"
|
||||
>
|
||||
{{ item.value }}
|
||||
</UiButton>
|
||||
</UiPaginationListItem>
|
||||
<UiPaginationEllipsis v-else :key="item.type" :index="index" />
|
||||
</Button>
|
||||
</PaginationListItem>
|
||||
<PaginationEllipsis v-else :key="item.type" :index="index" />
|
||||
</template>
|
||||
|
||||
<UiPaginationNext />
|
||||
<UiPaginationLast />
|
||||
</UiPaginationList>
|
||||
</UiPagination>
|
||||
<PaginationNext />
|
||||
<PaginationLast />
|
||||
</PaginationList>
|
||||
</Pagination>
|
||||
</div>
|
||||
</UiDialogContent>
|
||||
</UiDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -37,13 +37,13 @@ const { getFormattedDateTitle } = useCalendar()
|
||||
|
||||
<menu class="flex gap-2 border-[1px] border-slate-700 rounded-sm w-fit">
|
||||
<li v-if="character.birth">
|
||||
<TooltipProvider :delay-duration="100">
|
||||
<TooltipProvider :delayDuration="100">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button
|
||||
@click="$emit('query:date-jump', character.birth!)"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
@click="$emit('query:date-jump', character.birth!)"
|
||||
>
|
||||
<PhPlant size="16" weight="fill" />
|
||||
{{ getFormattedDateTitle(character.birth, true) }}
|
||||
@@ -57,13 +57,13 @@ const { getFormattedDateTitle } = useCalendar()
|
||||
</li>
|
||||
<span v-if="character.birth && character.death">-</span>
|
||||
<li v-if="character.death">
|
||||
<TooltipProvider :delay-duration="100">
|
||||
<TooltipProvider :delayDuration="100">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button
|
||||
@click="$emit('query:date-jump', character.death!)"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
@click="$emit('query:date-jump', character.death!)"
|
||||
>
|
||||
<PhSkull size="16" weight="fill" />
|
||||
{{ getFormattedDateTitle(character.death, true) }}
|
||||
@@ -77,7 +77,7 @@ const { getFormattedDateTitle } = useCalendar()
|
||||
</li>
|
||||
</menu>
|
||||
|
||||
<hr v-if="character.description" class="border-white opacity-25" >
|
||||
<hr v-if="character.description" class="border-white opacity-25" />
|
||||
|
||||
<div v-if="character.description" class="text-sm">
|
||||
<span class="opacity-75">
|
||||
@@ -105,7 +105,7 @@ const dateDuration: string | null = props.event.endDate
|
||||
</div>
|
||||
|
||||
<div v-if="event.description" class="text-sm">
|
||||
<hr class="my-2 border-white opacity-50" >
|
||||
<hr class="my-2 border-white opacity-50" />
|
||||
<span class="opacity-75">
|
||||
{{ event.description }}
|
||||
</span>
|
||||
@@ -3,11 +3,12 @@ import { isCharacter, type Character } from '@/models/Characters'
|
||||
import { compareDates, type LeimDate, type LeimDateOrder } from '@/models/Date'
|
||||
import { isCalendarEvent, type CalendarEvent } from '@/models/Events'
|
||||
import { useCalendar } from '@/stores/CalendarStore'
|
||||
import { computed } from 'vue'
|
||||
import type { SearchMode } from '../../SearchMode'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { SearchMode } from '../../Search'
|
||||
|
||||
import CharacterCallout from './CharacterCallout.vue'
|
||||
import EventCallout from './EventCallout.vue'
|
||||
import { useScroll } from '@vueuse/core'
|
||||
|
||||
const props = defineProps<{
|
||||
results: (Character | CalendarEvent)[]
|
||||
@@ -6,6 +6,7 @@ import { useElementBounding } from '@vueuse/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, ref, type ComputedRef } from 'vue'
|
||||
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import CalendarEventButton from '../../CalendarEvent.vue'
|
||||
import type { CalendarEvent } from '@/models/Events'
|
||||
|
||||
@@ -73,7 +74,6 @@ const eventsNotDisplayed = computed(
|
||||
class="relative z-10 group block w-full text-center cursor-pointer"
|
||||
@click="selectDate(date)"
|
||||
>
|
||||
<ClientOnly>
|
||||
<span
|
||||
class="inline-flex w-8 h-8 aspect-square items-center justify-center rounded-full border-2 border-transparent font-bold transition-colors group-hover:border-slate-800"
|
||||
:class="{
|
||||
@@ -83,7 +83,6 @@ const eventsNotDisplayed = computed(
|
||||
>
|
||||
{{ date.day }}
|
||||
</span>
|
||||
</ClientOnly>
|
||||
</button>
|
||||
|
||||
<ul
|
||||
@@ -98,15 +97,15 @@ const eventsNotDisplayed = computed(
|
||||
</li>
|
||||
|
||||
<li v-if="eventsNotDisplayed > 0" class="pointer-events-auto">
|
||||
<UiPopover>
|
||||
<UiPopoverTrigger as-child>
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<button
|
||||
class="text-xs px-2 py-1 block w-full text-left font-bold rounded-sm whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer transition-colors hover:bg-slate-800"
|
||||
>
|
||||
{{ eventsNotDisplayed }} autre{{ eventsNotDisplayed > 1 ? 's' : '' }}
|
||||
</button>
|
||||
</UiPopoverTrigger>
|
||||
<UiPopoverContent class="w-80" :align="'center'" :side="'right'">
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-80" :align="'center'" :side="'right'">
|
||||
<div class="text-center mb-4">
|
||||
<span
|
||||
class="inline-flex w-12 h-12 aspect-square items-center justify-center text-lg font-semibold text-slate-300 bg-slate-800 rounded-full"
|
||||
@@ -123,8 +122,8 @@ const eventsNotDisplayed = computed(
|
||||
<CalendarEventButton :event :tile-date="date" />
|
||||
</li>
|
||||
</ul>
|
||||
</UiPopoverContent>
|
||||
</UiPopover>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -26,7 +26,7 @@ const moveCalendarRight = useThrottleFn(() => {
|
||||
|
||||
<template>
|
||||
<div class="container mt-[10vh] mb-auto" @wheel="handleWheel">
|
||||
<div class="grid grid-cols-5 gap-x-8 gap-y-16">
|
||||
<div ref="test" class="grid grid-cols-5 gap-x-8 gap-y-16">
|
||||
<MonthTile
|
||||
v-for="month in staticConfig.monthsPerYear"
|
||||
:key="month"
|
||||
54
src/components/global/user/CTA.vue
Normal file
54
src/components/global/user/CTA.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<script lang="ts" setup>
|
||||
import { supabase } from '@/supabase/client'
|
||||
import { type Session } from '@supabase/supabase-js'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { PhUserCircle } from '@phosphor-icons/vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import MenuLoggedIn from './MenuLoggedIn.vue'
|
||||
import MenuLoggedOut from './MenuLoggedOut.vue'
|
||||
|
||||
const session = ref<Session | null>()
|
||||
const hasSession = computed(() => Boolean(session.value))
|
||||
const menuOpened = ref<boolean>(false)
|
||||
|
||||
onMounted(() => {
|
||||
supabase.auth.getSession().then(({ data }) => {
|
||||
session.value = data.session
|
||||
})
|
||||
|
||||
supabase.auth.onAuthStateChange((_, _session) => {
|
||||
session.value = _session
|
||||
})
|
||||
})
|
||||
|
||||
watch(session, closeMenu)
|
||||
|
||||
function closeMenu() {
|
||||
menuOpened.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Popover v-model:open="menuOpened">
|
||||
<PopoverTrigger :as-child="hasSession">
|
||||
<Avatar v-if="session" class="cursor-pointer">
|
||||
<AvatarImage
|
||||
:src="session?.user.user_metadata.avatar_url"
|
||||
:alt="session?.user.user_metadata.full_name"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Button v-else variant="outline" size="icon">
|
||||
<PhUserCircle size="18" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-fit p-0" :align="'start'" :side="'top'" :collision-padding="20">
|
||||
<MenuLoggedIn v-if="session" :session />
|
||||
<MenuLoggedOut v-else />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</template>
|
||||
37
src/components/global/user/MenuLoggedIn.vue
Normal file
37
src/components/global/user/MenuLoggedIn.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script lang="ts" setup>
|
||||
import { supabase } from '@/supabase/client'
|
||||
import { Command, CommandGroup, CommandItem, CommandList } from '@/components/ui/command'
|
||||
import type { Session } from '@supabase/supabase-js'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
defineProps<{
|
||||
session: Session
|
||||
}>()
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
async function handleLogout() {
|
||||
try {
|
||||
const { error } = await supabase.auth.signOut()
|
||||
|
||||
if (error) throw error
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
function gotoProfilePage() {
|
||||
router.push({ path: '/profil' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Command>
|
||||
<CommandList>
|
||||
<CommandGroup :heading="`Connecté en tant que ${session.user.user_metadata.email}`">
|
||||
<CommandItem value="profile" @select="gotoProfilePage"> Profil </CommandItem>
|
||||
<CommandItem value="logout" @select="handleLogout"> Déconnexion </CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</template>
|
||||
33
src/components/global/user/MenuLoggedOut.vue
Normal file
33
src/components/global/user/MenuLoggedOut.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<script lang="ts" setup>
|
||||
import { supabase } from '@/supabase/client'
|
||||
import { Command, CommandItem, CommandList } from '@/components/ui/command'
|
||||
import type { Session } from '@supabase/supabase-js'
|
||||
|
||||
defineProps<{
|
||||
session?: Session | null
|
||||
}>()
|
||||
|
||||
async function handleGoogleLogin() {
|
||||
try {
|
||||
supabase.auth.signInWithOAuth({
|
||||
provider: 'google',
|
||||
options: {
|
||||
queryParams: {
|
||||
access_type: 'offline',
|
||||
prompt: 'consent'
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Command>
|
||||
<CommandList>
|
||||
<CommandItem value="logout" @select="handleGoogleLogin"> Connexion </CommandItem>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</template>
|
||||
@@ -7,19 +7,18 @@ export const badgeVariants = cva(
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
|
||||
default: 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
|
||||
secondary:
|
||||
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
destructive:
|
||||
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
|
||||
outline: 'text-foreground',
|
||||
},
|
||||
outline: 'text-foreground'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
},
|
||||
variant: 'default'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export type BadgeVariants = VariantProps<typeof badgeVariants>
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { RouterLink } from 'vue-router'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
@@ -19,7 +20,7 @@ const props = defineProps<{
|
||||
>
|
||||
<slot />
|
||||
|
||||
<NuxtLink
|
||||
<RouterLink
|
||||
v-if="props.link"
|
||||
:to="props.link"
|
||||
class="absolute inset-0 focus-visible:outline-none"
|
||||
@@ -8,7 +8,7 @@ const props = defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('p-6 pt-0', props.class)">
|
||||
<div :class="cn('p-6 pt-0 text-muted-foreground', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -8,7 +8,7 @@ const props = defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('text-sm text-muted-foreground', props.class)">
|
||||
<p :class="cn('text-sm text-muted-foreground mt-1', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</p>
|
||||
</template>
|
||||
@@ -8,7 +8,7 @@ const props = defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('flex flex-col gap-y-1.5 p-6', props.class)">
|
||||
<div :class="cn('flex flex-col gap-y-1.5 px-6 pt-6 pb-3', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -8,11 +8,7 @@ const props = defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h3
|
||||
:class="
|
||||
cn('text-2xl font-semibold leading-none tracking-tight', props.class)
|
||||
"
|
||||
>
|
||||
<h3 :class="cn('text-2xl font-semibold leading-none tracking-tight', props.class)">
|
||||
<slot />
|
||||
</h3>
|
||||
</template>
|
||||
@@ -1,5 +1,3 @@
|
||||
export { ComboboxPortal } from 'radix-vue'
|
||||
|
||||
export { default as Command } from './Command.vue'
|
||||
export { default as CommandDialog } from './CommandDialog.vue'
|
||||
export { default as CommandEmpty } from './CommandEmpty.vue'
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user