base for noita submitter
This commit is contained in:
@@ -1,250 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
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 Results from "@/components/Results.vue";
|
||||
import { apiService, errorHelpers } from "@/services/apiService";
|
||||
import { usePuzzlesStore } from "@/stores/puzzles";
|
||||
import { useSubmissionsStore } from "@/stores/submissions";
|
||||
import type { PuzzleResponse, UserInfo } from "@/types";
|
||||
import { useCountdown } from "@vueuse/core";
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
const props = defineProps<{
|
||||
collectionTitle: string;
|
||||
collectionUrl: string;
|
||||
collectionDescription: string;
|
||||
}>();
|
||||
|
||||
const puzzlesStore = usePuzzlesStore();
|
||||
const submissionsStore = useSubmissionsStore();
|
||||
|
||||
const { submissions, isSubmissionModalOpen } = storeToRefs(submissionsStore);
|
||||
const { openSubmissionModal, loadSubmissions, closeSubmissionModal } =
|
||||
submissionsStore;
|
||||
|
||||
// Local state
|
||||
const userInfo = ref<UserInfo | null>(null);
|
||||
const isLoading = ref(true);
|
||||
const error = ref<string>("");
|
||||
|
||||
// Computed properties
|
||||
const isSuperuser = computed(() => {
|
||||
return userInfo.value?.is_superuser || false;
|
||||
});
|
||||
|
||||
// Computed property to get responses grouped by puzzle
|
||||
const responsesByPuzzle = computed(() => {
|
||||
const grouped: Record<number, PuzzleResponse[]> = {};
|
||||
submissions.value.forEach((submission) => {
|
||||
submission.responses.forEach((response) => {
|
||||
// Handle both number and object types for puzzle field
|
||||
if (!grouped[response.puzzle_id]) {
|
||||
grouped[response.puzzle_id] = [];
|
||||
}
|
||||
grouped[response.puzzle_id].push(response);
|
||||
});
|
||||
});
|
||||
return grouped;
|
||||
});
|
||||
|
||||
async function initialize() {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
error.value = "";
|
||||
|
||||
console.log("Starting data load...");
|
||||
|
||||
// Load user info
|
||||
console.log("Loading user info...");
|
||||
const userResponse = await apiService.getUserInfo();
|
||||
if (userResponse.data) {
|
||||
userInfo.value = userResponse.data;
|
||||
console.log("User info loaded:", userResponse.data);
|
||||
} else if (userResponse.error) {
|
||||
console.warn("User info error:", userResponse.error);
|
||||
}
|
||||
|
||||
// Load puzzles from API using store
|
||||
console.log("Loading puzzles...");
|
||||
await puzzlesStore.loadPuzzles();
|
||||
console.log("Puzzles loaded:", puzzlesStore.puzzles.length);
|
||||
|
||||
// Load existing submissions using store
|
||||
console.log("Loading submissions...");
|
||||
await loadSubmissions();
|
||||
console.log("Submissions loaded:", submissions.value.length);
|
||||
|
||||
console.log("Data load complete!");
|
||||
} catch (err) {
|
||||
error.value = errorHelpers.getErrorMessage(err);
|
||||
console.error("Failed to load data:", err);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
console.log("Loading state set to false");
|
||||
}
|
||||
|
||||
if (userInfo.value?.is_superuser) {
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
const { remaining, start } = useCountdown(60, {
|
||||
onComplete() {
|
||||
initialize();
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await initialize();
|
||||
});
|
||||
|
||||
// Function to match puzzle name from OCR to actual puzzle
|
||||
const findPuzzleByName = (ocrPuzzleName: string) => {
|
||||
return puzzlesStore.findPuzzleByName(ocrPuzzleName);
|
||||
};
|
||||
|
||||
const reloadPage = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
</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 class="flex items-start justify-between">
|
||||
<div
|
||||
v-if="userInfo?.is_authenticated"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<div class="text-sm">
|
||||
<span class="font-medium">{{ userInfo.username }}</span>
|
||||
<span
|
||||
v-if="userInfo.is_superuser"
|
||||
class="badge badge-warning badge-xs ml-1"
|
||||
>Admin</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-sm text-base-content/70">Not logged in</div>
|
||||
<div class="flex flex-col items-end gap-2">
|
||||
<a href="/api/docs" class="btn btn-xs">API docs</a>
|
||||
</div>
|
||||
<div class="flex flex-col items-end gap-2">
|
||||
<a href="/admin" class="btn btn-xs btn-warning">Admin panel</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<!-- Loading State -->
|
||||
<div v-if="userInfo?.is_superuser" class="flex justify-center">
|
||||
<div class="text-center">
|
||||
<p class="mb-6 text-base-content/70">
|
||||
<span class="loading loading-spinner loading-lg"></span>
|
||||
Auto reload page in {{ remaining }} seconds ...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<!-- Error State -->
|
||||
<div v-else-if="error" class="alert alert-error max-w-2xl mx-auto">
|
||||
<i class="mdi mdi-alert-circle text-xl"></i>
|
||||
<div>
|
||||
<h3 class="font-bold">Error Loading Data</h3>
|
||||
<div class="text-sm">{{ error }}</div>
|
||||
</div>
|
||||
<button @click="reloadPage" class="btn btn-sm btn-outline">
|
||||
<i class="mdi mdi-refresh mr-1"></i>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div v-else class="space-y-8">
|
||||
<!-- Collection Info -->
|
||||
<div class="mb-8">
|
||||
<div class="card bg-base-100 shadow-lg">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-2xl">{{ props.collectionTitle }}</h2>
|
||||
<p class="text-base-content/70">
|
||||
{{ props.collectionDescription }}
|
||||
</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>
|
||||
|
||||
<Results />
|
||||
|
||||
<!-- Admin Panel (only for superusers) -->
|
||||
<div v-if="isSuperuser">
|
||||
<AdminPanel />
|
||||
</div>
|
||||
|
||||
<!-- Puzzles Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<PuzzleCard
|
||||
v-for="puzzle in puzzlesStore.puzzles"
|
||||
:key="puzzle.id"
|
||||
:puzzle="puzzle"
|
||||
:responses="responsesByPuzzle[puzzle.id] || []"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-if="puzzlesStore.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="isSubmissionModalOpen" class="modal modal-open">
|
||||
<div class="modal-box max-w-6xl">
|
||||
<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="puzzlesStore.puzzles"
|
||||
:find-puzzle-by-name="findPuzzleByName"
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-backdrop" @click="closeSubmissionModal"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user