diff --git a/opus_submitter/opus_submitter/settingsLocal.py.dist b/opus_submitter/opus_submitter/settingsLocal.py.dist new file mode 100644 index 0000000..e69de29 diff --git a/opus_submitter/package.json b/opus_submitter/package.json index f127c2a..30f32db 100644 --- a/opus_submitter/package.json +++ b/opus_submitter/package.json @@ -12,6 +12,7 @@ "dependencies": { "@tailwindcss/vite": "^4.1.16", "install": "^0.13.0", + "pinia": "^3.0.3", "tailwindcss": "^4.1.16", "tesseract.js": "^5.1.1", "vue": "^3.5.22" diff --git a/opus_submitter/pnpm-lock.yaml b/opus_submitter/pnpm-lock.yaml index caddada..e94413a 100644 --- a/opus_submitter/pnpm-lock.yaml +++ b/opus_submitter/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: install: specifier: ^0.13.0 version: 0.13.0 + pinia: + specifier: ^3.0.3 + version: 3.0.3(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3)) tailwindcss: specifier: ^4.1.16 version: 4.1.16 @@ -480,6 +483,15 @@ packages: '@vue/compiler-ssr@3.5.22': resolution: {integrity: sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==} + '@vue/devtools-api@7.7.7': + resolution: {integrity: sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==} + + '@vue/devtools-kit@7.7.7': + resolution: {integrity: sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==} + + '@vue/devtools-shared@7.7.7': + resolution: {integrity: sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==} + '@vue/language-core@3.1.2': resolution: {integrity: sha512-PyFDCqpdfYUT+oMLqcc61oHfJlC6yjhybaefwQjRdkchIihToOEpJ2Wu/Ebq2yrnJdd1EsaAvZaXVAqcxtnDxQ==} peerDependencies: @@ -519,9 +531,16 @@ packages: alien-signals@3.0.3: resolution: {integrity: sha512-2JXjom6R7ZwrISpUphLhf4htUq1aKRCennTJ6u9kFfr3sLmC9+I4CxxVi+McoFnIg+p1HnVrfLT/iCt4Dlz//Q==} + birpc@2.6.1: + resolution: {integrity: sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==} + bmp-js@0.1.0: resolution: {integrity: sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==} + copy-anything@4.0.5: + resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} + engines: {node: '>=18'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -565,6 +584,9 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + idb-keyval@6.2.2: resolution: {integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==} @@ -578,6 +600,10 @@ packages: is-url@1.2.4: resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + is-what@5.5.0: + resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} + engines: {node: '>=18'} + jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true @@ -655,6 +681,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} @@ -679,6 +708,9 @@ packages: path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -686,6 +718,15 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pinia@3.0.3: + resolution: {integrity: sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==} + peerDependencies: + typescript: '>=4.4.4' + vue: ^2.7.0 || ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -693,6 +734,9 @@ packages: regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rollup@4.52.5: resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -702,6 +746,14 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + + superjson@2.2.5: + resolution: {integrity: sha512-zWPTX96LVsA/eVYnqOM2+ofcdPqdS1dAF1LN4TS2/MWuUpfitd9ctTa87wt4xrYnZnkLtS69xpBdSxVBP5Rm6w==} + engines: {node: '>=16'} + tailwindcss@4.1.16: resolution: {integrity: sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==} @@ -1103,6 +1155,24 @@ snapshots: '@vue/compiler-dom': 3.5.22 '@vue/shared': 3.5.22 + '@vue/devtools-api@7.7.7': + dependencies: + '@vue/devtools-kit': 7.7.7 + + '@vue/devtools-kit@7.7.7': + dependencies: + '@vue/devtools-shared': 7.7.7 + birpc: 2.6.1 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.5 + + '@vue/devtools-shared@7.7.7': + dependencies: + rfdc: 1.4.1 + '@vue/language-core@3.1.2(typescript@5.9.3)': dependencies: '@volar/language-core': 2.4.23 @@ -1146,8 +1216,14 @@ snapshots: alien-signals@3.0.3: {} + birpc@2.6.1: {} + bmp-js@0.1.0: {} + copy-anything@4.0.5: + dependencies: + is-what: 5.5.0 + csstype@3.1.3: {} daisyui@5.3.10: {} @@ -1201,6 +1277,8 @@ snapshots: graceful-fs@4.2.11: {} + hookable@5.5.3: {} + idb-keyval@6.2.2: {} install@0.13.0: {} @@ -1209,6 +1287,8 @@ snapshots: is-url@1.2.4: {} + is-what@5.5.0: {} + jiti@2.6.1: {} lightningcss-android-arm64@1.30.2: @@ -1264,6 +1344,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + mitt@3.0.1: {} + muggle-string@0.4.1: {} nanoid@3.3.11: {} @@ -1276,10 +1358,19 @@ snapshots: path-browserify@1.0.1: {} + perfect-debounce@1.0.0: {} + picocolors@1.1.1: {} picomatch@4.0.3: {} + pinia@3.0.3(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3)): + dependencies: + '@vue/devtools-api': 7.7.7 + vue: 3.5.22(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -1288,6 +1379,8 @@ snapshots: regenerator-runtime@0.13.11: {} + rfdc@1.4.1: {} + rollup@4.52.5: dependencies: '@types/estree': 1.0.8 @@ -1318,6 +1411,12 @@ snapshots: source-map-js@1.2.1: {} + speakingurl@14.0.1: {} + + superjson@2.2.5: + dependencies: + copy-anything: 4.0.5 + tailwindcss@4.1.16: {} tapable@2.3.0: {} diff --git a/opus_submitter/src/App.vue b/opus_submitter/src/App.vue index 2c3b455..13c19b7 100644 --- a/opus_submitter/src/App.vue +++ b/opus_submitter/src/App.vue @@ -3,16 +3,19 @@ import { ref, onMounted, computed } from 'vue' import PuzzleCard from './components/PuzzleCard.vue' import SubmissionForm from './components/SubmissionForm.vue' import AdminPanel from './components/AdminPanel.vue' -import { puzzleHelpers, submissionHelpers, errorHelpers, apiService } from './services/apiService' -import type { SteamCollection, SteamCollectionItem, Submission, PuzzleResponse, UserInfo } from './types' +import { apiService, errorHelpers } from './services/apiService' +import { usePuzzlesStore } from './stores/puzzles' +import { useSubmissionsStore } from './stores/submissions' +import type { SteamCollection, PuzzleResponse, UserInfo } from './types' -// API data +// Pinia stores +const puzzlesStore = usePuzzlesStore() +const submissionsStore = useSubmissionsStore() + +// Local state const collections = ref([]) -const puzzles = ref([]) -const submissions = ref([]) const userInfo = ref(null) const isLoading = ref(true) -const showSubmissionModal = ref(false) const error = ref('') // Mock data removed - using API data only @@ -25,7 +28,7 @@ const isSuperuser = computed(() => { // Computed property to get responses grouped by puzzle const responsesByPuzzle = computed(() => { const grouped: Record = {} - submissions.value.forEach(submission => { + submissionsStore.submissions.forEach(submission => { submission.responses.forEach(response => { // Handle both number and object types for puzzle field const puzzleId = typeof response.puzzle === 'number' ? response.puzzle : response.puzzle.id @@ -55,21 +58,20 @@ onMounted(async () => { console.warn('User info error:', userResponse.error) } - // Load puzzles from API + // Load puzzles from API using store console.log('Loading puzzles...') - const loadedPuzzles = await puzzleHelpers.loadPuzzles() - puzzles.value = loadedPuzzles - console.log('Puzzles loaded:', loadedPuzzles.length) + await puzzlesStore.loadPuzzles() + console.log('Puzzles loaded:', puzzlesStore.puzzles.length) // Create mock collection from loaded puzzles for display - if (loadedPuzzles.length > 0) { + if (puzzlesStore.puzzles.length > 0) { collections.value = [{ id: 1, steam_id: '3479142989', title: 'PolyLAN 41', description: 'Puzzle collection for PolyLAN 41 fil rouge', author_name: 'Flame Legrems', - total_items: loadedPuzzles.length, + total_items: puzzlesStore.puzzles.length, unique_visitors: 31, current_favorites: 1, created_at: new Date().toISOString(), @@ -78,11 +80,10 @@ onMounted(async () => { console.log('Collection created') } - // Load existing submissions + // Load existing submissions using store console.log('Loading submissions...') - const loadedSubmissions = await submissionHelpers.loadSubmissions() - submissions.value = loadedSubmissions - console.log('Submissions loaded:', loadedSubmissions.length) + await submissionsStore.loadSubmissions() + console.log('Submissions loaded:', submissionsStore.submissions.length) console.log('Data load complete!') @@ -104,32 +105,24 @@ const handleSubmission = async (submissionData: { isLoading.value = true error.value = '' - // Create submission via API - const response = await submissionHelpers.createFromFiles( + // Create submission via store + const submission = await submissionsStore.createSubmission( submissionData.files, - puzzles.value, submissionData.notes, submissionData.manualValidationRequested ) - - if (response.error) { - error.value = response.error - alert(`Submission failed: ${response.error}`) - return - } - - if (response.data) { - // Add to local submissions list - submissions.value.unshift(response.data) - // Show success message - const puzzleNames = response.data.responses.map(r => r.puzzle_name).join(', ') + // Show success message + if (submission) { + const puzzleNames = submission.responses.map(r => r.puzzle_name).join(', ') alert(`Solutions submitted successfully for puzzles: ${puzzleNames}`) - - // Close modal - showSubmissionModal.value = false + } else { + alert('Submission created successfully!') } + // Close modal + submissionsStore.closeSubmissionModal() + } catch (err) { const errorMessage = errorHelpers.getErrorMessage(err) error.value = errorMessage @@ -141,16 +134,16 @@ const handleSubmission = async (submissionData: { } const openSubmissionModal = () => { - showSubmissionModal.value = true + submissionsStore.openSubmissionModal() } const closeSubmissionModal = () => { - showSubmissionModal.value = false + submissionsStore.closeSubmissionModal() } // Function to match puzzle name from OCR to actual puzzle -const findPuzzleByName = (ocrPuzzleName: string): SteamCollectionItem | null => { - return puzzleHelpers.findPuzzleByName(puzzles.value, ocrPuzzleName) +const findPuzzleByName = (ocrPuzzleName: string) => { + return puzzlesStore.findPuzzleByName(ocrPuzzleName) } const reloadPage = () => { @@ -237,7 +230,7 @@ const reloadPage = () => {
{
-
+
🧩

No Puzzles Available

Check back later for new puzzle collections!

@@ -254,7 +247,7 @@ const reloadPage = () => {
-