Files
memory-infrastructure-palace/code/websites/pokedex.online/src/views/GamemasterManager.vue

614 lines
13 KiB
Vue

<template>
<div class="gamemaster-manager">
<div class="container">
<div class="header">
<router-link to="/" class="back-button"> Back Home </router-link>
<h1>Gamemaster Manager</h1>
</div>
<p class="description">
Fetch the latest Pokemon GO gamemaster data from PokeMiners and break it
up into separate files for easier processing.
</p>
<!-- Fetch Section -->
<div class="section">
<h2>1. Fetch Latest Gamemaster</h2>
<button
@click="fetchGamemaster"
:disabled="loading"
class="btn btn-primary"
>
{{ loading ? 'Fetching...' : 'Fetch from PokeMiners' }}
</button>
<div v-if="error" class="error">
{{ error }}
</div>
<div v-if="rawGamemaster" class="success">
Fetched {{ rawGamemaster.length.toLocaleString() }} items from
gamemaster
</div>
</div>
<!-- Break Up Section -->
<div v-if="rawGamemaster" class="section">
<h2>2. Break Up Gamemaster</h2>
<button @click="processGamemaster" class="btn btn-primary">
Process & Break Up Data
</button>
<div v-if="processedData" class="stats-grid">
<div class="stat-card">
<h3>Pokemon (Filtered)</h3>
<p class="stat-number">
{{ stats.pokemonCount.toLocaleString() }}
</p>
<p class="stat-detail">{{ stats.pokemonSize }}</p>
<p class="stat-info">Base forms + regional variants</p>
</div>
<div class="stat-card">
<h3>All Forms & Costumes</h3>
<p class="stat-number">
{{ stats.allFormsCount.toLocaleString() }}
</p>
<p class="stat-detail">{{ stats.allFormsSize }}</p>
<p class="stat-info">Every variant, costume, form</p>
</div>
<div class="stat-card">
<h3>Moves</h3>
<p class="stat-number">{{ stats.movesCount.toLocaleString() }}</p>
<p class="stat-detail">{{ stats.movesSize }}</p>
<p class="stat-info">All quick & charged moves</p>
</div>
</div>
</div>
<!-- Save to Server Section -->
<div v-if="processedData" class="section">
<h2>3. Save to Server</h2>
<p class="description">
Store processed data on server for other apps to access via API
</p>
<button
@click="saveToServer"
:disabled="saving"
class="btn btn-primary"
>
{{ saving ? 'Saving...' : '💾 Save All Files to Server' }}
</button>
<div v-if="saveSuccess" class="success">
Files saved to server successfully! Other apps can now access the
data via the Gamemaster API.
</div>
</div>
<!-- Download Section -->
<div v-if="processedData" class="section">
<h2>4. Download Files</h2>
<div class="button-group">
<button @click="downloadPokemon" class="btn btn-success">
📥 Download pokemon.json
</button>
<button @click="downloadAllForms" class="btn btn-success">
📥 Download pokemon-allFormsCostumes.json
</button>
<button @click="downloadMoves" class="btn btn-success">
📥 Download pokemon-moves.json
</button>
<button @click="downloadAll" class="btn btn-primary">
📦 Download All Files
</button>
</div>
</div>
<!-- Server Status Section -->
<div v-if="serverStatus" class="section">
<h2>5. Server Storage Status</h2>
<div class="status-card">
<p>
<strong>Last Update:</strong>
{{ formatDate(serverStatus.lastUpdate) }}
</p>
<p><strong>Files Available:</strong> {{ serverStatus.totalFiles }}</p>
<div class="file-list">
<div
v-for="file in serverStatus.available"
:key="file.filename"
class="file-item"
>
<span class="file-name">{{ file.filename }}</span>
<span class="file-size">{{ file.sizeKb }} KB</span>
</div>
</div>
</div>
</div>
<!-- Info Section -->
<div class="section info-section">
<h2>About This Tool</h2>
<p>
This tool fetches the latest Pokemon GO gamemaster data from
<a
href="https://github.com/PokeMiners/game_masters"
target="_blank"
rel="noopener"
>PokeMiners GitHub</a
>
and processes it into three separate files:
</p>
<ul>
<li>
<strong>pokemon.json</strong> - Base Pokemon forms plus regional
variants (Alola, Galarian, Hisuian, Paldea)
</li>
<li>
<strong>pokemon-allFormsCostumes.json</strong> - Complete dataset
including all costumes, event forms, shadows, etc.
</li>
<li>
<strong>pokemon-moves.json</strong> - All quick and charged moves
available in Pokemon GO
</li>
</ul>
<p class="note">
💡 The filtered pokemon.json is ideal for most use cases, while
allFormsCostumes is comprehensive for complete data analysis.
</p>
<h3 style="margin-top: 1.5rem; color: #667eea;">Using the API</h3>
<p>
Once data is saved to the server, other apps can access it via the
Gamemaster API:
</p>
<pre class="code-block"><code>import { GamemasterClient } from './gamemaster-client.js';
const gm = new GamemasterClient('/api/gamemaster');
// Get filtered pokemon
const pokemon = await gm.getPokemon();
// Get all forms with costumes
const allForms = await gm.getAllForms();
// Get moves
const moves = await gm.getMoves();
// Check what's available
const status = await gm.getStatus();</code></pre>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import {
fetchLatestGamemaster,
breakUpGamemaster,
downloadJson,
getGamemasterStats
} from '../utilities/gamemaster-utils.js';
const loading = ref(false);
const error = ref(null);
const saving = ref(false);
const saveSuccess = ref(false);
const rawGamemaster = ref(null);
const processedData = ref(null);
const serverStatus = ref(null);
const stats = computed(() => {
if (!processedData.value) return null;
return getGamemasterStats(processedData.value);
});
onMounted(async () => {
// Load server status on component mount
await loadServerStatus();
});
async function loadServerStatus() {
try {
const response = await fetch('/api/gamemaster/status');
if (response.ok) {
serverStatus.value = await response.json();
}
} catch (err) {
console.error('Failed to load server status:', err);
}
}
async function fetchGamemaster() {
loading.value = true;
error.value = null;
rawGamemaster.value = null;
processedData.value = null;
try {
const data = await fetchLatestGamemaster();
rawGamemaster.value = data;
} catch (err) {
error.value = `Failed to fetch gamemaster: ${err.message}`;
} finally {
loading.value = false;
}
}
function processGamemaster() {
if (!rawGamemaster.value) return;
try {
processedData.value = breakUpGamemaster(rawGamemaster.value);
} catch (err) {
error.value = `Failed to process gamemaster: ${err.message}`;
}
}
async function saveToServer() {
if (!processedData.value) return;
saving.value = true;
saveSuccess.value = false;
error.value = null;
try {
const response = await fetch('/api/gamemaster/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
pokemon: processedData.value.pokemon,
pokemonAllForms: processedData.value.pokemonAllForms,
moves: processedData.value.moves,
raw: rawGamemaster.value
})
});
if (!response.ok) {
const err = await response.json();
throw new Error(err.error || 'Failed to save to server');
}
saveSuccess.value = true;
// Reload server status
await loadServerStatus();
} catch (err) {
error.value = `Failed to save to server: ${err.message}`;
} finally {
saving.value = false;
}
}
function downloadPokemon() {
downloadJson(processedData.value.pokemon, 'pokemon.json');
}
function downloadAllForms() {
downloadJson(
processedData.value.pokemonAllForms,
'pokemon-allFormsCostumes.json'
);
}
function downloadMoves() {
downloadJson(processedData.value.moves, 'pokemon-moves.json');
}
function downloadAll() {
downloadPokemon();
setTimeout(() => downloadAllForms(), 500);
setTimeout(() => downloadMoves(), 1000);
}
function formatDate(dateString) {
if (!dateString || dateString === 'Never') return 'Never';
return new Date(dateString).toLocaleString();
}
</script>
</script>
<style scoped>
.gamemaster-manager {
min-height: 100vh;
padding: 2rem 1rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 12px;
padding: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.back-button {
padding: 0.5rem 1rem;
background: #667eea;
color: white;
text-decoration: none;
border-radius: 6px;
font-weight: 600;
transition: all 0.3s ease;
display: inline-block;
}
.back-button:hover {
background: #5568d3;
transform: translateX(-2px);
}
h1 {
color: #333;
margin: 0;
font-size: 2.5rem;
}
.description {
color: #666;
font-size: 1.1rem;
margin-bottom: 2rem;
}
.section {
margin: 2rem 0;
padding: 1.5rem;
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
}
h2 {
color: #495057;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.btn {
padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: 600;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #5568d3;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.btn-success {
background: #10b981;
color: white;
}
.btn-success:hover {
background: #059669;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
}
.button-group {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.error {
margin-top: 1rem;
padding: 1rem;
background: #fee;
color: #c33;
border-radius: 6px;
border-left: 4px solid #c33;
}
.success {
margin-top: 1rem;
padding: 1rem;
background: #d1fae5;
color: #065f46;
border-radius: 6px;
border-left: 4px solid #10b981;
font-weight: 500;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.stat-card {
background: white;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
text-align: center;
}
.stat-card h3 {
font-size: 1rem;
color: #666;
margin-bottom: 0.5rem;
}
.stat-number {
font-size: 2.5rem;
font-weight: bold;
color: #667eea;
margin: 0.5rem 0;
}
.stat-detail {
font-size: 1rem;
color: #999;
margin: 0.25rem 0;
}
.stat-info {
font-size: 0.875rem;
color: #666;
margin-top: 0.5rem;
}
.info-section {
background: #e7f3ff;
border-color: #667eea;
}
.info-section h2 {
color: #667eea;
}
.info-section ul {
margin: 1rem 0;
padding-left: 1.5rem;
}
.info-section li {
margin: 0.5rem 0;
color: #495057;
}
.info-section a {
color: #667eea;
text-decoration: none;
font-weight: 600;
}
.info-section a:hover {
text-decoration: underline;
}
.note {
margin-top: 1rem;
padding: 0.75rem;
background: white;
border-radius: 6px;
font-size: 0.95rem;
color: #495057;
}
.status-card {
background: white;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.status-card p {
margin: 0.75rem 0;
color: #495057;
font-size: 1rem;
}
.status-card strong {
color: #667eea;
}
.file-list {
margin-top: 1rem;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 0.75rem;
}
.file-item {
background: #f8f9fa;
padding: 0.75rem;
border-radius: 6px;
border: 1px solid #e9ecef;
display: flex;
justify-content: space-between;
align-items: center;
}
.file-name {
font-weight: 500;
color: #495057;
font-size: 0.9rem;
word-break: break-word;
flex: 1;
}
.file-size {
color: #999;
font-size: 0.85rem;
margin-left: 0.5rem;
white-space: nowrap;
}
.code-block {
background: #2d3748;
color: #e2e8f0;
padding: 1rem;
border-radius: 6px;
overflow-x: auto;
margin: 1rem 0;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.85rem;
line-height: 1.5;
}
.code-block code {
color: inherit;
}
@media (max-width: 768px) {
.container {
padding: 1rem;
}
h1 {
font-size: 2rem;
}
.stats-grid {
grid-template-columns: 1fr;
}
.button-group {
flex-direction: column;
}
.btn {
width: 100%;
margin-right: 0;
}
.file-list {
grid-template-columns: 1fr;
}
}
</style>