mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
714 lines
39 KiB
Twig
714 lines
39 KiB
Twig
{% extends "layouts/app.twig" %}
|
|
|
|
{% block content %}
|
|
<div class="p-6">
|
|
<!-- Header -->
|
|
<div class="mb-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h1 class="text-3xl font-bold text-gray-900">Edit Actor</h1>
|
|
<p class="text-gray-600 mt-1">Update actor information and metadata</p>
|
|
</div>
|
|
<a href="{{ path_for('actors.show', {'id': actor.id}) }}" class="bg-gray-500 text-white px-4 py-2 rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2">
|
|
Cancel
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
{% if error %}
|
|
<div class="bg-red-50 border border-red-200 rounded-md p-4 mb-6">
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<h3 class="text-sm font-medium text-red-800">Error</h3>
|
|
<div class="mt-2 text-sm text-red-700">
|
|
{{ error }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Edit Form -->
|
|
<form method="POST" enctype="multipart/form-data" class="bg-white rounded-lg shadow-md border border-gray-200">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h2 class="text-lg font-medium text-gray-900">Basic Information</h2>
|
|
</div>
|
|
|
|
<div class="p-6 space-y-6">
|
|
<!-- Name -->
|
|
<div>
|
|
<label for="name" class="block text-sm font-medium text-gray-700">Name *</label>
|
|
<div class="flex space-x-2">
|
|
<input type="text" name="name" id="name" value="{{ actor.name }}" required
|
|
class="mt-1 block flex-1 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
<button type="button" id="fetch-api-data" class="mt-1 bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 text-sm">
|
|
Fetch from API
|
|
</button>
|
|
<button type="button" id="fetch-stash-data" class="mt-1 bg-purple-600 text-white px-4 py-2 rounded-md hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 text-sm">
|
|
Fetch from Stash
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stash Search Query (Hidden by default) -->
|
|
<div id="stash-config" class="hidden border border-purple-200 rounded-md p-4 bg-purple-50">
|
|
<h4 class="text-sm font-medium text-purple-800 mb-3">Stash Performer Search</h4>
|
|
<div>
|
|
<label for="stash_query" class="block text-sm font-medium text-purple-700">Search Query</label>
|
|
<input type="text" name="stash_query" id="stash_query"
|
|
placeholder="Performer name to search"
|
|
class="mt-1 block w-full border border-purple-300 rounded-md shadow-sm focus:ring-purple-500 focus:border-purple-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
<p class="mt-2 text-xs text-purple-600">Enter the performer name to search for in your configured Stash instance. Uses the same API configuration as your Stash sync.</p>
|
|
</div>
|
|
|
|
<!-- Current Image -->
|
|
{% if actor.thumbnail_path %}
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700">Current Image</label>
|
|
<div class="mt-1 flex items-center space-x-4">
|
|
<img src="{{ actor.thumbnail_path }}" alt="{{ actor.name }}" class="w-24 h-32 object-cover rounded border">
|
|
<div class="text-sm text-gray-500">
|
|
Current actor image. Upload a new image below to replace it.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Image Source Selection -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-3">Image Source</label>
|
|
<div class="space-y-3">
|
|
<div class="flex items-center">
|
|
<input type="radio" name="image_source" id="image_source_upload" value="upload"
|
|
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300"
|
|
checked>
|
|
<label for="image_source_upload" class="ml-2 block text-sm text-gray-900">
|
|
Upload from computer
|
|
</label>
|
|
</div>
|
|
<div class="flex items-center">
|
|
<input type="radio" name="image_source" id="image_source_url" value="url"
|
|
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300">
|
|
<label for="image_source_url" class="ml-2 block text-sm text-gray-900">
|
|
Download from URL
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Image Upload -->
|
|
<div id="upload_section">
|
|
<label for="thumbnail" class="block text-sm font-medium text-gray-700">Upload New Image</label>
|
|
<input type="file" name="thumbnail" id="thumbnail" accept="image/*"
|
|
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100">
|
|
<p class="mt-1 text-sm text-gray-500">Supported formats: JPEG, PNG, GIF, WebP. Maximum file size: 5MB.</p>
|
|
</div>
|
|
|
|
<!-- External URL -->
|
|
<div id="url_section" class="hidden">
|
|
<label for="thumbnail_url" class="block text-sm font-medium text-gray-700">Image URL</label>
|
|
<input type="url" name="thumbnail_url" id="thumbnail_url"
|
|
placeholder="https://example.com/image.jpg"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
<p class="mt-1 text-sm text-gray-500">Enter a direct link to an image. The image will be downloaded and stored locally.</p>
|
|
|
|
<!-- Image Preview -->
|
|
<div id="image_preview" class="mt-4 hidden">
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Preview</label>
|
|
<div class="border border-gray-300 rounded-md p-4 bg-gray-50">
|
|
<div id="preview_loading" class="hidden flex items-center justify-center py-8">
|
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
|
<span class="ml-2 text-gray-600">Loading preview...</span>
|
|
</div>
|
|
<div id="preview_error" class="hidden text-center py-8">
|
|
<svg class="mx-auto h-12 w-12 text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
|
</svg>
|
|
<p class="mt-2 text-sm text-red-600" id="preview_error_text">Unable to load image preview</p>
|
|
</div>
|
|
<img id="preview_image" class="max-w-full h-auto max-h-64 mx-auto rounded shadow-sm hidden" alt="Image preview">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Biography -->
|
|
<div>
|
|
<label for="biography" class="block text-sm font-medium text-gray-700">Biography</label>
|
|
<textarea name="biography" id="biography" rows="4"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2"
|
|
placeholder="Enter actor biography...">{{ metadata.biography ?? '' }}</textarea>
|
|
</div>
|
|
|
|
<!-- Personal Information -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<div>
|
|
<label for="birth_date" class="block text-sm font-medium text-gray-700">Birth Date</label>
|
|
<input type="date" name="birth_date" id="birth_date" value="{{ metadata.birth_date ?? '' }}"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
<div>
|
|
<label for="death_date" class="block text-sm font-medium text-gray-700">Death Date</label>
|
|
<input type="date" name="death_date" id="death_date" value="{{ metadata.death_date ?? '' }}"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
<div>
|
|
<label for="gender" class="block text-sm font-medium text-gray-700">Gender</label>
|
|
<select name="gender" id="gender"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
<option value="">Select Gender</option>
|
|
<option value="FEMALE" {{ (metadata.gender ?? '') == 'FEMALE' ? 'selected' : '' }}>Female</option>
|
|
<option value="MALE" {{ (metadata.gender ?? '') == 'MALE' ? 'selected' : '' }}>Male</option>
|
|
<option value="TRANSGENDER_FEMALE" {{ (metadata.gender ?? '') == 'TRANSGENDER_FEMALE' ? 'selected' : '' }}>Transgender Female</option>
|
|
<option value="TRANSGENDER_MALE" {{ (metadata.gender ?? '') == 'TRANSGENDER_MALE' ? 'selected' : '' }}>Transgender Male</option>
|
|
<option value="INTERSEX" {{ (metadata.gender ?? '') == 'INTERSEX' ? 'selected' : '' }}>Intersex</option>
|
|
<option value="NON_BINARY" {{ (metadata.gender ?? '') == 'NON_BINARY' ? 'selected' : '' }}>Non-binary</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="birth_place" class="block text-sm font-medium text-gray-700">Birth Place</label>
|
|
<input type="text" name="birth_place" id="birth_place" value="{{ metadata.birth_place ?? '' }}"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
<div>
|
|
<label for="nationality" class="block text-sm font-medium text-gray-700">Nationality</label>
|
|
<input type="text" name="nationality" id="nationality" value="{{ metadata.nationality ?? '' }}"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
<div>
|
|
<label for="ethnicity" class="block text-sm font-medium text-gray-700">Ethnicity</label>
|
|
<input type="text" name="ethnicity" id="ethnicity" value="{{ metadata.ethnicity ?? '' }}"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Physical Attributes -->
|
|
<div class="border-t border-gray-200 pt-6">
|
|
<h3 class="text-lg font-medium text-gray-900 mb-4">Physical Attributes</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<div>
|
|
<label for="height" class="block text-sm font-medium text-gray-700">Height</label>
|
|
<input type="text" name="height" id="height" value="{{ metadata.height ?? '' }}"
|
|
placeholder="e.g., 5'6" or 168cm"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
<div>
|
|
<label for="measurements" class="block text-sm font-medium text-gray-700">Measurements</label>
|
|
<input type="text" name="measurements" id="measurements" value="{{ metadata.measurements ?? '' }}"
|
|
placeholder="e.g., 34-24-34"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
<div>
|
|
<label for="cup_size" class="block text-sm font-medium text-gray-700">Cup Size</label>
|
|
<input type="text" name="cup_size" id="cup_size" value="{{ metadata.cup_size ?? '' }}"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
<div>
|
|
<label for="ethnicity" class="block text-sm font-medium text-gray-700">Ethnicity</label>
|
|
<input type="text" name="ethnicity" id="ethnicity" value="{{ metadata.ethnicity ?? '' }}"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
<div>
|
|
<label for="hair_color" class="block text-sm font-medium text-gray-700">Hair Color</label>
|
|
<input type="text" name="hair_color" id="hair_color" value="{{ metadata.hair_color ?? '' }}"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
<div>
|
|
<label for="eye_color" class="block text-sm font-medium text-gray-700">Eye Color</label>
|
|
<input type="text" name="eye_color" id="eye_color" value="{{ metadata.eye_color ?? '' }}"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Body Modifications -->
|
|
<div class="border-t border-gray-200 pt-6">
|
|
<h3 class="text-lg font-medium text-gray-900 mb-4">Body Modifications</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<label for="piercings" class="block text-sm font-medium text-gray-700">Piercings</label>
|
|
<input type="text" name="piercings" id="piercings" value="{{ metadata.piercings ?? '' }}"
|
|
placeholder="e.g., navel, nipples"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
<div>
|
|
<label for="tattoos" class="block text-sm font-medium text-gray-700">Tattoos</label>
|
|
<input type="text" name="tattoos" id="tattoos" value="{{ metadata.tattoos ?? '' }}"
|
|
placeholder="e.g., lower back, ankle"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Aliases -->
|
|
<div class="border-t border-gray-200 pt-6">
|
|
<div>
|
|
<label for="aliases" class="block text-sm font-medium text-gray-700">Aliases</label>
|
|
<input type="text" name="aliases" id="aliases" value="{{ (metadata.aliases ?? [])|join(', ') }}"
|
|
placeholder="e.g., Stage Name, Other Names (comma-separated)"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
<p class="mt-1 text-sm text-gray-500">Separate multiple aliases with commas</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Social Media -->
|
|
<div class="border-t border-gray-200 pt-6">
|
|
<h3 class="text-lg font-medium text-gray-900 mb-4">Social Media</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<label for="twitter" class="block text-sm font-medium text-gray-700">Twitter</label>
|
|
<input type="url" name="twitter" id="twitter" value="{{ metadata.social_media.twitter ?? '' }}"
|
|
placeholder="https://twitter.com/username"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
<div>
|
|
<label for="instagram" class="block text-sm font-medium text-gray-700">Instagram</label>
|
|
<input type="url" name="instagram" id="instagram" value="{{ metadata.social_media.instagram ?? '' }}"
|
|
placeholder="https://instagram.com/username"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
<div>
|
|
<label for="onlyfans" class="block text-sm font-medium text-gray-700">OnlyFans</label>
|
|
<input type="url" name="onlyfans" id="onlyfans" value="{{ metadata.social_media.onlyfans ?? '' }}"
|
|
placeholder="https://onlyfans.com/username"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
<div>
|
|
<label for="website" class="block text-sm font-medium text-gray-700">Website</label>
|
|
<input type="url" name="website" id="website" value="{{ metadata.social_media.website ?? '' }}"
|
|
placeholder="https://example.com"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Adult-Specific Information -->
|
|
<div class="border-t border-gray-200 pt-6">
|
|
<h3 class="text-lg font-medium text-gray-900 mb-4">Adult Industry Information</h3>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
|
<div>
|
|
<label for="debut_year" class="block text-sm font-medium text-gray-700">Debut Year</label>
|
|
<input type="number" name="debut_year" id="debut_year" value="{{ metadata.adult_specific.debut_year ?? '' }}"
|
|
min="1900" max="{{ 'now'|date('Y') + 5 }}"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
<div>
|
|
<label for="retirement_year" class="block text-sm font-medium text-gray-700">Retirement Year</label>
|
|
<input type="number" name="retirement_year" id="retirement_year" value="{{ metadata.adult_specific.retirement_year ?? '' }}"
|
|
min="1900" max="{{ 'now'|date('Y') + 10 }}"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-6">
|
|
<div class="flex items-center">
|
|
<input type="checkbox" name="active" id="active" value="1" {{ (metadata.adult_specific.active ?? false) ? 'checked' : '' }}
|
|
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
|
<label for="active" class="ml-2 block text-sm text-gray-900">
|
|
Currently Active in Adult Industry
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<label for="adult_genres" class="block text-sm font-medium text-gray-700">Adult Genres</label>
|
|
<input type="text" name="adult_genres" id="adult_genres" value="{{ (metadata.adult_specific.genres ?? [])|join(', ') }}"
|
|
placeholder="e.g., MILF, Anal, POV (comma-separated)"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
<p class="mt-1 text-sm text-gray-500">Genres this performer specializes in</p>
|
|
</div>
|
|
<div>
|
|
<label for="specialties" class="block text-sm font-medium text-gray-700">Specialties</label>
|
|
<input type="text" name="specialties" id="specialties" value="{{ (metadata.adult_specific.specialties ?? [])|join(', ') }}"
|
|
placeholder="e.g., Deep Throat, Squirt, Roleplay (comma-separated)"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm px-3 py-2">
|
|
<p class="mt-1 text-sm text-gray-500">Specific skills or specialties</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form Actions -->
|
|
<div class="px-6 py-4 bg-gray-50 border-t border-gray-200 flex justify-end space-x-3">
|
|
<a href="{{ path_for('actors.show', {'id': actor.id}) }}" class="bg-gray-500 text-white px-4 py-2 rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2">
|
|
Cancel
|
|
</a>
|
|
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
|
|
Save Changes
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
// Toggle image source sections
|
|
document.querySelectorAll('input[name="image_source"]').forEach(radio => {
|
|
radio.addEventListener('change', function() {
|
|
const uploadSection = document.getElementById('upload_section');
|
|
const urlSection = document.getElementById('url_section');
|
|
const imagePreview = document.getElementById('image_preview');
|
|
|
|
if (this.value === 'upload') {
|
|
uploadSection.classList.remove('hidden');
|
|
urlSection.classList.add('hidden');
|
|
imagePreview.classList.add('hidden');
|
|
} else if (this.value === 'url') {
|
|
uploadSection.classList.add('hidden');
|
|
urlSection.classList.remove('hidden');
|
|
// Check if URL field has a value and show preview if it does
|
|
const urlInput = document.getElementById('thumbnail_url');
|
|
if (urlInput.value.trim()) {
|
|
updateImagePreview(urlInput.value.trim());
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Image URL preview functionality
|
|
function updateImagePreview(url) {
|
|
const previewContainer = document.getElementById('image_preview');
|
|
const loadingDiv = document.getElementById('preview_loading');
|
|
const errorDiv = document.getElementById('preview_error');
|
|
const imageElement = document.getElementById('preview_image');
|
|
|
|
// Show preview container and loading state
|
|
previewContainer.classList.remove('hidden');
|
|
loadingDiv.classList.remove('hidden');
|
|
errorDiv.classList.add('hidden');
|
|
imageElement.classList.add('hidden');
|
|
|
|
// Create a new image to test loading
|
|
const testImage = new Image();
|
|
|
|
testImage.onload = function() {
|
|
// Image loaded successfully
|
|
loadingDiv.classList.add('hidden');
|
|
errorDiv.classList.add('hidden');
|
|
imageElement.src = url;
|
|
imageElement.classList.remove('hidden');
|
|
};
|
|
|
|
testImage.onerror = function() {
|
|
// Image failed to load
|
|
loadingDiv.classList.add('hidden');
|
|
errorDiv.classList.remove('hidden');
|
|
imageElement.classList.add('hidden');
|
|
document.getElementById('preview_error_text').textContent = 'Unable to load image. Please check the URL.';
|
|
};
|
|
|
|
// Set a timeout for slow-loading images
|
|
setTimeout(() => {
|
|
if (loadingDiv.classList.contains('hidden') === false) {
|
|
loadingDiv.classList.add('hidden');
|
|
errorDiv.classList.remove('hidden');
|
|
document.getElementById('preview_error_text').textContent = 'Image loading timed out. Please try again.';
|
|
}
|
|
}, 10000); // 10 second timeout
|
|
|
|
testImage.src = url;
|
|
}
|
|
|
|
// Add event listener to URL input
|
|
document.getElementById('thumbnail_url').addEventListener('input', function() {
|
|
const url = this.value.trim();
|
|
|
|
if (url) {
|
|
// Debounce the preview update
|
|
clearTimeout(this.previewTimeout);
|
|
this.previewTimeout = setTimeout(() => {
|
|
updateImagePreview(url);
|
|
}, 500); // Wait 500ms after user stops typing
|
|
} else {
|
|
// Hide preview if URL is empty
|
|
document.getElementById('image_preview').classList.add('hidden');
|
|
}
|
|
});
|
|
|
|
// Clear preview timeout on form submission
|
|
document.querySelector('form').addEventListener('submit', function() {
|
|
const urlInput = document.getElementById('thumbnail_url');
|
|
if (urlInput.previewTimeout) {
|
|
clearTimeout(urlInput.previewTimeout);
|
|
}
|
|
});
|
|
|
|
document.getElementById('fetch-api-data').addEventListener('click', async function() {
|
|
const nameInput = document.getElementById('name');
|
|
const actorName = nameInput.value.trim();
|
|
|
|
if (!actorName) {
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
const button = this;
|
|
const originalText = button.textContent;
|
|
button.textContent = 'Fetching...';
|
|
button.disabled = true;
|
|
|
|
try {
|
|
// Format name for API (firstname%20lastname)
|
|
const formattedName = actorName.replace(/\s+/g, '%20');
|
|
const apiUrl = `https://api.adultdatalink.com/pornstar/pornstar-data?name=${formattedName}`;
|
|
|
|
const response = await fetch(apiUrl);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`API request failed: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
// Map API data to form fields
|
|
if (data) {
|
|
console.log(data);
|
|
console.log(data.appearance.ethnicity);
|
|
|
|
|
|
console.log(data.date_of_birth)
|
|
date = new Date(Date.parse(data.date_of_birth));
|
|
console.log(date);
|
|
date = date.toLocaleDateString(undefined, {year:'numeric'}) + '-' + date.toLocaleDateString(undefined, {month:'2-digit'}) + '-' + date.toLocaleDateString(undefined, {day:'2-digit'})
|
|
|
|
console.log(date);
|
|
// Basic info
|
|
if (data.avatar){
|
|
document.getElementById('image_source_url').value = 'url';
|
|
document.getElementById('thumbnail_url').value = data.avatar;
|
|
|
|
}
|
|
if (data.biography) document.getElementById('biography').value = data.biography;
|
|
if (data.birth_date) document.getElementById('birth_date').value = date;
|
|
if (data.death_date) document.getElementById('death_date').value = data.death_date;
|
|
if (data.place_of_birth) document.getElementById('birth_place').value = data.place_of_birth;
|
|
if (data.country) document.getElementById('nationality').value = data.country;
|
|
if (data.appearance.ethnicity) document.getElementById('ethnicity').value = data.appearance.ethnicity;
|
|
|
|
// Gender mapping
|
|
if (data.gender) {
|
|
const genderSelect = document.getElementById('gender');
|
|
const genderMap = {
|
|
'woman': 'FEMALE',
|
|
'male': 'MALE',
|
|
'transgender_female': 'TRANSGENDER_FEMALE',
|
|
'transgender_male': 'TRANSGENDER_MALE',
|
|
'intersex': 'INTERSEX',
|
|
'non_binary': 'NON_BINARY'
|
|
};
|
|
const mappedGender = genderMap[data.gender.toLowerCase()] || data.gender.toUpperCase();
|
|
genderSelect.value = mappedGender;
|
|
}
|
|
|
|
// Physical attributes
|
|
if (data.appearance.height) document.getElementById('height').value = data.appearance.height;
|
|
document.getElementById('measurements').value = parseInt(data.appearance.bra)+'-'+ parseInt(data.appearance.waist)+'-'+parseInt(data.appearance.hip);
|
|
if (data.appearance.cup) document.getElementById('cup_size').value = data.appearance.cup;
|
|
if (data.appearance.hair_color) document.getElementById('hair_color').value = data.appearance.hair_color;
|
|
if (data.appearance.eye_color) document.getElementById('eye_color').value = data.appearance.eye_color;
|
|
|
|
// Body modifications
|
|
if (data.appearance.piercing_locations) document.getElementById('piercings').value = data.appearance.piercing_locations;
|
|
if (data.appearance.tattoo_locations) document.getElementById('tattoos').value = data.appearance.tattoo_locations;
|
|
|
|
// Aliases
|
|
if (data.aliases) document.getElementById('aliases').value = data.aliases;
|
|
if (data.aliases && Array.isArray(data.aliases)) {
|
|
document.getElementById('aliases').value = data.aliases.join(', ');
|
|
}
|
|
|
|
|
|
// Social media
|
|
if (data.social_media) {
|
|
if (data.social_media.twitter) document.getElementById('twitter').value = data.social_media.twitter;
|
|
if (data.social_media.instagram) document.getElementById('instagram').value = data.social_media.instagram;
|
|
if (data.social_media.onlyfans) document.getElementById('onlyfans').value = data.social_media.onlyfans;
|
|
if (data.social_media.website) document.getElementById('website').value = data.social_media.website;
|
|
}
|
|
|
|
if (data.career_start) document.getElementById('debut_year').value = data.career_start;
|
|
if (data.career_end) document.getElementById('retirement_year').value = data.career_end;
|
|
if (data.career_status !== undefined) document.getElementById('active').checked = data.active;
|
|
if (data.genres && Array.isArray(data.adult_specific.genres)) {
|
|
document.getElementById('adult_genres').value = data.adult_specific.genres.join(', ');
|
|
}
|
|
|
|
if (data.tags && Array.isArray(data.tags)) {
|
|
document.getElementById('specialties').value = data.tags.join(', ');
|
|
}
|
|
|
|
} else {
|
|
alert('No data found for this performer');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error fetching data:', error);
|
|
alert('Error fetching data from API: ' + error.message);
|
|
} finally {
|
|
// Reset button state
|
|
button.textContent = originalText;
|
|
button.disabled = false;
|
|
}
|
|
});
|
|
|
|
// Stash API fetch functionality
|
|
document.getElementById('fetch-stash-data').addEventListener('click', async function() {
|
|
const nameInput = document.getElementById('name');
|
|
const actorName = nameInput.value.trim();
|
|
|
|
if (!actorName) {
|
|
alert('Please enter a name first');
|
|
return;
|
|
}
|
|
|
|
// Show Stash config section
|
|
const stashConfig = document.getElementById('stash-config');
|
|
stashConfig.classList.remove('hidden');
|
|
|
|
// Pre-fill query with actor name
|
|
const queryInput = document.getElementById('stash_query');
|
|
if (!queryInput.value.trim()) {
|
|
queryInput.value = actorName;
|
|
}
|
|
|
|
// Show loading state
|
|
const button = this;
|
|
const originalText = button.textContent;
|
|
button.textContent = 'Fetching from Stash...';
|
|
button.disabled = true;
|
|
|
|
try {
|
|
const stashQuery = document.getElementById('stash_query').value.trim();
|
|
|
|
if (!stashQuery) {
|
|
alert('Please enter a search query');
|
|
return;
|
|
}
|
|
|
|
// Make API call to backend endpoint
|
|
const response = await fetch('/api/actors/fetch-stash', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
query: stashQuery
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.error || `API request failed: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (data && data.performers && data.performers.length > 0) {
|
|
const performer = data.performers[0]; // Use first result
|
|
const stashUrl = data.stash_url; // Get Stash URL from response
|
|
|
|
// Map Stash performer data to form fields
|
|
if (performer.image_path) {
|
|
// Set image source to URL and populate the URL field
|
|
document.getElementById('image_source_url').checked = true;
|
|
document.getElementById('upload_section').classList.add('hidden');
|
|
document.getElementById('url_section').classList.remove('hidden');
|
|
|
|
// Construct full image URL
|
|
let imageUrl = performer.image_path;
|
|
if (imageUrl.startsWith('/')) {
|
|
imageUrl = stashUrl + imageUrl;
|
|
} else if (!imageUrl.startsWith('http')) {
|
|
imageUrl = `${stashUrl}/performer/${performer.id}/${imageUrl}`;
|
|
}
|
|
document.getElementById('thumbnail_url').value = imageUrl;
|
|
}
|
|
|
|
if (performer.details) document.getElementById('biography').value = performer.details;
|
|
if (performer.birthdate) document.getElementById('birth_date').value = performer.birthdate;
|
|
if (performer.death_date) document.getElementById('death_date').value = performer.death_date;
|
|
if (performer.country) document.getElementById('nationality').value = performer.country;
|
|
if (performer.ethnicity) document.getElementById('ethnicity').value = performer.ethnicity;
|
|
|
|
// Gender mapping
|
|
if (performer.gender) {
|
|
const genderSelect = document.getElementById('gender');
|
|
genderSelect.value = performer.gender.toUpperCase();
|
|
}
|
|
|
|
// Physical attributes
|
|
if (performer.height_cm) document.getElementById('height').value = performer.height_cm + 'cm';
|
|
if (performer.measurements) document.getElementById('measurements').value = performer.measurements;
|
|
if (performer.hair_color) document.getElementById('hair_color').value = performer.hair_color;
|
|
if (performer.eye_color) document.getElementById('eye_color').value = performer.eye_color;
|
|
if (performer.weight) document.getElementById('weight').value = performer.weight + 'kg';
|
|
|
|
// Body modifications
|
|
if (performer.piercings) document.getElementById('piercings').value = performer.piercings;
|
|
if (performer.tattoos) document.getElementById('tattoos').value = performer.tattoos;
|
|
|
|
// Aliases
|
|
if (performer.alias_list && Array.isArray(performer.alias_list)) {
|
|
document.getElementById('aliases').value = performer.alias_list.join(', ');
|
|
}
|
|
|
|
// Social media
|
|
if (performer.url) document.getElementById('website').value = performer.url;
|
|
|
|
// Adult-specific information
|
|
if (performer.career_length) {
|
|
const debutYear = extractDebutYear(performer.career_length);
|
|
const retirementYear = extractRetirementYear(performer.career_length);
|
|
if (debutYear) document.getElementById('debut_year').value = debutYear;
|
|
if (retirementYear) document.getElementById('retirement_year').value = retirementYear;
|
|
document.getElementById('active').checked = isActivePerformer(performer.career_length);
|
|
}
|
|
|
|
//if (performer.scene_count) document.getElementById('scene_count').value = performer.scene_count;
|
|
|
|
// Additional Stash-specific fields
|
|
//if (performer.disambiguation) document.getElementById('birth_place').value = performer.disambiguation;
|
|
//if (performer.fake_tits !== undefined) document.getElementById('fake_tits').value = performer.fake_tits ? 'Yes' : 'No';
|
|
//if (performer.penis_length) document.getElementById('penis_length').value = performer.penis_length;
|
|
//if (performer.circumcised !== undefined) document.getElementById('circumcised').value = performer.circumcised ? 'Yes' : 'No';
|
|
|
|
} else {
|
|
alert('No performers found matching the query');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error fetching data from Stash:', error);
|
|
alert('Error fetching data from Stash: ' + error.message);
|
|
} finally {
|
|
// Reset button state
|
|
button.textContent = originalText;
|
|
button.disabled = false;
|
|
}
|
|
});
|
|
|
|
// Helper functions for parsing Stash data
|
|
function extractDebutYear(careerLength) {
|
|
if (!careerLength) return null;
|
|
const match = careerLength.match(/(\d{4})\s*-\s*\d{4}/);
|
|
return match ? match[1] : null;
|
|
}
|
|
|
|
function extractRetirementYear(careerLength) {
|
|
if (!careerLength) return null;
|
|
const match = careerLength.match(/\d{4}\s*-\s*(\d{4})/);
|
|
return match ? match[1] : null;
|
|
}
|
|
|
|
function isActivePerformer(careerLength) {
|
|
if (!careerLength) return false;
|
|
return careerLength.trim().endsWith('-');
|
|
}
|
|
</script>
|
|
{% endblock %}
|