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
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
# Misc
|
node_modules
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.fleet
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
.idea
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
# Local env files
|
|
||||||
.env
|
.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.
|
## 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.
|
||||||
## 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.
|
|
||||||
|
|||||||
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,
|
"typescript": true,
|
||||||
"tailwind": {
|
"tailwind": {
|
||||||
"config": "tailwind.config.js",
|
"config": "tailwind.config.js",
|
||||||
"css": "assets/main.css",
|
"css": "src/assets/main.css",
|
||||||
"baseColor": "slate",
|
"baseColor": "slate",
|
||||||
"cssVariables": true
|
"cssVariables": true
|
||||||
},
|
},
|
||||||
"framework": "nuxt",
|
"framework": "vite",
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"components": "@/components",
|
"components": "@/components",
|
||||||
"utils": "@/lib/utils"
|
"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,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt build",
|
"dev": "vite",
|
||||||
"dev": "nuxt dev",
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
"generate": "nuxt generate",
|
"preview": "vite preview",
|
||||||
"preview": "nuxt preview",
|
"test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'",
|
||||||
"postinstall": "nuxt prepare",
|
"test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'",
|
||||||
"lint": "eslint .",
|
"test:unit": "cypress run --component",
|
||||||
"lint:fix": "eslint . --fix",
|
"test:unit:dev": "cypress open --component",
|
||||||
"lint:prettier": "prettier . --check"
|
"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": {
|
"dependencies": {
|
||||||
"@nuxt/eslint": "^0.3.12",
|
|
||||||
"@phosphor-icons/vue": "^2.2.1",
|
"@phosphor-icons/vue": "^2.2.1",
|
||||||
"@pinia/nuxt": "^0.5.1",
|
"@supabase/supabase-js": "^2.43.1",
|
||||||
"@vueuse/core": "^10.9.0",
|
"@vueuse/core": "^10.9.0",
|
||||||
"@vueuse/nuxt": "^10.9.0",
|
|
||||||
"class-variance-authority": "^0.7.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",
|
"lucide-vue-next": "^0.364.0",
|
||||||
"nuxt": "^3.7.0",
|
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"radix-vue": "^1.7.4",
|
"radix-vue": "^1.6.1",
|
||||||
"shadcn-nuxt": "^0.10.4",
|
"tailwind-merge": "^2.2.2",
|
||||||
"tailwind-merge": "^2.3.0",
|
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"vue": "^3.4.27",
|
"vue": "^3.4.21",
|
||||||
"vue-router": "^4.3.2"
|
"vue-router": "^4.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxtjs/color-mode": "^3.4.1",
|
"@rushstack/eslint-patch": "^1.3.3",
|
||||||
"@nuxtjs/supabase": "^1.2.2",
|
"@tsconfig/node20": "^20.1.2",
|
||||||
"@nuxtjs/tailwindcss": "^6.12.0",
|
"@types/node": "^20.11.28",
|
||||||
"@stylistic/eslint-plugin-js": "^2.1.0",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
"@vue/eslint-config-prettier": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^7.8.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": "^8.49.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-plugin-cypress": "^2.15.1",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
"eslint-plugin-vue": "^9.26.0",
|
"npm-run-all2": "^6.1.2",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.0.3",
|
||||||
"sass": "^1.72.0",
|
"sass": "^1.72.0",
|
||||||
|
"start-server-and-test": "^2.0.3",
|
||||||
"tailwindcss": "^3.4.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,7 +2,8 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
:root {
|
@layer base {
|
||||||
|
:root {
|
||||||
--background: 0 0% 100%;
|
--background: 0 0% 100%;
|
||||||
--foreground: 222.2 84% 4.9%;
|
--foreground: 222.2 84% 4.9%;
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@
|
|||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root.dark {
|
.dark {
|
||||||
--background: 222.2 84% 4.9%;
|
--background: 222.2 84% 4.9%;
|
||||||
--foreground: 210 40% 98%;
|
--foreground: 210 40% 98%;
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@
|
|||||||
--destructive-foreground: 210 40% 98%;
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
|
||||||
--ring: 212.7 26.8% 83.9%;
|
--ring: 212.7 26.8% 83.9%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@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'
|
import YearLayout from './state/yearly/Layout.vue'
|
||||||
|
|
||||||
const { currentConfig } = useCalendar()
|
const { currentConfig } = useCalendar()
|
||||||
const { selectedDate, jumpToDate } = useCalendar()
|
|
||||||
|
|
||||||
const { fetchEvents } = useCalendarEvents()
|
|
||||||
fetchEvents()
|
|
||||||
|
|
||||||
const currentViewComponent: ComputedRef<Component> = computed<Component>(() => {
|
const currentViewComponent: ComputedRef<Component> = computed<Component>(() => {
|
||||||
switch (currentConfig.viewType) {
|
switch (currentConfig.viewType) {
|
||||||
@@ -31,16 +27,11 @@ const currentViewComponent: ComputedRef<Component> = computed<Component>(() => {
|
|||||||
return CenturyLayout
|
return CenturyLayout
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
jumpToDate(selectedDate)
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-full grid grid-rows-[auto,1fr]">
|
<div class="h-full grid grid-rows-[auto,1fr]">
|
||||||
<CalendarMenu />
|
<CalendarMenu />
|
||||||
|
|
||||||
<KeepAlive>
|
<KeepAlive>
|
||||||
<component :is="currentViewComponent" />
|
<component :is="currentViewComponent" />
|
||||||
</KeepAlive>
|
</KeepAlive>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { getRelativeString } from '@/models/Date'
|
import { getRelativeString } from '@/models/Date'
|
||||||
|
import { useCalendar } from '@/stores/CalendarStore'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
@@ -14,19 +15,10 @@ const dateDifference = computed(() => getRelativeString(defaultDate, selectedDat
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ClientOnly>
|
|
||||||
<h1 class="text-2xl font-bold flex items-center gap-1">
|
<h1 class="text-2xl font-bold flex items-center gap-1">
|
||||||
<PhMapPin size="26" weight="bold" /> {{ mainDateTitle }}
|
<PhMapPin size="26" weight="bold" /> {{ mainDateTitle }}
|
||||||
</h1>
|
</h1>
|
||||||
<h2 class="text-lg italic opacity-75">
|
<h2 class="text-lg italic opacity-75">
|
||||||
{{ dateDifference }}
|
{{ dateDifference }}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<template #fallback>
|
|
||||||
<div class="grid gap-1">
|
|
||||||
<UiSkeleton class="h-8 w-64" />
|
|
||||||
<UiSkeleton class="h-6 w-28" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</ClientOnly>
|
|
||||||
</template>
|
</template>
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { areDatesIdentical, type LeimDate } from '@/models/Date'
|
import { areDatesIdentical, type LeimDate } from '@/models/Date'
|
||||||
|
|
||||||
import type { CalendarEvent } from '@/models/Events'
|
import type { CalendarEvent } from '@/models/Events'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import CalendarEventDetails from './CalendarEventDetails.vue'
|
import CalendarEventDetails from './CalendarEventDetails.vue'
|
||||||
|
import { Popover, PopoverTrigger } from '@/components/ui/popover'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
event: CalendarEvent
|
event: CalendarEvent
|
||||||
@@ -25,8 +26,8 @@ function handleClosePopover() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UiPopover v-model:open="isPopoverOpen">
|
<Popover v-model:open="isPopoverOpen">
|
||||||
<UiPopoverTrigger as-child>
|
<PopoverTrigger as-child>
|
||||||
<button
|
<button
|
||||||
class="text-xs px-2 py-1 block w-full text-left rounded-sm whitespace-nowrap overflow-hidden text-ellipsis"
|
class="text-xs px-2 py-1 block w-full text-left rounded-sm whitespace-nowrap overflow-hidden text-ellipsis"
|
||||||
:class="
|
:class="
|
||||||
@@ -57,15 +58,15 @@ function handleClosePopover() {
|
|||||||
>
|
>
|
||||||
{{ event.title }}
|
{{ event.title }}
|
||||||
</button>
|
</button>
|
||||||
</UiPopoverTrigger>
|
</PopoverTrigger>
|
||||||
|
|
||||||
<CalendarEventDetails
|
<CalendarEventDetails
|
||||||
v-once
|
|
||||||
:event
|
:event
|
||||||
:spans-multiple-days
|
:spans-multiple-days
|
||||||
:is-start-event
|
:is-start-event
|
||||||
:is-end-event
|
:is-end-event
|
||||||
@query:close-popover="handleClosePopover"
|
@query:close-popover="handleClosePopover"
|
||||||
|
v-once
|
||||||
/>
|
/>
|
||||||
</UiPopover>
|
</Popover>
|
||||||
</template>
|
</template>
|
||||||
@@ -12,6 +12,8 @@ import {
|
|||||||
PhArrowBendDoubleUpLeft,
|
PhArrowBendDoubleUpLeft,
|
||||||
PhArrowBendDoubleUpRight
|
PhArrowBendDoubleUpRight
|
||||||
} from '@phosphor-icons/vue'
|
} from '@phosphor-icons/vue'
|
||||||
|
import { Badge } from '@/components/ui/badge'
|
||||||
|
import { PopoverContent } from '@/components/ui/popover'
|
||||||
|
|
||||||
const { defaultDate, getFormattedDateTitle, jumpToDate } = useCalendar()
|
const { defaultDate, getFormattedDateTitle, jumpToDate } = useCalendar()
|
||||||
const { getRelativeEventFromEvent } = useCalendarEvents()
|
const { getRelativeEventFromEvent } = useCalendarEvents()
|
||||||
@@ -49,8 +51,8 @@ function handleGotoRelativeEvent(position: 'next' | 'prev' = 'next') {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UiPopoverContent
|
<PopoverContent
|
||||||
class="event-details w-96 bg-slate-900"
|
class="event-details w-96"
|
||||||
:align="'center'"
|
:align="'center'"
|
||||||
:align-offset="50"
|
:align-offset="50"
|
||||||
:side="'left'"
|
:side="'left'"
|
||||||
@@ -109,21 +111,21 @@ function handleGotoRelativeEvent(position: 'next' | 'prev' = 'next') {
|
|||||||
<template v-if="event.category || event.secondaryCategories">
|
<template v-if="event.category || event.secondaryCategories">
|
||||||
<ul class="flex gap-1">
|
<ul class="flex gap-1">
|
||||||
<li v-if="event.category">
|
<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 }}
|
{{ event.category }}
|
||||||
</UiBadge>
|
</Badge>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li v-for="cat in event.secondaryCategories" :key="cat">
|
<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 }}
|
{{ cat }}
|
||||||
</UiBadge>
|
</Badge>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="event.description">
|
<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">
|
<div class="mt-2 text-sm text-slate-300">
|
||||||
{{ event.description }}
|
{{ event.description }}
|
||||||
@@ -132,47 +134,47 @@ function handleGotoRelativeEvent(position: 'next' | 'prev' = 'next') {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav v-if="event.startDate && event.endDate" class="absolute -top-2 right-2 flex gap-2">
|
<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
|
<button
|
||||||
class="flex gap-1"
|
class="flex gap-1"
|
||||||
title="Naviguer au début"
|
|
||||||
@click="handleJumpToDate(event.startDate!)"
|
@click="handleJumpToDate(event.startDate!)"
|
||||||
|
title="Naviguer au début"
|
||||||
>
|
>
|
||||||
<PhHourglassHigh size="16" weight="fill" /> Début
|
<PhHourglassHigh size="16" weight="fill" /> Début
|
||||||
</button>
|
</button>
|
||||||
</UiBadge>
|
</Badge>
|
||||||
<UiBadge class="hover:opacity-100 hover:bg-slate-300" as-child title="Naviguer à la fin">
|
<Badge class="hover:opacity-100 hover:bg-slate-300" as-child title="Naviguer à la fin">
|
||||||
<button
|
<button
|
||||||
class="flex gap-1"
|
class="flex gap-1"
|
||||||
title="Naviguer à la fin"
|
|
||||||
@click="handleJumpToDate(event.endDate!)"
|
@click="handleJumpToDate(event.endDate!)"
|
||||||
|
title="Naviguer à la fin"
|
||||||
>
|
>
|
||||||
<PhHourglassLow size="16" weight="fill" /> Fin
|
<PhHourglassLow size="16" weight="fill" /> Fin
|
||||||
</button>
|
</button>
|
||||||
</UiBadge>
|
</Badge>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<nav class="mt-2 flex gap-2">
|
<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
|
<button
|
||||||
class="flex gap-1"
|
class="flex gap-1"
|
||||||
title="Évènement précédent"
|
|
||||||
@click="handleGotoRelativeEvent('prev')"
|
@click="handleGotoRelativeEvent('prev')"
|
||||||
|
title="Évènement précédent"
|
||||||
>
|
>
|
||||||
<PhArrowBendDoubleUpLeft size="16" weight="fill" /> Précédent
|
<PhArrowBendDoubleUpLeft size="16" weight="fill" /> Précédent
|
||||||
</button>
|
</button>
|
||||||
</UiBadge>
|
</Badge>
|
||||||
<UiBadge class="hover:opacity-100 hover:bg-slate-300" as-child title="Naviguer à la fin">
|
<Badge class="hover:opacity-100 hover:bg-slate-300" as-child title="Naviguer à la fin">
|
||||||
<button
|
<button
|
||||||
class="flex gap-1"
|
class="flex gap-1"
|
||||||
title="Évènement suivant"
|
|
||||||
@click="handleGotoRelativeEvent('next')"
|
@click="handleGotoRelativeEvent('next')"
|
||||||
|
title="Évènement suivant"
|
||||||
>
|
>
|
||||||
<PhArrowBendDoubleUpRight size="16" weight="fill" /> Suivant
|
<PhArrowBendDoubleUpRight size="16" weight="fill" /> Suivant
|
||||||
</button>
|
</button>
|
||||||
</UiBadge>
|
</Badge>
|
||||||
</nav>
|
</nav>
|
||||||
</UiPopoverContent>
|
</PopoverContent>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useCalendar } from '@/stores/CalendarStore'
|
import { useCalendar } from '@/stores/CalendarStore'
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
import { PhMagnifyingGlass } from '@phosphor-icons/vue'
|
import { PhMagnifyingGlass } from '@phosphor-icons/vue'
|
||||||
import CalendarMenuNav from './CalendarMenuNav.vue'
|
import CalendarMenuNav from './CalendarMenuNav.vue'
|
||||||
import CalendarMenuSubnav from './CalendarMenuSubnav.vue'
|
import CalendarMenuSubnav from './CalendarMenuSubnav.vue'
|
||||||
@@ -28,10 +29,10 @@ const { revealAdvancedSearch } = useCalendar()
|
|||||||
|
|
||||||
<menu class="flex items-center gap-2">
|
<menu class="flex items-center gap-2">
|
||||||
<li>
|
<li>
|
||||||
<UiButton search-slash @click="revealAdvancedSearch()">
|
<Button search-slash @click="revealAdvancedSearch()">
|
||||||
<PhMagnifyingGlass size="20" weight="light" />
|
<PhMagnifyingGlass size="20" weight="light" />
|
||||||
Recherche avancée
|
Recherche avancée
|
||||||
</UiButton>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<CalendarSwitch />
|
<CalendarSwitch />
|
||||||
@@ -2,12 +2,14 @@
|
|||||||
import { computed, type ComputedRef } from 'vue'
|
import { computed, type ComputedRef } from 'vue'
|
||||||
import { useCalendar } from '@/stores/CalendarStore'
|
import { useCalendar } from '@/stores/CalendarStore'
|
||||||
|
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||||
import {
|
import {
|
||||||
PhCaretDoubleLeft,
|
PhCaretDoubleLeft,
|
||||||
PhCaretDoubleRight,
|
PhCaretDoubleRight,
|
||||||
PhCaretLeft,
|
PhCaretLeft,
|
||||||
PhCaretRight
|
PhCaretRight
|
||||||
} from '@phosphor-icons/vue'
|
} from '@phosphor-icons/vue'
|
||||||
|
import Button from '@/components/ui/button/Button.vue'
|
||||||
|
|
||||||
interface DirectionLabels {
|
interface DirectionLabels {
|
||||||
pastFar: string
|
pastFar: string
|
||||||
@@ -143,56 +145,56 @@ function toFutureFar(): void {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<UiTooltipProvider :delay-duration="250">
|
<TooltipProvider :delayDuration="250">
|
||||||
<UiTooltip>
|
<Tooltip>
|
||||||
<UiTooltipTrigger as-child>
|
<TooltipTrigger as-child>
|
||||||
<UiButton variant="outline" size="icon" @click="toPastFar()">
|
<Button variant="outline" size="icon" @click="toPastFar()">
|
||||||
<PhCaretDoubleLeft size="18" />
|
<PhCaretDoubleLeft size="18" />
|
||||||
</UiButton>
|
</Button>
|
||||||
</UiTooltipTrigger>
|
</TooltipTrigger>
|
||||||
<UiTooltipContent>
|
<TooltipContent>
|
||||||
<p>{{ activeDirectionLabels.pastFar }}</p>
|
<p>{{ activeDirectionLabels.pastFar }}</p>
|
||||||
</UiTooltipContent>
|
</TooltipContent>
|
||||||
</UiTooltip>
|
</Tooltip>
|
||||||
</UiTooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
||||||
<UiTooltipProvider :delay-duration="250">
|
<TooltipProvider :delayDuration="250">
|
||||||
<UiTooltip>
|
<Tooltip>
|
||||||
<UiTooltipTrigger as-child>
|
<TooltipTrigger as-child>
|
||||||
<UiButton variant="outline" size="icon" @click="toPastNear()">
|
<Button variant="outline" size="icon" @click="toPastNear()">
|
||||||
<PhCaretLeft size="18" />
|
<PhCaretLeft size="18" />
|
||||||
</UiButton>
|
</Button>
|
||||||
</UiTooltipTrigger>
|
</TooltipTrigger>
|
||||||
<UiTooltipContent>
|
<TooltipContent>
|
||||||
<p>{{ activeDirectionLabels.pastNear }}</p>
|
<p>{{ activeDirectionLabels.pastNear }}</p>
|
||||||
</UiTooltipContent>
|
</TooltipContent>
|
||||||
</UiTooltip>
|
</Tooltip>
|
||||||
</UiTooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
||||||
<UiTooltipProvider :delay-duration="250">
|
<TooltipProvider :delayDuration="250">
|
||||||
<UiTooltip>
|
<Tooltip>
|
||||||
<UiTooltipTrigger as-child>
|
<TooltipTrigger as-child>
|
||||||
<UiButton variant="outline" size="icon" @click="toFutureNear()">
|
<Button variant="outline" size="icon" @click="toFutureNear()">
|
||||||
<PhCaretRight size="18" />
|
<PhCaretRight size="18" />
|
||||||
</UiButton>
|
</Button>
|
||||||
</UiTooltipTrigger>
|
</TooltipTrigger>
|
||||||
<UiTooltipContent>
|
<TooltipContent>
|
||||||
<p>{{ activeDirectionLabels.futureNear }}</p>
|
<p>{{ activeDirectionLabels.futureNear }}</p>
|
||||||
</UiTooltipContent>
|
</TooltipContent>
|
||||||
</UiTooltip>
|
</Tooltip>
|
||||||
</UiTooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
||||||
<UiTooltipProvider :delay-duration="250">
|
<TooltipProvider :delayDuration="250">
|
||||||
<UiTooltip>
|
<Tooltip>
|
||||||
<UiTooltipTrigger as-child>
|
<TooltipTrigger as-child>
|
||||||
<UiButton variant="outline" size="icon" @click="toFutureFar()">
|
<Button variant="outline" size="icon" @click="toFutureFar()">
|
||||||
<PhCaretDoubleRight size="18" />
|
<PhCaretDoubleRight size="18" />
|
||||||
</UiButton>
|
</Button>
|
||||||
</UiTooltipTrigger>
|
</TooltipTrigger>
|
||||||
<UiTooltipContent>
|
<TooltipContent>
|
||||||
<p>{{ activeDirectionLabels.futureFar }}</p>
|
<p>{{ activeDirectionLabels.futureFar }}</p>
|
||||||
</UiTooltipContent>
|
</TooltipContent>
|
||||||
</UiTooltip>
|
</Tooltip>
|
||||||
</UiTooltipProvider>
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -3,7 +3,9 @@ import { daysPerMonth, monthsPerYear, type LeimDate } from '@/models/Date'
|
|||||||
import { useCalendar } from '@/stores/CalendarStore'
|
import { useCalendar } from '@/stores/CalendarStore'
|
||||||
import { useCalendarEvents } from '@/stores/EventStore'
|
import { useCalendarEvents } from '@/stores/EventStore'
|
||||||
|
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||||
import { PhArrowLineLeft, PhArrowLineRight } from '@phosphor-icons/vue'
|
import { PhArrowLineLeft, PhArrowLineRight } from '@phosphor-icons/vue'
|
||||||
|
import Button from '@/components/ui/button/Button.vue'
|
||||||
|
|
||||||
const { currentDate, currentConfig, jumpToDate } = useCalendar()
|
const { currentDate, currentConfig, jumpToDate } = useCalendar()
|
||||||
const { getRelativeEventFromDate } = useCalendarEvents()
|
const { getRelativeEventFromDate } = useCalendarEvents()
|
||||||
@@ -60,54 +62,46 @@ function handleGotoPreviousEventPage(position: 'next' | 'prev' = 'next') {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex gap-2">
|
<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">
|
<div class="w-40 px-4 py-2 border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm">
|
||||||
<ClientOnly>
|
<span class="text-sm">{{ currentDate.currentDateTitle }}</span>
|
||||||
<span>{{ currentDate.currentDateTitle }}</span>
|
|
||||||
|
|
||||||
<template #fallback>
|
|
||||||
<span class="inline-block">
|
|
||||||
<UiSkeleton class="h-[19px] w-full rounded-sm" />
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</ClientOnly>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm">
|
<div class="border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm">
|
||||||
<UiTooltipProvider :delay-duration="250">
|
<TooltipProvider :delayDuration="250">
|
||||||
<UiTooltip>
|
<Tooltip>
|
||||||
<UiTooltipTrigger as-child>
|
<TooltipTrigger as-child>
|
||||||
<UiButton
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
class="rounded-t-sm rounded-b-none"
|
class="rounded-t-sm rounded-b-none"
|
||||||
@click="handleGotoPreviousEventPage('prev')"
|
@click="handleGotoPreviousEventPage('prev')"
|
||||||
>
|
>
|
||||||
<PhArrowLineLeft size="22" />
|
<PhArrowLineLeft size="22" />
|
||||||
</UiButton>
|
</Button>
|
||||||
</UiTooltipTrigger>
|
</TooltipTrigger>
|
||||||
<UiTooltipContent>
|
<TooltipContent>
|
||||||
<p>Précédente page à évènements</p>
|
<p>Précédente page à évènements</p>
|
||||||
</UiTooltipContent>
|
</TooltipContent>
|
||||||
</UiTooltip>
|
</Tooltip>
|
||||||
</UiTooltipProvider>
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
<div class="border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm">
|
<div class="border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm">
|
||||||
<UiTooltipProvider :delay-duration="250">
|
<TooltipProvider :delayDuration="250">
|
||||||
<UiTooltip>
|
<Tooltip>
|
||||||
<UiTooltipTrigger as-child>
|
<TooltipTrigger as-child>
|
||||||
<UiButton
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
class="rounded-t-sm rounded-b-none"
|
class="rounded-t-sm rounded-b-none"
|
||||||
@click="handleGotoPreviousEventPage('next')"
|
@click="handleGotoPreviousEventPage('next')"
|
||||||
>
|
>
|
||||||
<PhArrowLineRight size="22" />
|
<PhArrowLineRight size="22" />
|
||||||
</UiButton>
|
</Button>
|
||||||
</UiTooltipTrigger>
|
</TooltipTrigger>
|
||||||
<UiTooltipContent>
|
<TooltipContent>
|
||||||
<p>Prochaine page à évènements</p>
|
<p>Prochaine page à évènements</p>
|
||||||
</UiTooltipContent>
|
</TooltipContent>
|
||||||
</UiTooltip>
|
</Tooltip>
|
||||||
</UiTooltipProvider>
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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 { capitalize } from '@/utils/Strings'
|
||||||
import { useMagicKeys, useScroll, useStorage, whenever } from '@vueuse/core'
|
import { useMagicKeys, useScroll, useStorage, whenever } from '@vueuse/core'
|
||||||
import { computed, ref, watch } from 'vue'
|
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 { PhClockClockwise, PhClockCounterClockwise, PhMagnifyingGlass } from '@phosphor-icons/vue'
|
||||||
import {
|
import {
|
||||||
ComboboxAnchor,
|
ComboboxAnchor,
|
||||||
@@ -30,10 +53,10 @@ import {
|
|||||||
|
|
||||||
import SearchList from './lists/SearchList.vue'
|
import SearchList from './lists/SearchList.vue'
|
||||||
|
|
||||||
const { characters } = storeToRefs(useCharacters())
|
const { characters } = useCharacters()
|
||||||
const { allEvents } = storeToRefs(useCalendarEvents())
|
const { allEvents } = useCalendarEvents()
|
||||||
|
|
||||||
const modalOpen = defineModel<boolean>({ default: false })
|
const modalOpen = defineModel({ default: false })
|
||||||
|
|
||||||
const searchQuery = ref<string>('')
|
const searchQuery = ref<string>('')
|
||||||
// const searchEnough = computed<boolean>(() => searchQuery.value.length >= 2)
|
// const searchEnough = computed<boolean>(() => searchQuery.value.length >= 2)
|
||||||
@@ -73,11 +96,11 @@ const searchResults = computed<(Character | CalendarEvent)[]>(() => {
|
|||||||
// Assign data to loop over and filter
|
// Assign data to loop over and filter
|
||||||
// They are assigned this way for readability
|
// They are assigned this way for readability
|
||||||
if (selectedEntity.value === 'events') {
|
if (selectedEntity.value === 'events') {
|
||||||
dataToFilter = allEvents.value
|
dataToFilter = allEvents
|
||||||
} else if (selectedEntity.value === 'characters') {
|
} else if (selectedEntity.value === 'characters') {
|
||||||
dataToFilter = characters.value
|
dataToFilter = characters
|
||||||
} else {
|
} else {
|
||||||
dataToFilter = [...allEvents.value, ...characters.value]
|
dataToFilter = [...allEvents, ...characters]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,7 +128,7 @@ const searchResults = computed<(Character | CalendarEvent)[]>(() => {
|
|||||||
|
|
||||||
// Handle categories logic
|
// Handle categories logic
|
||||||
let hitCategories: boolean = false
|
let hitCategories: boolean = false
|
||||||
const allCategories: CalendarEventCategory[] = []
|
let allCategories: CalendarEventCategory[] = []
|
||||||
|
|
||||||
if (item.category) {
|
if (item.category) {
|
||||||
allCategories.push(item.category)
|
allCategories.push(item.category)
|
||||||
@@ -139,7 +162,7 @@ const searchResults = computed<(Character | CalendarEvent)[]>(() => {
|
|||||||
|
|
||||||
// Handle categories logic
|
// Handle categories logic
|
||||||
let hitCategories: boolean = false
|
let hitCategories: boolean = false
|
||||||
const allCategories: CharacterCategory[] = []
|
let allCategories: CharacterCategory[] = []
|
||||||
|
|
||||||
if (item.category) {
|
if (item.category) {
|
||||||
allCategories.push(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
|
modalOpen.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the search Uidialog
|
* Closes the search dialog
|
||||||
*/
|
*/
|
||||||
function closeUiDialog() {
|
function closeDialog() {
|
||||||
modalOpen.value = false
|
modalOpen.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,13 +218,13 @@ function handleEntitySwitch() {
|
|||||||
const keys = useMagicKeys()
|
const keys = useMagicKeys()
|
||||||
|
|
||||||
whenever(keys.control_period, () => {
|
whenever(keys.control_period, () => {
|
||||||
openUiDialog()
|
openDialog()
|
||||||
})
|
})
|
||||||
|
|
||||||
const searchResultsRef = ref<HTMLElement | null>(null)
|
const searchResultsRef = ref<HTMLElement | null>(null)
|
||||||
const { y: searchResultsY } = useScroll(searchResultsRef)
|
const { y: searchResultsY } = useScroll(searchResultsRef)
|
||||||
|
|
||||||
watch([currentPage, selectedEntity], () => {
|
watch(currentPage, () => {
|
||||||
searchResultsY.value = 0
|
searchResultsY.value = 0
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -227,10 +250,10 @@ const filteredCategories = computed(() =>
|
|||||||
*
|
*
|
||||||
* @param e Radix Change Event
|
* @param e Radix Change Event
|
||||||
*/
|
*/
|
||||||
function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
|
function handleCategorySelect(e: any) {
|
||||||
if (typeof e === 'string') {
|
if (typeof e.detail.value === 'string') {
|
||||||
searchCategory.value = ''
|
searchCategory.value = ''
|
||||||
selectedCategories.value.push(e)
|
selectedCategories.value.push(e.detail.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filteredCategories.value.length === 0) {
|
if (filteredCategories.value.length === 0) {
|
||||||
@@ -240,31 +263,31 @@ function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UiDialog v-model:open="modalOpen" @update:open="resetSearch()">
|
<Dialog v-model:open="modalOpen" @update:open="resetSearch()">
|
||||||
<UiDialogContent
|
<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="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="{
|
:class="{
|
||||||
'bottom-16': searchResults.length > 0
|
'bottom-16': searchResults.length > 0
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<VisuallyHidden>
|
<VisuallyHidden>
|
||||||
<UiDialogTitle> Recherche avancée </UiDialogTitle>
|
<DialogTitle> Recherche avancée </DialogTitle>
|
||||||
</VisuallyHidden>
|
</VisuallyHidden>
|
||||||
<VisuallyHidden>
|
<VisuallyHidden>
|
||||||
<UiDialogDescription>
|
<DialogDescription>
|
||||||
Rechercher les données disponibles sur le calendrier
|
Rechercher les données disponibles sur le calendrier
|
||||||
</UiDialogDescription>
|
</DialogDescription>
|
||||||
</VisuallyHidden>
|
</VisuallyHidden>
|
||||||
|
|
||||||
<!-- UiDialog header -->
|
<!-- Dialog header -->
|
||||||
<div id="searchForm" class="grid gap-3">
|
<div class="grid gap-3" id="searchForm">
|
||||||
<div class="relative w-full h-fit">
|
<div class="relative w-full h-fit">
|
||||||
<UiInput
|
<Input
|
||||||
id="search"
|
id="search"
|
||||||
v-model:model-value="searchQuery"
|
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Rechercher le calendrier"
|
placeholder="Rechercher le calendrier"
|
||||||
class="pl-10 py-6 text-lg"
|
class="pl-10 py-6 text-lg"
|
||||||
|
v-model:model-value="searchQuery"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
<span class="absolute start-1 inset-y-0 flex items-center justify-center px-2 opacity-50">
|
<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 class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<UiToggleGroup
|
<ToggleGroup
|
||||||
v-model="selectedEntity"
|
|
||||||
type="single"
|
type="single"
|
||||||
class="justify-start"
|
class="justify-start"
|
||||||
|
v-model="selectedEntity"
|
||||||
@update:model-value="handleEntitySwitch()"
|
@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
|
Évènements
|
||||||
</UiToggleGroupItem>
|
</ToggleGroupItem>
|
||||||
<UiToggleGroupItem value="characters" aria-label="Uniquement les personnages">
|
<ToggleGroupItem value="characters" aria-label="Uniquement les personnages" v-once>
|
||||||
Personnages
|
Personnages
|
||||||
</UiToggleGroupItem>
|
</ToggleGroupItem>
|
||||||
</UiToggleGroup>
|
</ToggleGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-1">
|
<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">
|
<div class="flex gap-2 flex-wrap items-center px-3">
|
||||||
<UiTagsInputItem v-for="item in selectedCategories" :key="item" :value="item">
|
<TagsInputItem v-for="item in selectedCategories" :key="item" :value="item">
|
||||||
<UiTagsInputItemText class="capitalize" />
|
<TagsInputItemText class="capitalize" />
|
||||||
<UiTagsInputItemDelete />
|
<TagsInputItemDelete />
|
||||||
</UiTagsInputItem>
|
</TagsInputItem>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ComboboxRoot
|
<ComboboxRoot
|
||||||
@@ -306,7 +329,7 @@ function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
|
|||||||
>
|
>
|
||||||
<ComboboxAnchor as-child>
|
<ComboboxAnchor as-child>
|
||||||
<ComboboxInput placeholder="Catégories" as-child>
|
<ComboboxInput placeholder="Catégories" as-child>
|
||||||
<UiTagsInputInput
|
<TagsInputInput
|
||||||
class="w-full px-3"
|
class="w-full px-3"
|
||||||
:class="selectedCategories.length > 0 ? 'mt-2' : ''"
|
:class="selectedCategories.length > 0 ? 'mt-2' : ''"
|
||||||
@keydown.enter.prevent
|
@keydown.enter.prevent
|
||||||
@@ -315,79 +338,79 @@ function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
|
|||||||
</ComboboxAnchor>
|
</ComboboxAnchor>
|
||||||
|
|
||||||
<ComboboxPortal :to="'#searchForm'">
|
<ComboboxPortal :to="'#searchForm'">
|
||||||
<UiCommandList
|
<CommandList
|
||||||
position="popper"
|
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"
|
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"
|
:dismissable="true"
|
||||||
>
|
>
|
||||||
<UiCommandEmpty />
|
<CommandEmpty />
|
||||||
<UiCommandGroup>
|
<CommandGroup>
|
||||||
<UiCommandItem
|
<CommandItem
|
||||||
v-for="cat in filteredCategories"
|
v-for="framework in filteredCategories"
|
||||||
:key="cat"
|
:key="framework"
|
||||||
:value="cat"
|
:value="framework"
|
||||||
@select.prevent="handleCategorySelect(cat)"
|
@select.prevent="handleCategorySelect"
|
||||||
>
|
>
|
||||||
{{ capitalize(cat) }}
|
{{ capitalize(framework) }}
|
||||||
</UiCommandItem>
|
</CommandItem>
|
||||||
</UiCommandGroup>
|
</CommandGroup>
|
||||||
</UiCommandList>
|
</CommandList>
|
||||||
</ComboboxPortal>
|
</ComboboxPortal>
|
||||||
</ComboboxRoot>
|
</ComboboxRoot>
|
||||||
</UiTagsInput>
|
</TagsInput>
|
||||||
|
|
||||||
<UiTooltipProvider :delay-duration="250">
|
<TooltipProvider :delayDuration="250">
|
||||||
<UiTooltip>
|
<Tooltip>
|
||||||
<UiTooltipTrigger as-child>
|
<TooltipTrigger as-child>
|
||||||
<UiButton
|
<Button
|
||||||
:variant="selectedOrder === 'desc' ? 'secondary' : 'outline'"
|
:variant="selectedOrder === 'desc' ? 'secondary' : 'outline'"
|
||||||
size="icon"
|
size="icon"
|
||||||
@click="setOrderDesc()"
|
@click="setOrderDesc()"
|
||||||
>
|
>
|
||||||
<PhClockCounterClockwise size="18" />
|
<PhClockCounterClockwise size="18" />
|
||||||
</UiButton>
|
</Button>
|
||||||
</UiTooltipTrigger>
|
</TooltipTrigger>
|
||||||
<UiTooltipContent>
|
<TooltipContent>
|
||||||
<p>Plus ancien</p>
|
<p>Plus ancien</p>
|
||||||
</UiTooltipContent>
|
</TooltipContent>
|
||||||
</UiTooltip>
|
</Tooltip>
|
||||||
</UiTooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
||||||
<UiTooltipProvider :delay-duration="250">
|
<TooltipProvider :delayDuration="250">
|
||||||
<UiTooltip>
|
<Tooltip>
|
||||||
<UiTooltipTrigger as-child>
|
<TooltipTrigger as-child>
|
||||||
<UiButton
|
<Button
|
||||||
:variant="selectedOrder === 'asc' ? 'secondary' : 'outline'"
|
:variant="selectedOrder === 'asc' ? 'secondary' : 'outline'"
|
||||||
size="icon"
|
size="icon"
|
||||||
@click="setOrderAsc()"
|
@click="setOrderAsc()"
|
||||||
>
|
>
|
||||||
<PhClockClockwise size="18" />
|
<PhClockClockwise size="18" />
|
||||||
</UiButton>
|
</Button>
|
||||||
</UiTooltipTrigger>
|
</TooltipTrigger>
|
||||||
<UiTooltipContent>
|
<TooltipContent>
|
||||||
<p>Plus récent</p>
|
<p>Plus récent</p>
|
||||||
</UiTooltipContent>
|
</TooltipContent>
|
||||||
</UiTooltip>
|
</Tooltip>
|
||||||
</UiTooltipProvider>
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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
|
<SearchList
|
||||||
:results="searchResults"
|
:results="searchResults"
|
||||||
:current-entity="selectedEntity"
|
:current-entity="selectedEntity"
|
||||||
:order="selectedOrder"
|
:order="selectedOrder"
|
||||||
:start-at="startOfList"
|
:start-at="startOfList"
|
||||||
:end-at="endOfList"
|
:end-at="endOfList"
|
||||||
@jumped-to-date="closeUiDialog()"
|
@jumped-to-date="closeDialog()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<UiPagination
|
<Pagination
|
||||||
v-model:page="currentPage"
|
v-model:page="currentPage"
|
||||||
:total="searchResults.length"
|
:total="searchResults.length"
|
||||||
:items-per-page="itemsPerPage"
|
:items-per-page="itemsPerPage"
|
||||||
@@ -395,32 +418,32 @@ function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
|
|||||||
show-edges
|
show-edges
|
||||||
:default-page="1"
|
:default-page="1"
|
||||||
>
|
>
|
||||||
<UiPaginationList v-slot="{ items }" class="flex items-center gap-1">
|
<PaginationList v-slot="{ items }" class="flex items-center gap-1">
|
||||||
<UiPaginationFirst />
|
<PaginationFirst />
|
||||||
<UiPaginationPrev />
|
<PaginationPrev />
|
||||||
|
|
||||||
<template v-for="(item, index) in items">
|
<template v-for="(item, index) in items">
|
||||||
<UiPaginationListItem
|
<PaginationListItem
|
||||||
v-if="item.type === 'page'"
|
v-if="item.type === 'page'"
|
||||||
:key="index"
|
:key="index"
|
||||||
:value="item.value"
|
:value="item.value"
|
||||||
as-child
|
as-child
|
||||||
>
|
>
|
||||||
<UiButton
|
<Button
|
||||||
class="w-10 h-10 p-0"
|
class="w-10 h-10 p-0"
|
||||||
:variant="item.value === currentPage ? 'default' : 'outline'"
|
:variant="item.value === currentPage ? 'default' : 'outline'"
|
||||||
>
|
>
|
||||||
{{ item.value }}
|
{{ item.value }}
|
||||||
</UiButton>
|
</Button>
|
||||||
</UiPaginationListItem>
|
</PaginationListItem>
|
||||||
<UiPaginationEllipsis v-else :key="item.type" :index="index" />
|
<PaginationEllipsis v-else :key="item.type" :index="index" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<UiPaginationNext />
|
<PaginationNext />
|
||||||
<UiPaginationLast />
|
<PaginationLast />
|
||||||
</UiPaginationList>
|
</PaginationList>
|
||||||
</UiPagination>
|
</Pagination>
|
||||||
</div>
|
</div>
|
||||||
</UiDialogContent>
|
</DialogContent>
|
||||||
</UiDialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
@@ -37,13 +37,13 @@ const { getFormattedDateTitle } = useCalendar()
|
|||||||
|
|
||||||
<menu class="flex gap-2 border-[1px] border-slate-700 rounded-sm w-fit">
|
<menu class="flex gap-2 border-[1px] border-slate-700 rounded-sm w-fit">
|
||||||
<li v-if="character.birth">
|
<li v-if="character.birth">
|
||||||
<TooltipProvider :delay-duration="100">
|
<TooltipProvider :delayDuration="100">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger as-child>
|
<TooltipTrigger as-child>
|
||||||
<Button
|
<Button
|
||||||
|
@click="$emit('query:date-jump', character.birth!)"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="xs"
|
size="xs"
|
||||||
@click="$emit('query:date-jump', character.birth!)"
|
|
||||||
>
|
>
|
||||||
<PhPlant size="16" weight="fill" />
|
<PhPlant size="16" weight="fill" />
|
||||||
{{ getFormattedDateTitle(character.birth, true) }}
|
{{ getFormattedDateTitle(character.birth, true) }}
|
||||||
@@ -57,13 +57,13 @@ const { getFormattedDateTitle } = useCalendar()
|
|||||||
</li>
|
</li>
|
||||||
<span v-if="character.birth && character.death">-</span>
|
<span v-if="character.birth && character.death">-</span>
|
||||||
<li v-if="character.death">
|
<li v-if="character.death">
|
||||||
<TooltipProvider :delay-duration="100">
|
<TooltipProvider :delayDuration="100">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger as-child>
|
<TooltipTrigger as-child>
|
||||||
<Button
|
<Button
|
||||||
|
@click="$emit('query:date-jump', character.death!)"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="xs"
|
size="xs"
|
||||||
@click="$emit('query:date-jump', character.death!)"
|
|
||||||
>
|
>
|
||||||
<PhSkull size="16" weight="fill" />
|
<PhSkull size="16" weight="fill" />
|
||||||
{{ getFormattedDateTitle(character.death, true) }}
|
{{ getFormattedDateTitle(character.death, true) }}
|
||||||
@@ -77,7 +77,7 @@ const { getFormattedDateTitle } = useCalendar()
|
|||||||
</li>
|
</li>
|
||||||
</menu>
|
</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">
|
<div v-if="character.description" class="text-sm">
|
||||||
<span class="opacity-75">
|
<span class="opacity-75">
|
||||||
@@ -105,7 +105,7 @@ const dateDuration: string | null = props.event.endDate
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="event.description" class="text-sm">
|
<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">
|
<span class="opacity-75">
|
||||||
{{ event.description }}
|
{{ event.description }}
|
||||||
</span>
|
</span>
|
||||||
@@ -3,11 +3,12 @@ import { isCharacter, type Character } from '@/models/Characters'
|
|||||||
import { compareDates, type LeimDate, type LeimDateOrder } from '@/models/Date'
|
import { compareDates, type LeimDate, type LeimDateOrder } from '@/models/Date'
|
||||||
import { isCalendarEvent, type CalendarEvent } from '@/models/Events'
|
import { isCalendarEvent, type CalendarEvent } from '@/models/Events'
|
||||||
import { useCalendar } from '@/stores/CalendarStore'
|
import { useCalendar } from '@/stores/CalendarStore'
|
||||||
import { computed } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import type { SearchMode } from '../../SearchMode'
|
import type { SearchMode } from '../../Search'
|
||||||
|
|
||||||
import CharacterCallout from './CharacterCallout.vue'
|
import CharacterCallout from './CharacterCallout.vue'
|
||||||
import EventCallout from './EventCallout.vue'
|
import EventCallout from './EventCallout.vue'
|
||||||
|
import { useScroll } from '@vueuse/core'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
results: (Character | CalendarEvent)[]
|
results: (Character | CalendarEvent)[]
|
||||||
@@ -6,6 +6,7 @@ import { useElementBounding } from '@vueuse/core'
|
|||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { computed, ref, type ComputedRef } from 'vue'
|
import { computed, ref, type ComputedRef } from 'vue'
|
||||||
|
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||||
import CalendarEventButton from '../../CalendarEvent.vue'
|
import CalendarEventButton from '../../CalendarEvent.vue'
|
||||||
import type { CalendarEvent } from '@/models/Events'
|
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"
|
class="relative z-10 group block w-full text-center cursor-pointer"
|
||||||
@click="selectDate(date)"
|
@click="selectDate(date)"
|
||||||
>
|
>
|
||||||
<ClientOnly>
|
|
||||||
<span
|
<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="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="{
|
:class="{
|
||||||
@@ -83,7 +83,6 @@ const eventsNotDisplayed = computed(
|
|||||||
>
|
>
|
||||||
{{ date.day }}
|
{{ date.day }}
|
||||||
</span>
|
</span>
|
||||||
</ClientOnly>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul
|
<ul
|
||||||
@@ -98,15 +97,15 @@ const eventsNotDisplayed = computed(
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li v-if="eventsNotDisplayed > 0" class="pointer-events-auto">
|
<li v-if="eventsNotDisplayed > 0" class="pointer-events-auto">
|
||||||
<UiPopover>
|
<Popover>
|
||||||
<UiPopoverTrigger as-child>
|
<PopoverTrigger as-child>
|
||||||
<button
|
<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"
|
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' : '' }}
|
{{ eventsNotDisplayed }} autre{{ eventsNotDisplayed > 1 ? 's' : '' }}
|
||||||
</button>
|
</button>
|
||||||
</UiPopoverTrigger>
|
</PopoverTrigger>
|
||||||
<UiPopoverContent class="w-80" :align="'center'" :side="'right'">
|
<PopoverContent class="w-80" :align="'center'" :side="'right'">
|
||||||
<div class="text-center mb-4">
|
<div class="text-center mb-4">
|
||||||
<span
|
<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"
|
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" />
|
<CalendarEventButton :event :tile-date="date" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</UiPopoverContent>
|
</PopoverContent>
|
||||||
</UiPopover>
|
</Popover>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,7 +26,7 @@ const moveCalendarRight = useThrottleFn(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container mt-[10vh] mb-auto" @wheel="handleWheel">
|
<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
|
<MonthTile
|
||||||
v-for="month in staticConfig.monthsPerYear"
|
v-for="month in staticConfig.monthsPerYear"
|
||||||
:key="month"
|
: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: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default: 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
|
||||||
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
|
|
||||||
secondary:
|
secondary:
|
||||||
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||||
destructive:
|
destructive:
|
||||||
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
|
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
|
||||||
outline: 'text-foreground',
|
outline: 'text-foreground'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: 'default',
|
variant: 'default'
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
export type BadgeVariants = VariantProps<typeof badgeVariants>
|
export type BadgeVariants = VariantProps<typeof badgeVariants>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { HTMLAttributes } from 'vue'
|
import type { HTMLAttributes } from 'vue'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { RouterLink } from 'vue-router'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
class?: HTMLAttributes['class']
|
class?: HTMLAttributes['class']
|
||||||
@@ -19,7 +20,7 @@ const props = defineProps<{
|
|||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
|
||||||
<NuxtLink
|
<RouterLink
|
||||||
v-if="props.link"
|
v-if="props.link"
|
||||||
:to="props.link"
|
:to="props.link"
|
||||||
class="absolute inset-0 focus-visible:outline-none"
|
class="absolute inset-0 focus-visible:outline-none"
|
||||||
@@ -8,7 +8,7 @@ const props = defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="cn('p-6 pt-0', props.class)">
|
<div :class="cn('p-6 pt-0 text-muted-foreground', props.class)">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -8,7 +8,7 @@ const props = defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="cn('text-sm text-muted-foreground', props.class)">
|
<p :class="cn('text-sm text-muted-foreground mt-1', props.class)">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
@@ -8,7 +8,7 @@ const props = defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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 />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -8,11 +8,7 @@ const props = defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<h3
|
<h3 :class="cn('text-2xl font-semibold leading-none tracking-tight', props.class)">
|
||||||
:class="
|
|
||||||
cn('text-2xl font-semibold leading-none tracking-tight', props.class)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<slot />
|
<slot />
|
||||||
</h3>
|
</h3>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
export { ComboboxPortal } from 'radix-vue'
|
|
||||||
|
|
||||||
export { default as Command } from './Command.vue'
|
export { default as Command } from './Command.vue'
|
||||||
export { default as CommandDialog } from './CommandDialog.vue'
|
export { default as CommandDialog } from './CommandDialog.vue'
|
||||||
export { default as CommandEmpty } from './CommandEmpty.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