diff --git a/.cursor/rules/commands.mdc b/.cursor/rules/commands.mdc new file mode 100644 index 0000000..564ab01 --- /dev/null +++ b/.cursor/rules/commands.mdc @@ -0,0 +1,147 @@ +--- +description: +globs: +alwaysApply: true +--- +# Commandes Makefile de Mangarr + +Toujours chercher si une commande est disponible dans [Makefile](mdc:Makefile). + +## Structure Générale +Le Makefile est organisé en plusieurs sections distinctes : +- Docker 🐳 +- Composer 🧙 +- Symfony 🎵 +- Webpack Encore 📦 + +## Variables Principales +```makefile +# Exécutables Docker +DOCKER_COMP = docker compose +DOCKER_COMP_EXEC = $(DOCKER_COMP) exec + +# Conteneurs +PHP_CONT = $(DOCKER_COMP_EXEC) php +NODE_CONT = $(DOCKER_COMP_EXEC) node + +# Exécutables dans les conteneurs +PHP = $(PHP_CONT) php +COMPOSER = $(PHP_CONT) composer +SYMFONY = $(PHP) bin/console +SF_MEMORY = $(PHP) -d memory_limit=256M bin/console +``` + +## Bonnes Pratiques + +### 1. Organisation des Commandes +- Regrouper les commandes par catégorie avec des commentaires clairs +- Utiliser des variables pour les commandes répétitives +- Documenter chaque commande avec `##` pour l'aide automatique +- Préfixer les commandes internes avec `_` (exemple: `_check-deps`) + +### 2. Paramètres et Options +- Utiliser la syntaxe `make command p=value` pour les paramètres +- Documenter les paramètres possibles dans les commentaires +- Utiliser `?=` pour les valeurs par défaut modifiables + +### 3. Dépendances +- Définir clairement les dépendances entre les commandes +- Utiliser des commandes composées pour les tâches complexes +- Éviter les dépendances circulaires + +### 4. Documentation +- Chaque commande doit avoir une description avec `##` +- Inclure des exemples d'utilisation pour les commandes complexes +- Utiliser la commande `help` pour afficher la documentation + +## Commandes Disponibles + +### Docker 🐳 +```makefile +build: ## Construit les images Docker +up: ## Démarre les conteneurs +start: ## Démarre les conteneurs en mode détaché +down: ## Arrête et supprime les conteneurs +logs: ## Affiche les logs en temps réel +sh: ## Se connecte au conteneur PHP +``` + +### Composer 🧙 +```makefile +composer: ## Exécute une commande composer (c=command) +vendor: ## Installe les dépendances +``` + +### Symfony 🎵 +```makefile +sf: ## Liste/exécute les commandes Symfony (c=command) +cc: ## Vide le cache +migration: ## Crée une nouvelle migration +fixtures: ## Charge les fixtures +consume: ## Consomme les messages de la queue +``` + +### Webpack Encore 📦 +```makefile +npm-install: ## Installe les dépendances npm +npm-run: ## Lance le serveur de développement +npm-watch: ## Surveille les changements +``` + +## Exemples d'Utilisation + +### 1. Installation du Projet +```bash +make install # Construit et démarre les conteneurs, installe les dépendances +``` + +### 2. Développement Quotidien +```bash +make start # Démarre les conteneurs +make npm-watch # Lance la compilation des assets +make consume # Démarre les workers +``` + +### 3. Commandes avec Paramètres +```bash +make composer c="require symfony/orm-pack" # Ajoute une dépendance +make sf c="make:entity" # Crée une entité +make test f="ScrapeChapterHandlerTest" # Lance un test spécifique +``` + +## Ajout de Nouvelles Commandes + +### 1. Structure de Base +```makefile +command-name: ## Description de la commande + @$(DOCKER_COMP) ... # Commande à exécuter +``` + +### 2. Avec Paramètres +```makefile +command-with-param: ## Description (p=value) + @$(eval p ?=) + @$(DOCKER_COMP) ... $(p) +``` + +### 3. Commande Composée +```makefile +full-install: build start vendor npm-install ## Description complète +``` + +## Maintenance + +### 1. Nettoyage +- Supprimer les commandes obsolètes +- Mettre à jour les descriptions +- Vérifier les dépendances + +### 2. Documentation +- Maintenir la section d'aide à jour +- Ajouter des exemples pour les nouvelles commandes +- Documenter les changements importants + +### 3. Tests +- Tester les nouvelles commandes +- Vérifier les dépendances +- Valider les paramètres \ No newline at end of file diff --git a/.cursor/rules/front_vue.mdc b/.cursor/rules/front_vue.mdc new file mode 100644 index 0000000..b4ca07f --- /dev/null +++ b/.cursor/rules/front_vue.mdc @@ -0,0 +1,155 @@ +--- +description: +globs: +alwaysApply: false +--- +# Architecture Frontend Vue.js + +## Structure Générale +L'application Vue.js suit une architecture hexagonale (ports & adapters) avec une séparation claire des responsabilités. Le code est organisé en domaines distincts dans le dossier `assets/vue/`. +Pour ce qui est du style, on utilise TailwindCss, Headlessui et Heroicons. + +``` +assets/vue/ +├── app/ +│ ├── shared/ # Code partagé entre les domaines +│ │ ├── components/ # Composants réutilisables +│ │ │ ├── ui/ # Composants UI génériques (boutons, inputs, etc.) +│ │ │ └── layout/ # Layouts réutilisables +│ │ ├── composables/ # Composables Vue partagés +│ │ ├── plugins/ # Plugins Vue (router, pinia, etc.) +│ │ └── utils/ # Utilitaires partagés +│ │ +│ ├── domain/ # Domaines métier +│ │ ├── manga/ # Domaine Manga +│ │ │ ├── application/ # Cas d'utilisation +│ │ │ │ ├── commands/ # Commands & CommandHandlers +│ │ │ │ ├── queries/ # Queries & QueryHandlers +│ │ │ │ └── store/ # Store Pinia du domaine +│ │ │ ├── domain/ # Cœur métier +│ │ │ │ ├── entities/ # Entités +│ │ │ │ ├── value-objects/# Objets valeur +│ │ │ │ └── services/ # Services métier +│ │ │ ├── infrastructure/ # Adaptateurs +│ │ │ │ ├── api/ # Client API +│ │ │ │ └── repository/ # Implémentation repository +│ │ │ └── presentation/ # Interface utilisateur +│ │ │ ├── components/ # Composants spécifiques au domaine +│ │ │ ├── composables/ # Composables spécifiques +│ │ │ └── pages/ # Pages du domaine +│ │ │ +│ │ ├── reader/ # Domaine Reader (même structure) +│ │ └── scraping/ # Domaine Scraping (même structure) +│ │ +│ └── router/ # Configuration du routeur +│ └── index.js # Point d'entrée du routeur +``` + +## Contrat d'API +Le contrat d'API complet est disponible dans le fichier [api-docs.json](mdc:public/api-docs.json). Ce fichier contient la documentation OpenAPI de toutes les routes disponibles et leurs schémas. + +## Règles d'Architecture + +### 1. Règles Générales +- Chaque domaine est isolé et ne dépend que de lui-même et du domaine `shared` +- Les dépendances externes sont gérées via les adaptateurs dans l'infrastructure +- L'application est une SPA (Single Page Application) sans rechargement de page +- Utilisation de Vue Router pour la navigation côté client +- Gestion d'état avec Pinia organisée par domaine + +### 2. Couche Domain +- Contient les entités et la logique métier pure +- Ne dépend d'aucune bibliothèque externe sauf Vue.js +- Les entités sont des classes JavaScript standard +- Exemple : +```javascript +export class Manga { + constructor({ id, title, description = null }) { + this.id = id; + this.title = title; + this.description = description; + } + + static create(data) { + return new Manga(data); + } +} +``` + +### 3. Couche Application +- Gère les cas d'utilisation via les stores Pinia +- Coordonne les interactions entre l'UI et le domaine +- Transforme les données du domaine pour l'UI +- Exemple de store : +```javascript +export const useMangaStore = defineStore('manga', { + state: () => ({ + mangas: [], + loading: false, + error: null + }), + actions: { + async fetchMangas() { + // Logique de chargement + } + } +}); +``` + +### 4. Couche Infrastructure +- Gère la communication avec l'API +- Isole les dépendances externes +- Exemple d'API client : +```javascript +export class MangaApi { + static async fetchAll() { + const response = await fetch('/api/mangas'); + return response.json(); + } +} +``` + +### 5. Couche Présentation +- Composants Vue.js spécifiques au domaine +- Utilise les composants UI partagés +- Communique avec la couche application via les stores +- Exemple de composant : +```vue + +``` + +## Bonnes Pratiques + +### 1. Composants +- Utiliser la Composition API pour la logique +- Séparer les composants UI génériques des composants métier +- Favoriser les props et events pour la communication parent-enfant +- Utiliser les stores pour la communication entre composants distants + +### 2. État +- Un store Pinia par domaine +- Actions asynchrones dans les stores +- Getters pour les données dérivées +- État local dans les composants quand possible + +### 3. Router +- Routes organisées par domaine +- Lazy loading des composants de page +- Navigation programmatique via le router +- Guards pour la protection des routes + +### 4. Style +- Utilisation de Tailwind CSS +- Classes utilitaires pour le style +- Composants Headless UI pour l'accessibilité +- Design system cohérent via les composants partagés + +## Validation +Les règles d'architecture peuvent être validées par des outils comme : +- ESLint pour les règles de code +- Tests unitaires pour les composants +- Tests d'intégration pour les stores \ No newline at end of file diff --git a/assets/vue/app/App.vue b/assets/vue/app/App.vue new file mode 100644 index 0000000..5f37311 --- /dev/null +++ b/assets/vue/app/App.vue @@ -0,0 +1,29 @@ + + + + + \ No newline at end of file diff --git a/assets/vue/app/domain/manga/application/queries/searchMangas.js b/assets/vue/app/domain/manga/application/queries/searchMangas.js new file mode 100644 index 0000000..a772aa9 --- /dev/null +++ b/assets/vue/app/domain/manga/application/queries/searchMangas.js @@ -0,0 +1,18 @@ +export class SearchMangas { + constructor(mangaRepository) { + this.mangaRepository = mangaRepository; + } + + async execute(query) { + if (!query || query.trim().length === 0) { + return []; + } + + try { + return await this.mangaRepository.searchMangas(query); + } catch (error) { + console.error('Search error:', error); + throw error; + } + } +} \ No newline at end of file diff --git a/assets/vue/app/domain/manga/application/store/mangaStore.js b/assets/vue/app/domain/manga/application/store/mangaStore.js new file mode 100644 index 0000000..becfff2 --- /dev/null +++ b/assets/vue/app/domain/manga/application/store/mangaStore.js @@ -0,0 +1,72 @@ +import { defineStore } from 'pinia'; +import { ref } from 'vue'; +import { ApiMangaRepository } from '../../infrastructure/api/apiMangaRepository'; + +const mangaRepository = new ApiMangaRepository(); + +export const useMangaStore = defineStore('manga', () => { + const collection = ref(null); + const detailedMangas = ref({}); + const loading = ref(false); + const error = ref(null); + const isBackgroundLoading = ref(false); + + const loadCollection = async () => { + if (loading.value) return; + + loading.value = true; + error.value = null; + + try { + collection.value = await mangaRepository.getCollection(); + } catch (err) { + error.value = err.message; + console.error('Failed to load collection:', err); + } finally { + loading.value = false; + } + }; + + const refreshCollectionInBackground = async () => { + if (isBackgroundLoading.value) return; + + isBackgroundLoading.value = true; + + try { + const updatedCollection = await mangaRepository.getCollection(); + collection.value = updatedCollection; + } catch (err) { + console.error('Failed to refresh collection:', err); + } finally { + isBackgroundLoading.value = false; + } + }; + + const loadMangaDetail = async (slug) => { + if (detailedMangas.value[slug]) return; + + try { + const manga = await mangaRepository.getMangaBySlug(slug); + detailedMangas.value[slug] = manga; + } catch (err) { + console.error(`Failed to load manga details for ${slug}:`, err); + throw err; + } + }; + + const getMangaFromCollection = (slug) => { + return collection.value?.items.find(manga => manga.slug === slug); + }; + + return { + collection, + detailedMangas, + loading, + error, + isBackgroundLoading, + loadCollection, + refreshCollectionInBackground, + loadMangaDetail, + getMangaFromCollection + }; +}); \ No newline at end of file diff --git a/assets/vue/app/domain/manga/domain/entities/manga.js b/assets/vue/app/domain/manga/domain/entities/manga.js new file mode 100644 index 0000000..5c42700 --- /dev/null +++ b/assets/vue/app/domain/manga/domain/entities/manga.js @@ -0,0 +1,42 @@ +export class Manga { + constructor({ + id, + slug, + title, + description = null, + authors = [], + imageUrl = null, + publicationYear = null, + status = null, + rating = null, + genres = [], + createdAt = new Date().toISOString() + }) { + this.id = id; + this.slug = slug; + this.title = title; + this.description = description; + this.authors = authors; + this.imageUrl = imageUrl; + this.publicationYear = publicationYear; + this.status = status; + this.rating = rating; + this.genres = genres; + this.createdAt = createdAt; + } + + static create(data) { + return new Manga(data); + } +} + +export class MangaCollection { + constructor(items, total, page, limit, hasNextPage, hasPreviousPage) { + this.items = items.map(item => Manga.create(item)); + this.total = total; + this.page = page; + this.limit = limit; + this.hasNextPage = hasNextPage; + this.hasPreviousPage = hasPreviousPage; + } +} \ No newline at end of file diff --git a/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js b/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js new file mode 100644 index 0000000..11309b0 --- /dev/null +++ b/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js @@ -0,0 +1,50 @@ +import { MangaCollection } from '../../domain/entities/manga'; + +export class ApiMangaRepository { + async getCollection() { + try { + const response = await fetch('/api/mangas'); + if (!response.ok) { + throw new Error('Failed to fetch manga collection'); + } + const data = await response.json(); + return new MangaCollection( + data.items, + data.total, + data.page, + data.limit, + data.hasNextPage, + data.hasPreviousPage + ); + } catch (error) { + console.error('API Error:', error); + throw error; + } + } + + async getMangaBySlug(slug) { + try { + const response = await fetch(`/api/mangas/${slug}`); + if (!response.ok) { + throw new Error('Failed to fetch manga details'); + } + return await response.json(); + } catch (error) { + console.error('API Error:', error); + throw error; + } + } + + async searchMangas(query) { + try { + const response = await fetch(`/api/mangas/search?q=${encodeURIComponent(query)}`); + if (!response.ok) { + throw new Error('Failed to search mangas'); + } + return await response.json(); + } catch (error) { + console.error('API Error:', error); + throw error; + } + } +} \ No newline at end of file diff --git a/assets/vue/app/domain/manga/presentation/components/MangaCard.vue b/assets/vue/app/domain/manga/presentation/components/MangaCard.vue new file mode 100644 index 0000000..3c8712f --- /dev/null +++ b/assets/vue/app/domain/manga/presentation/components/MangaCard.vue @@ -0,0 +1,49 @@ + + + \ No newline at end of file diff --git a/assets/vue/app/domain/manga/presentation/components/MangaGrid.vue b/assets/vue/app/domain/manga/presentation/components/MangaGrid.vue new file mode 100644 index 0000000..35d6b2b --- /dev/null +++ b/assets/vue/app/domain/manga/presentation/components/MangaGrid.vue @@ -0,0 +1,20 @@ + + + \ No newline at end of file diff --git a/assets/vue/app/domain/manga/presentation/pages/HomePage.vue b/assets/vue/app/domain/manga/presentation/pages/HomePage.vue new file mode 100644 index 0000000..6c58719 --- /dev/null +++ b/assets/vue/app/domain/manga/presentation/pages/HomePage.vue @@ -0,0 +1,67 @@ + + + \ No newline at end of file diff --git a/assets/vue/app/index.js b/assets/vue/app/index.js new file mode 100644 index 0000000..afcac44 --- /dev/null +++ b/assets/vue/app/index.js @@ -0,0 +1,25 @@ +import { createApp } from 'vue' +import { createRouter, createWebHistory } from 'vue-router' +import { createPinia } from 'pinia' +import App from './App.vue' +import routes from './router' +import '../../styles/app.scss' + +// Création du router +const router = createRouter({ + history: createWebHistory('/vue/'), + routes +}) + +// Création du store +const pinia = createPinia() + +// Création de l'application +const app = createApp(App) + +// Installation des plugins +app.use(router) +app.use(pinia) + +// Montage de l'application +app.mount('#vue-app') \ No newline at end of file diff --git a/assets/vue/app/router/index.js b/assets/vue/app/router/index.js new file mode 100644 index 0000000..dba1a19 --- /dev/null +++ b/assets/vue/app/router/index.js @@ -0,0 +1,127 @@ +import { createRouter, createWebHistory } from 'vue-router'; +import HomePage from '../domain/manga/presentation/pages/HomePage.vue'; + +// Placeholder component for new routes +const PlaceholderComponent = { + props: { + title: { + type: String, + required: true + } + }, + template: ` +
+

