Files
polylan-submitter/opus_submitter/src/App.vue
T
2025-10-29 02:35:56 +01:00

248 lines
7.3 KiB
Vue

<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import PuzzleCard from './components/PuzzleCard.vue'
import SubmissionForm from './components/SubmissionForm.vue'
import type { SteamCollection, SteamCollectionItem, Submission, PuzzleResponse } from './types'
// Mock data - replace with actual API calls later
const collections = ref<SteamCollection[]>([])
const puzzles = ref<SteamCollectionItem[]>([])
const submissions = ref<Submission[]>([])
const isLoading = ref(true)
const showSubmissionModal = ref(false)
// Mock data for development
const mockCollections: SteamCollection[] = [
{
id: 1,
steam_id: '3479142989',
title: 'PolyLAN 41',
description: 'Puzzle for PolyLAN 41 fil rouge',
author_name: 'Flame Legrems',
total_items: 10,
unique_visitors: 31,
current_favorites: 1,
created_at: '2025-05-29T11:19:24Z',
updated_at: '2025-05-30T22:15:09Z'
}
]
const mockPuzzles: SteamCollectionItem[] = [
{
id: 1,
steam_item_id: '3479143948',
title: 'P41-FLOC',
author_name: 'Flame Legrems',
description: 'A challenging puzzle involving complex molecular arrangements',
tags: ['puzzle', 'chemistry', 'advanced'],
order_index: 0,
collection: 1,
created_at: '2025-05-29T11:19:24Z',
updated_at: '2025-05-30T22:15:09Z'
},
{
id: 2,
steam_item_id: '3479143084',
title: 'P41-40',
author_name: 'Flame Legrems',
description: 'Test your optimization skills with this intricate design challenge',
tags: ['optimization', 'design'],
order_index: 1,
collection: 1,
created_at: '2025-05-29T11:19:24Z',
updated_at: '2025-05-30T22:15:09Z'
},
{
id: 3,
steam_item_id: '3479143304',
title: 'P41-39',
author_name: 'Flame Legrems',
description: 'A puzzle focusing on efficient resource management',
tags: ['efficiency', 'resources'],
order_index: 2,
collection: 1,
created_at: '2025-05-29T11:19:24Z',
updated_at: '2025-05-30T22:15:09Z'
},
{
id: 4,
steam_item_id: '3479143433',
title: 'P41-38',
author_name: 'Flame Legrems',
description: 'Master the art of precise timing in this temporal challenge',
tags: ['timing', 'precision'],
order_index: 3,
collection: 1,
created_at: '2025-05-29T11:19:24Z',
updated_at: '2025-05-30T22:15:09Z'
},
{
id: 5,
steam_item_id: '3479143537',
title: 'P41-37',
author_name: 'Flame Legrems',
description: 'Explore innovative solutions in this creative puzzle',
tags: ['creative', 'innovation'],
order_index: 4,
collection: 1,
created_at: '2025-05-29T11:19:24Z',
updated_at: '2025-05-30T22:15:09Z'
}
]
// Computed property to get responses grouped by puzzle
const responsesByPuzzle = computed(() => {
const grouped: Record<number, PuzzleResponse[]> = {}
submissions.value.forEach(submission => {
submission.responses.forEach(response => {
if (!grouped[response.puzzle_id]) {
grouped[response.puzzle_id] = []
}
grouped[response.puzzle_id].push(response)
})
})
return grouped
})
onMounted(async () => {
// Simulate API loading
await new Promise(resolve => setTimeout(resolve, 500))
collections.value = mockCollections
puzzles.value = mockPuzzles
isLoading.value = false
})
const handleSubmission = (submission: Submission) => {
console.log('Submission received:', submission)
// Add submission to the list
submissions.value.push({
...submission,
id: Date.now(), // Simple ID generation for demo
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
})
// Show success message
const puzzleNames = submission.responses.map(r => r.puzzle_name).join(', ')
alert(`Solutions submitted for puzzles: ${puzzleNames}`)
// Close modal
showSubmissionModal.value = false
}
const openSubmissionModal = () => {
showSubmissionModal.value = true
}
const closeSubmissionModal = () => {
showSubmissionModal.value = false
}
// Function to match puzzle name from OCR to actual puzzle
const findPuzzleByName = (ocrPuzzleName: string): SteamCollectionItem | null => {
if (!ocrPuzzleName) return null
// Try exact match first
let match = puzzles.value.find(p =>
p.title.toLowerCase() === ocrPuzzleName.toLowerCase()
)
if (!match) {
// Try partial match
match = puzzles.value.find(p =>
p.title.toLowerCase().includes(ocrPuzzleName.toLowerCase()) ||
ocrPuzzleName.toLowerCase().includes(p.title.toLowerCase())
)
}
return match || null
}
</script>
<template>
<div class="min-h-screen bg-base-200">
<!-- Header -->
<div class="navbar bg-base-100 shadow-lg">
<div class="container mx-auto">
<div class="flex-1">
<h1 class="text-xl font-bold">Opus Magnum Puzzle Submitter</h1>
</div>
</div>
</div>
<!-- Main Content -->
<div class="container mx-auto px-4 py-8">
<!-- Loading State -->
<div v-if="isLoading" class="flex justify-center items-center min-h-[400px]">
<div class="text-center">
<span class="loading loading-spinner loading-lg"></span>
<p class="mt-4 text-base-content/70">Loading puzzles...</p>
</div>
</div>
<!-- Main Content -->
<div v-else class="space-y-8">
<!-- Collection Info -->
<div v-if="collections.length > 0" class="mb-8">
<div class="card bg-base-100 shadow-lg">
<div class="card-body">
<h2 class="card-title text-2xl">{{ collections[0].title }}</h2>
<p class="text-base-content/70">{{ collections[0].description }}</p>
<div class="flex flex-wrap gap-4 mt-4">
<button
@click="openSubmissionModal"
class="btn btn-primary"
>
<i class="mdi mdi-plus mr-2"></i>
Submit Solution
</button>
</div>
</div>
</div>
</div>
<!-- Puzzles Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<PuzzleCard
v-for="puzzle in puzzles"
:key="puzzle.id"
:puzzle="puzzle"
:responses="responsesByPuzzle[puzzle.id] || []"
/>
</div>
<!-- Empty State -->
<div v-if="puzzles.length === 0" class="text-center py-12">
<div class="text-6xl mb-4">🧩</div>
<h3 class="text-xl font-bold mb-2">No Puzzles Available</h3>
<p class="text-base-content/70">Check back later for new puzzle collections!</p>
</div>
</div>
</div>
<!-- Submission Modal -->
<div v-if="showSubmissionModal" class="modal modal-open">
<div class="modal-box max-w-4xl">
<div class="flex justify-between items-center mb-4">
<h3 class="font-bold text-lg">Submit Solution</h3>
<button
@click="closeSubmissionModal"
class="btn btn-sm btn-circle btn-ghost"
>
<i class="mdi mdi-close"></i>
</button>
</div>
<SubmissionForm
:puzzles="puzzles"
:find-puzzle-by-name="findPuzzleByName"
@submit="handleSubmission"
/>
</div>
<div class="modal-backdrop" @click="closeSubmissionModal"></div>
</div>
</div>
</template>