mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
Stuff i guess ?
This commit is contained in:
737
resources/views/admin/games/edit.twig
Normal file
737
resources/views/admin/games/edit.twig
Normal file
@@ -0,0 +1,737 @@
|
||||
{% extends 'admin/layout.twig' %}
|
||||
|
||||
{% block title %}{{ title }} - Admin Panel - MediaLib{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-0">{{ title }}</h1>
|
||||
<p class="text-muted mb-0">{{ game ? 'Edit game details' : 'Add a new game to your library' }}</p>
|
||||
</div>
|
||||
<a href="{{ path_for('admin.games.index') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-2"></i>Back to Games
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
{% if flash.getMessage('success') %}
|
||||
<div class="alert alert-success">
|
||||
{{ flash.getMessage('success') | first }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">Title *</label>
|
||||
<input type="text" class="form-control" id="title" name="title" required
|
||||
value="{{ old.title ?? game.title ?? '' }}">
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="release_date" class="form-label">Release Date</label>
|
||||
<input type="date" class="form-control" id="release_date" name="release_date"
|
||||
value="{{ old.release_date ?? (game.release_date ? game.release_date|date('Y-m-d') : '') }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="platform" class="form-label">Platform</label>
|
||||
<select class="form-select" id="platform" name="platform">
|
||||
<option value="">Select Platform</option>
|
||||
<option value="PC" {{ (old.platform ?? game.platform ?? '') == 'PC' ? 'selected' : '' }}>PC</option>
|
||||
<option value="PlayStation" {{ (old.platform ?? game.platform ?? '') == 'PlayStation' ? 'selected' : '' }}>PlayStation</option>
|
||||
<option value="Xbox" {{ (old.platform ?? game.platform ?? '') == 'Xbox' ? 'selected' : '' }}>Xbox</option>
|
||||
<option value="Nintendo" {{ (old.platform ?? game.platform ?? '') == 'Nintendo' ? 'selected' : '' }}>Nintendo</option>
|
||||
<option value="Mobile" {{ (old.platform ?? game.platform ?? '') == 'Mobile' ? 'selected' : '' }}>Mobile</option>
|
||||
<option value="Other" {{ (old.platform ?? game.platform ?? '') == 'Other' ? 'selected' : '' }}>Other</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="developer" class="form-label">Developer</label>
|
||||
<input type="text" class="form-control" id="developer" name="developer"
|
||||
value="{{ old.developer ?? game.developer ?? '' }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="publisher" class="form-label">Publisher</label>
|
||||
<input type="text" class="form-control" id="publisher" name="publisher"
|
||||
value="{{ old.publisher ?? game.publisher ?? '' }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="genres" class="form-label">Genres (comma-separated)</label>
|
||||
<input type="text" class="form-control" id="genres" name="genres"
|
||||
value="{{ old.genres ?? (game.genres ? game.genres|join(', ') : '') }}">
|
||||
<div class="form-text">Example: Action, Adventure, RPG</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">Description</label>
|
||||
<textarea class="form-control" id="description" name="description" rows="4">{{ old.description ?? game.description ?? '' }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="cover_image" class="form-label">Cover Image URL</label>
|
||||
<input type="text" class="form-control" id="cover_image" name="cover_image"
|
||||
value="{{ old.cover_image ?? game.cover_image ?? '' }}">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="trailer_url" class="form-label">Trailer URL</label>
|
||||
<input type="text" class="form-control" id="trailer_url" name="trailer_url"
|
||||
value="{{ old.trailer_url ?? game.trailer_url ?? '' }}">
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-save me-2"></i>Save Changes
|
||||
</button>
|
||||
|
||||
{% if game %}
|
||||
<button type="button" class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">
|
||||
<i class="bi bi-trash me-2"></i>Delete Game
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Cover Preview</h5>
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<div id="coverPreview" class="mb-3" style="min-height: 300px; display: flex; align-items: center; justify-content: center; background-color: #f8f9fa;">
|
||||
{% if game and game.cover_image %}
|
||||
<img src="{{ game.cover_image }}" alt="Cover" class="img-fluid" style="max-height: 300px;">
|
||||
{% else %}
|
||||
<div class="text-muted">No cover available</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SteamGridDB Media -->
|
||||
{% if game %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">SteamGridDB Media</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Search for game media</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="sgdb-search" class="form-control" placeholder="Search for a game...">
|
||||
<button class="btn btn-primary" type="button" id="sgdb-search-btn">
|
||||
<i class="bi bi-search"></i> Search
|
||||
</button>
|
||||
</div>
|
||||
<div id="sgdb-search-results" class="mt-3"></div>
|
||||
</div>
|
||||
|
||||
<div id="media-selection" class="d-none">
|
||||
<h6>Available Media</h6>
|
||||
<div class="mb-3">
|
||||
<ul id="media-tabs" class="nav nav-tabs" role="tablist"></ul>
|
||||
<div id="media-content" class="tab-content p-3 border border-top-0 rounded-bottom"></div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<button class="btn btn-sm btn-outline-secondary" id="back-to-search">
|
||||
<i class="bi bi-arrow-left me-1"></i> Back to Search
|
||||
</button>
|
||||
<button class="btn btn-sm btn-primary set-media-btn" disabled>
|
||||
<i class="bi bi-check-circle me-1"></i> Use Selected
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Metadata -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Metadata</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-2">
|
||||
<span class="text-muted">Created:</span>
|
||||
<span class="float-end">{{ game ? game.created_at|date('Y-m-d H:i') : 'New' }}</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="text-muted">Last Updated:</span>
|
||||
<span class="float-end">{{ game ? game.updated_at|date('Y-m-d H:i') : 'N/A' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Confirmation Modal -->
|
||||
{% if game %}
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="deleteModalLabel">Confirm Deletion</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to delete "{{ game.title }}"? This action cannot be undone.</p>
|
||||
<p class="text-danger"><strong>Warning:</strong> This will remove all data associated with this game.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<form action="{{ path_for('admin.games.delete', {id: game.id}) }}" method="post" class="d-inline">
|
||||
<input type="hidden" name="_METHOD" value="DELETE">
|
||||
<button type="submit" class="btn btn-danger">Delete Game</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block styles %}
|
||||
<style>
|
||||
.media-tab-pane {
|
||||
display: none;
|
||||
}
|
||||
.media-tab-pane.active {
|
||||
display: block;
|
||||
}
|
||||
.media-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.media-item {
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.media-item:hover {
|
||||
border-color: var(--bs-primary);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.media-item.selected {
|
||||
border-color: var(--bs-primary);
|
||||
box-shadow: 0 0 0 2px var(--bs-primary);
|
||||
}
|
||||
.media-item img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
}
|
||||
.media-preview {
|
||||
max-width: 100%;
|
||||
max-height: 300px;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Define all functions first
|
||||
async function loadGameMedia(gameId) {
|
||||
const tabs = [
|
||||
{ type: 'grids', title: 'Grids', field: 'cover_url' },
|
||||
{ type: 'heroes', title: 'Heroes', field: 'banner_url' },
|
||||
{ type: 'icons', title: 'Icons', field: 'icon_url' },
|
||||
{ type: 'logos', title: 'Logos', field: 'logo_url' }
|
||||
];
|
||||
|
||||
// Add hidden fields if they don't exist
|
||||
const form = document.querySelector('form');
|
||||
['banner_url', 'icon_url', 'logo_url'].forEach(field => {
|
||||
if (!form.querySelector(`[name="${field}"]`)) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = field;
|
||||
input.id = field;
|
||||
form.appendChild(input);
|
||||
}
|
||||
});
|
||||
|
||||
const tabsContainer = document.getElementById('media-tabs');
|
||||
const contentContainer = document.getElementById('media-content');
|
||||
|
||||
// Clear existing content
|
||||
tabsContainer.innerHTML = '';
|
||||
contentContainer.innerHTML = '';
|
||||
|
||||
// Create tabs
|
||||
const tabList = document.createElement('ul');
|
||||
tabList.className = 'nav nav-tabs';
|
||||
tabList.role = 'tablist';
|
||||
|
||||
// Create tab content
|
||||
tabs.forEach((tab, index) => {
|
||||
// Tab button
|
||||
const tabItem = document.createElement('li');
|
||||
tabItem.className = 'nav-item';
|
||||
tabItem.role = 'presentation';
|
||||
|
||||
const tabButton = document.createElement('button');
|
||||
tabButton.className = `nav-link ${index === 0 ? 'active' : ''}`;
|
||||
tabButton.setAttribute('data-bs-toggle', 'tab');
|
||||
tabButton.setAttribute('data-bs-target', `#${tab.type}-pane`);
|
||||
tabButton.type = 'button';
|
||||
tabButton.role = 'tab';
|
||||
tabButton.textContent = tab.title;
|
||||
|
||||
tabItem.appendChild(tabButton);
|
||||
tabList.appendChild(tabItem);
|
||||
|
||||
// Tab content
|
||||
const tabPane = document.createElement('div');
|
||||
tabPane.id = `${tab.type}-pane`;
|
||||
tabPane.className = `media-tab-pane fade ${index === 0 ? 'show active' : ''}`;
|
||||
tabPane.setAttribute('data-field', tab.field);
|
||||
tabPane.role = 'tabpanel';
|
||||
tabPane.innerHTML = `
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h6>Select a ${tab.title.toLowerCase()} image</h6>
|
||||
<div class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="media-grid" id="${tab.type}-grid"></div>
|
||||
<div class="mt-3">
|
||||
<img src="" class="media-preview d-none img-thumbnail mb-2" alt="Selected image">
|
||||
<button type="button" class="btn btn-sm btn-primary use-selected-btn" disabled>
|
||||
<i class="bi bi-check-circle me-1"></i> Use Selected
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
contentContainer.appendChild(tabPane);
|
||||
|
||||
// Load media for this tab
|
||||
loadMediaForTab(tab.type, tab.field, gameId);
|
||||
});
|
||||
|
||||
tabsContainer.appendChild(tabList);
|
||||
|
||||
// Show the media selection section
|
||||
document.getElementById('media-selection').classList.remove('d-none');
|
||||
}
|
||||
|
||||
async function loadMediaForTab(type, field, gameId) {
|
||||
const grid = document.getElementById(`${type}-grid`);
|
||||
if (!grid) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/admin/games/sgdb/media/${gameId}/${type}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.data && data.data.length > 0) {
|
||||
grid.innerHTML = '';
|
||||
|
||||
data.data.forEach((item, index) => {
|
||||
const mediaItem = document.createElement('div');
|
||||
mediaItem.className = 'media-item';
|
||||
mediaItem.setAttribute('data-url', item.url);
|
||||
mediaItem.setAttribute('data-thumb', item.thumb || item.url);
|
||||
mediaItem.setAttribute('title', item.style || item.dimensions || '');
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = item.thumb || item.url;
|
||||
img.alt = `Image ${index + 1}`;
|
||||
img.className = 'img-fluid';
|
||||
|
||||
mediaItem.appendChild(img);
|
||||
grid.appendChild(mediaItem);
|
||||
|
||||
// Add click handler for media selection
|
||||
mediaItem.addEventListener('click', function() {
|
||||
const container = this.closest('.media-tab-pane');
|
||||
container.querySelectorAll('.media-item').forEach(i => i.classList.remove('selected'));
|
||||
this.classList.add('selected');
|
||||
|
||||
const url = this.getAttribute('data-url');
|
||||
const field = container.getAttribute('data-field');
|
||||
const preview = container.querySelector('.media-preview');
|
||||
|
||||
if (preview) {
|
||||
preview.src = url;
|
||||
preview.classList.remove('d-none');
|
||||
}
|
||||
|
||||
// Enable the use selected button
|
||||
const useButton = container.querySelector('.use-selected-btn');
|
||||
if (useButton) {
|
||||
useButton.disabled = false;
|
||||
useButton.onclick = function() {
|
||||
// Update the corresponding form field
|
||||
const field = container.getAttribute('data-field');
|
||||
let input = document.querySelector(`input[name="${field}"]`);
|
||||
|
||||
// If input doesn't exist, create it
|
||||
if (!input) {
|
||||
input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = field;
|
||||
input.id = field;
|
||||
document.querySelector('form').appendChild(input);
|
||||
}
|
||||
|
||||
input.value = url;
|
||||
|
||||
// For cover_url, also update the visible input if it exists
|
||||
if (field === 'cover_url') {
|
||||
const coverInput = document.getElementById('cover_url');
|
||||
if (coverInput) {
|
||||
coverInput.value = url;
|
||||
}
|
||||
}
|
||||
|
||||
// Show success message
|
||||
const alert = document.createElement('div');
|
||||
alert.className = 'alert alert-success mt-2';
|
||||
alert.textContent = 'Media selected successfully!';
|
||||
container.querySelector('.media-preview').insertAdjacentElement('afterend', alert);
|
||||
|
||||
// Remove the alert after 3 seconds
|
||||
setTimeout(() => alert.remove(), 3000);
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
grid.innerHTML = '<div class="alert alert-info">No media found for this type.</div>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error loading ${type}:`, error);
|
||||
grid.innerHTML = '<div class="alert alert-danger">Failed to load media. Please try again.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Media tabs
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Handle tab switching
|
||||
const tabButtons = document.querySelectorAll('[data-bs-toggle="tab"]');
|
||||
|
||||
// Initialize first tab as active
|
||||
if (tabButtons.length > 0) {
|
||||
document.querySelector('#media-tabs .nav-link').click();
|
||||
}
|
||||
|
||||
tabButtons.forEach(button => {
|
||||
button.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const target = this.getAttribute('data-bs-target');
|
||||
document.querySelectorAll('.media-tab-pane').forEach(pane => {
|
||||
pane.classList.remove('active');
|
||||
});
|
||||
document.querySelector(target).classList.add('active');
|
||||
|
||||
// Update active tab button
|
||||
tabButtons.forEach(btn => btn.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Load media for a specific game
|
||||
async function loadGameMedia(gameId) {
|
||||
const tabs = [
|
||||
{ type: 'grids', title: 'Grids', field: 'image_url' },
|
||||
{ type: 'heroes', title: 'Heroes', field: 'banner_url' },
|
||||
{ type: 'icons', title: 'Icons', field: 'icon' },
|
||||
{ type: 'logos', title: 'Logos', field: 'logo_url' }
|
||||
];
|
||||
|
||||
const tabsContainer = document.getElementById('media-tabs');
|
||||
const contentContainer = document.getElementById('media-content');
|
||||
|
||||
// Clear existing content
|
||||
tabsContainer.innerHTML = '';
|
||||
contentContainer.innerHTML = '';
|
||||
|
||||
// Create tabs
|
||||
const tabList = document.createElement('ul');
|
||||
tabList.className = 'nav nav-tabs';
|
||||
tabList.role = 'tablist';
|
||||
|
||||
// Create tab content
|
||||
tabs.forEach((tab, index) => {
|
||||
// Tab button
|
||||
const tabItem = document.createElement('li');
|
||||
tabItem.className = 'nav-item';
|
||||
tabItem.role = 'presentation';
|
||||
|
||||
const tabButton = document.createElement('button');
|
||||
tabButton.className = `nav-link ${index === 0 ? 'active' : ''}`;
|
||||
tabButton.setAttribute('data-bs-toggle', 'tab');
|
||||
tabButton.setAttribute('data-bs-target', `#${tab.type}-pane`);
|
||||
tabButton.type = 'button';
|
||||
tabButton.role = 'tab';
|
||||
tabButton.textContent = tab.title;
|
||||
|
||||
tabItem.appendChild(tabButton);
|
||||
tabList.appendChild(tabItem);
|
||||
|
||||
// Tab content
|
||||
const tabPane = document.createElement('div');
|
||||
tabPane.id = `${tab.type}-pane`;
|
||||
tabPane.className = `media-tab-pane fade ${index === 0 ? 'show active' : ''}`;
|
||||
tabPane.setAttribute('data-field', tab.field);
|
||||
tabPane.role = 'tabpanel';
|
||||
tabPane.innerHTML = `
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h6>Select a ${tab.title.toLowerCase()} image</h6>
|
||||
<div class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="media-grid" id="${tab.type}-grid"></div>
|
||||
<img src="" class="media-preview d-none img-thumbnail" alt="Selected image">
|
||||
`;
|
||||
|
||||
contentContainer.appendChild(tabPane);
|
||||
|
||||
// Load media for this tab
|
||||
loadMediaForTab(tab.type, tab.field, gameId);
|
||||
});
|
||||
|
||||
tabsContainer.appendChild(tabList);
|
||||
|
||||
// Show the first tab
|
||||
if (tabs.length > 0) {
|
||||
document.querySelector('#media-tabs .nav-link').click();
|
||||
}
|
||||
}
|
||||
|
||||
// Load media for a specific tab
|
||||
async function loadMediaForTab(type, field, gameId) {
|
||||
const grid = document.getElementById(`${type}-grid`);
|
||||
if (!grid) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/admin/games/sgdb/media/${gameId}/${type}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.data.length > 0) {
|
||||
grid.innerHTML = '';
|
||||
|
||||
data.data.forEach((item, index) => {
|
||||
const img = document.createElement('div');
|
||||
img.className = 'media-item';
|
||||
img.setAttribute('data-url', item.url);
|
||||
img.setAttribute('data-thumb', item.thumb || item.url);
|
||||
img.setAttribute('title', item.style || item.dimensions || '');
|
||||
|
||||
const imgElement = document.createElement('img');
|
||||
imgElement.src = item.thumb || item.url;
|
||||
imgElement.alt = `Image ${index + 1}`;
|
||||
imgElement.loading = 'lazy';
|
||||
|
||||
img.appendChild(imgElement);
|
||||
grid.appendChild(img);
|
||||
|
||||
// Add click handler to update the form field
|
||||
img.addEventListener('click', function() {
|
||||
const url = this.getAttribute('data-url');
|
||||
const input = document.querySelector(`input[name="${field}"]`);
|
||||
if (input) {
|
||||
input.value = url;
|
||||
}
|
||||
|
||||
// Show preview
|
||||
const preview = this.closest('.media-tab-pane').querySelector('.media-preview');
|
||||
if (preview) {
|
||||
preview.src = url;
|
||||
preview.classList.remove('d-none');
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
grid.innerHTML = '<div class="col-12"><p class="text-muted">No media found.</p></div>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error loading ${type}:`, error);
|
||||
grid.innerHTML = '<div class="col-12"><p class="text-danger">Error loading media. Please try again.</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Handle form submission for setting media
|
||||
document.querySelectorAll('.set-media-btn').forEach(button => {
|
||||
button.addEventListener('click', async function() {
|
||||
const tabPane = this.closest('.media-tab-pane');
|
||||
const selectedItem = tabPane.querySelector('.media-item.selected');
|
||||
|
||||
if (!selectedItem) {
|
||||
alert('Please select an image first');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = selectedItem.getAttribute('data-url');
|
||||
const field = tabPane.getAttribute('data-field');
|
||||
const gameId = '{{ game.id ?? 0 }}';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/admin/games/${gameId}/sgdb/media`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: field.replace('_url', '').replace('_', '-'),
|
||||
url: url
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Update the form field
|
||||
const input = document.querySelector(`input[name="${field}"]`);
|
||||
if (input) {
|
||||
input.value = data.data.url;
|
||||
}
|
||||
|
||||
// Show success message
|
||||
const alert = document.createElement('div');
|
||||
alert.className = 'alert alert-success mt-3';
|
||||
alert.textContent = 'Media updated successfully';
|
||||
tabPane.insertBefore(alert, tabPane.firstChild);
|
||||
|
||||
// Remove the message after 3 seconds
|
||||
setTimeout(() => {
|
||||
alert.remove();
|
||||
}, 3000);
|
||||
} else {
|
||||
throw new Error(data.message || 'Failed to update media');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating media:', error);
|
||||
alert('Failed to update media: ' + error.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Handle SteamGridDB search
|
||||
const searchInput = document.getElementById('sgdb-search');
|
||||
const searchBtn = document.getElementById('sgdb-search-btn');
|
||||
const searchResults = document.getElementById('sgdb-search-results');
|
||||
const mediaSelection = document.getElementById('media-selection');
|
||||
const backToSearch = document.getElementById('back-to-search');
|
||||
|
||||
if (searchBtn && searchInput) {
|
||||
// Search for games
|
||||
searchBtn.addEventListener('click', async () => {
|
||||
const query = searchInput.value.trim();
|
||||
if (!query) return;
|
||||
|
||||
searchBtn.disabled = true;
|
||||
searchBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Searching...';
|
||||
searchResults.innerHTML = '<div class="text-center"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div><p class="mt-2">Searching for games...</p></div>';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/admin/games/sgdb/search?q=${encodeURIComponent(query)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.data && data.data.length > 0) {
|
||||
let html = '<div class="list-group">';
|
||||
data.data.forEach(game => {
|
||||
html += `
|
||||
<a href="#" class="list-group-item list-group-item-action" data-game-id="${game.id}">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h6 class="mb-1">${game.name}</h6>
|
||||
<small>${game.release_date ? new Date(game.release_date * 1000).getFullYear() : 'N/A'}</small>
|
||||
</div>
|
||||
<p class="mb-1">${game.platforms ? game.platforms.join(', ') : 'Unknown Platform'}</p>
|
||||
</a>
|
||||
`;
|
||||
});
|
||||
html += '</div>';
|
||||
searchResults.innerHTML = html;
|
||||
|
||||
// Add click handlers for game selection
|
||||
document.querySelectorAll('#sgdb-search-results .list-group-item').forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const gameId = item.getAttribute('data-game-id');
|
||||
loadGameMedia(gameId);
|
||||
searchResults.innerHTML = '';
|
||||
mediaSelection.classList.remove('d-none');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
searchResults.innerHTML = '<div class="alert alert-info">No games found. Try a different search term.</div>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error searching SteamGridDB:', error);
|
||||
searchResults.innerHTML = '<div class="alert alert-danger">Error searching for games. Please try again later.</div>';
|
||||
} finally {
|
||||
searchBtn.disabled = false;
|
||||
searchBtn.innerHTML = '<i class="bi bi-search me-1"></i> Search';
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search on Enter key
|
||||
searchInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
searchBtn.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle back to search button
|
||||
if (backToSearch) {
|
||||
backToSearch.addEventListener('click', () => {
|
||||
mediaSelection.classList.add('d-none');
|
||||
searchResults.innerHTML = '';
|
||||
searchInput.value = '';
|
||||
searchInput.focus();
|
||||
});
|
||||
}
|
||||
|
||||
// Preview cover when URL changes
|
||||
document.getElementById('cover_url').addEventListener('input', function() {
|
||||
const preview = document.getElementById('coverPreview');
|
||||
const url = this.value.trim();
|
||||
|
||||
if (url) {
|
||||
preview.innerHTML = `<img src="${url}" alt="Cover Preview" class="img-fluid" style="max-height: 300px;">`;
|
||||
} else {
|
||||
preview.innerHTML = '<div class="text-muted">No cover available</div>';
|
||||
}
|
||||
});
|
||||
|
||||
// Handle form submission
|
||||
document.querySelector('form').addEventListener('submit', function(e) {
|
||||
const title = document.getElementById('title').value.trim();
|
||||
if (!title) {
|
||||
e.preventDefault();
|
||||
alert('Title is required');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user