{{ title }}

+

Cette fonctionnalité sera bientôt disponible.

+
+ ` +}; + +const routes = [ + { + path: '/', + name: 'home', + component: HomePage + }, + { + path: '/manga/:slug', + name: 'manga-detail', + component: PlaceholderComponent, + props: { title: 'Détails du manga' } + }, + { + path: '/add', + name: 'add-manga', + component: PlaceholderComponent, + props: { title: 'Ajouter un manga' } + }, + { + path: '/reader/:chapterId', + name: 'reader', + component: PlaceholderComponent, + props: { title: 'Lecteur' } + }, + // Pages placeholder avec chargement différé + { + path: '/import', + name: 'import', + component: PlaceholderComponent, + props: { title: 'Import de bibliothèque' } + }, + { + path: '/discover', + name: 'discover', + component: PlaceholderComponent, + props: { title: 'Découvrir' } + }, + { + path: '/convert', + name: 'convert', + component: PlaceholderComponent, + props: { title: 'Convertir CBR en CBZ' } + }, + { + path: '/calendar', + name: 'calendar', + component: PlaceholderComponent, + props: { title: 'Calendrier' } + }, + { + path: '/activity', + name: 'activity', + component: PlaceholderComponent, + props: { title: 'Activité' } + }, + // Paramètres + { + path: '/settings/general', + name: 'settings-general', + component: PlaceholderComponent, + props: { title: 'Paramètres généraux' } + }, + { + path: '/settings/folders', + name: 'settings-folders', + component: PlaceholderComponent, + props: { title: 'Gestion des dossiers' } + }, + { + path: '/settings/scrappers', + name: 'settings-scrappers', + component: PlaceholderComponent, + props: { title: 'Configuration des scrappers' } + }, + { + path: '/settings/ui', + name: 'settings-ui', + component: PlaceholderComponent, + props: { title: "Paramètres de l'interface" } + }, + // Système + { + path: '/system/status', + name: 'system-status', + component: PlaceholderComponent, + props: { title: 'Status du système' } + }, + { + path: '/system/backup', + name: 'system-backup', + component: PlaceholderComponent, + props: { title: 'Sauvegarde' } + }, + { + path: '/system/logs', + name: 'system-logs', + component: PlaceholderComponent, + props: { title: 'Journaux système' } + }, + { + path: '/system/updates', + name: 'system-updates', + component: PlaceholderComponent, + props: { title: 'Mises à jour' } + } +]; + +export default routes; \ No newline at end of file diff --git a/assets/vue/app/shared/components/layout/Header.vue b/assets/vue/app/shared/components/layout/Header.vue new file mode 100644 index 0000000..0c343dc --- /dev/null +++ b/assets/vue/app/shared/components/layout/Header.vue @@ -0,0 +1,23 @@ + + + \ No newline at end of file diff --git a/assets/vue/app/shared/components/layout/Layout.vue b/assets/vue/app/shared/components/layout/Layout.vue new file mode 100644 index 0000000..c4eb15d --- /dev/null +++ b/assets/vue/app/shared/components/layout/Layout.vue @@ -0,0 +1,36 @@ + + + \ No newline at end of file diff --git a/assets/vue/app/shared/components/layout/SearchBar.vue b/assets/vue/app/shared/components/layout/SearchBar.vue new file mode 100644 index 0000000..2a85531 --- /dev/null +++ b/assets/vue/app/shared/components/layout/SearchBar.vue @@ -0,0 +1,130 @@ + + + diff --git a/assets/vue/app/shared/components/layout/Sidebar.vue b/assets/vue/app/shared/components/layout/Sidebar.vue new file mode 100644 index 0000000..b6496ce --- /dev/null +++ b/assets/vue/app/shared/components/layout/Sidebar.vue @@ -0,0 +1,155 @@ + + + \ No newline at end of file diff --git a/assets/vue/app/shared/components/ui/Toolbar.vue b/assets/vue/app/shared/components/ui/Toolbar.vue new file mode 100644 index 0000000..339f00c --- /dev/null +++ b/assets/vue/app/shared/components/ui/Toolbar.vue @@ -0,0 +1,47 @@ + + + \ No newline at end of file diff --git a/config/routes.yaml b/config/routes.yaml index 8790af5..ddbf60c 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -9,3 +9,9 @@ react_app: controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController defaults: template: 'react/index.html.twig' + +vue_app: + path: /vue + controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController + defaults: + template: 'vue/index.html.twig' diff --git a/package-lock.json b/package-lock.json index fdeba04..98fe511 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,8 @@ "@babel/core": "^7.17.0", "@babel/preset-env": "^7.16.0", "@babel/preset-react": "^7.26.3", + "@headlessui/vue": "^1.7.23", + "@heroicons/vue": "^2.2.0", "@hotwired/stimulus": "^3.0.0", "@hotwired/turbo": "^7.1.1 || ^8.0", "@symfony/stimulus-bridge": "^3.2.0", @@ -31,14 +33,19 @@ "@symfony/ux-react": "file:vendor/symfony/ux-react/assets", "@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets", "@symfony/webpack-encore": "^4.0.0", + "@vue/compiler-sfc": "^3.5.13", "core-js": "^3.23.0", "daisyui": "^4.4.2", + "pinia": "^3.0.1", "react": "^18.0", "react-dom": "^18.0", "regenerator-runtime": "^0.13.9", "sass": "^1.59.3", "sass-loader": "^13.2.0", "stimulus-use": "^0.52.2", + "vue": "^3.5.13", + "vue-loader": "^17.4.2", + "vue-router": "^4.5.0", "webpack": "^5.74.0", "webpack-cli": "^4.10.0", "webpack-notifier": "^1.15.0" @@ -2416,6 +2423,32 @@ "react": ">=16.3" } }, + "node_modules/@headlessui/vue": { + "version": "1.7.23", + "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.23.tgz", + "integrity": "sha512-JzdCNqurrtuu0YW6QaDtR2PIYCKPUWq28csDyMvN4zmGccmE7lz40Is6hc3LA4HFeCI7sekZ/PQMTNmn9I/4Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tanstack/vue-virtual": "^3.0.0-beta.60" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@heroicons/vue": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-2.2.0.tgz", + "integrity": "sha512-G3dbSxoeEKqbi/DFalhRxJU4mTXJn7GwZ7ae8NuEQzd1bqdd0jAbdaBZlHPcvPD2xI1iGzNVB4k20Un2AguYPw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "vue": ">= 3" + } + }, "node_modules/@hotwired/stimulus": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@hotwired/stimulus/-/stimulus-3.2.2.tgz", @@ -2640,9 +2673,10 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -3367,6 +3401,34 @@ "node": ">=8" } }, + "node_modules/@tanstack/virtual-core": { + "version": "3.13.5", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.5.tgz", + "integrity": "sha512-gMLNylxhJdUlfRR1G3U9rtuwUh2IjdrrniJIDcekVJN3/3i+bluvdMi3+eodnxzJq5nKnxnigo9h0lIpaqV6HQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/vue-virtual": { + "version": "3.13.5", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.5.tgz", + "integrity": "sha512-1hhUA6CUjmKc5JDyKLcYOV6mI631FaKKxXh77Ja4UtIy6EOofYaLPk8vVgvK6vLMUSfHR2vI3ZpPY9ibyX60SA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.13.5" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.0.0" + } + }, "node_modules/@testing-library/dom": { "version": "7.31.2", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.2.tgz", @@ -3875,6 +3937,137 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, + "node_modules/@vue/compiler-core": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-core/node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/compiler-core/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-dom/node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.48", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-ssr/node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.2.tgz", + "integrity": "sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.2" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.2.tgz", + "integrity": "sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.2", + "birpc": "^0.2.19", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.1" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.2.tgz", + "integrity": "sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, "node_modules/@vue/reactivity": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz", @@ -3883,6 +4076,85 @@ "@vue/shared": "3.1.5" } }, + "node_modules/@vue/runtime-core": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-core/node_modules/@vue/reactivity": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-core/node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/runtime-dom/node_modules/@vue/reactivity": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-dom/node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "vue": "3.5.13" + } + }, + "node_modules/@vue/server-renderer/node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@vue/shared": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz", @@ -4593,6 +4865,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/birpc": { + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz", + "integrity": "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -5200,6 +5482,22 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/core-js": { "version": "3.37.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", @@ -6152,6 +6450,13 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -6820,6 +7125,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", + "dev": true, + "license": "MIT" + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -6831,6 +7143,13 @@ "node": ">= 0.4" } }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, "node_modules/hotkeys-js": { "version": "3.13.7", "resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.13.7.tgz", @@ -7376,6 +7695,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -7755,6 +8087,16 @@ "lz-string": "bin/bin.js" } }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", @@ -8439,6 +8781,13 @@ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "license": "MIT" }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -8465,6 +8814,28 @@ "node": ">=6" } }, + "node_modules/pinia": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.1.tgz", + "integrity": "sha512-WXglsDzztOTH6IfcJ99ltYZin2mY8XZCXujkYWVIJlBjqsP6ST7zw+Aarh63E1cDVYeyUcPCxPHzJpEOmzB6Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.2" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", @@ -9938,6 +10309,13 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -10501,6 +10879,16 @@ "wbuf": "^1.7.3" } }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", @@ -10718,6 +11106,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/superjson": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -11357,6 +11758,157 @@ } } }, + "node_modules/vue": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-loader": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.4.2.tgz", + "integrity": "sha512-yTKOA4R/VN4jqjw4y5HrynFL8AK0Z3/Jt7eOJXEitsm0GMRHDBjCfCiuTiLP7OESvsZYo2pATCWhDqxC5ZrM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "watchpack": "^2.4.0" + }, + "peerDependencies": { + "webpack": "^4.1.0 || ^5.0.0-0" + }, + "peerDependenciesMeta": { + "@vue/compiler-sfc": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/vue-loader/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/vue-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/vue-loader/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/vue-loader/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue-loader/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/vue-loader/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vue-router": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.0.tgz", + "integrity": "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue/node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "dev": true, + "license": "MIT" + }, "node_modules/watchpack": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", diff --git a/package.json b/package.json index 4f324d6..4b63d2c 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,8 @@ "@babel/core": "^7.17.0", "@babel/preset-env": "^7.16.0", "@babel/preset-react": "^7.26.3", + "@headlessui/vue": "^1.7.23", + "@heroicons/vue": "^2.2.0", "@hotwired/stimulus": "^3.0.0", "@hotwired/turbo": "^7.1.1 || ^8.0", "@symfony/stimulus-bridge": "^3.2.0", @@ -10,14 +12,19 @@ "@symfony/ux-react": "file:vendor/symfony/ux-react/assets", "@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets", "@symfony/webpack-encore": "^4.0.0", + "@vue/compiler-sfc": "^3.5.13", "core-js": "^3.23.0", "daisyui": "^4.4.2", + "pinia": "^3.0.1", "react": "^18.0", "react-dom": "^18.0", "regenerator-runtime": "^0.13.9", "sass": "^1.59.3", "sass-loader": "^13.2.0", "stimulus-use": "^0.52.2", + "vue": "^3.5.13", + "vue-loader": "^17.4.2", + "vue-router": "^4.5.0", "webpack": "^5.74.0", "webpack-cli": "^4.10.0", "webpack-notifier": "^1.15.0" diff --git a/templates/vue/index.html.twig b/templates/vue/index.html.twig new file mode 100644 index 0000000..e04806e --- /dev/null +++ b/templates/vue/index.html.twig @@ -0,0 +1,13 @@ + + + + + + Vue App + {{ encore_entry_link_tags('vue-app') }} + + +
+ {{ encore_entry_script_tags('vue-app') }} + + \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 92480d3..ba37835 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -22,6 +22,7 @@ Encore */ .addEntry('app', './assets/app.js') .addEntry('react-app', './assets/react/app/index.jsx') + .addEntry('vue-app', './assets/vue/app/index.js') // .addEntry('alpine', 'alpinejs') // enables the Symfony UX Stimulus bridge (used in assets/bootstrap.js) @@ -31,6 +32,7 @@ Encore .splitEntryChunks() .enableReactPreset() + .enableVueLoader() // will require an extra script tag for runtime.js // but, you probably want this, unless you're building a single-page app