Files
NoN-Site/nonpacks/templates/library/project_detail.html
T
2026-08-04 00:34:02 -05:00

338 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends 'base.html' %}
{% load static %}
{% load markdown %}
{% block title %}{{ project.title }} - Packs Site{% endblock %}
{% block content %}
<div class="project-header card">
<div class="project-avatar">
{% if project.thumbnail %}
<img src="{{ project.thumbnail_url }}" alt="{{ project.title }}">
{% else %}
<div class="default-avatar large">{{ project.title|first|upper }}</div>
{% endif %}
</div>
<div class="project-header-main">
<div class="project-title-row">
<h1>{{ project.title }}</h1>
<span class="category-badge">{{ project.get_category_display }}</span>
<span class="project-id-badge" title="Project ID">#{{ project.pk }}</span>
</div>
{% if project.summary %}<p class="project-summary">{{ project.summary }}</p>{% endif %}
<div class="project-meta">
<span class="project-owner"><i class="fas fa-user"></i>
<a href="{% url 'profiles:user_profile' project.owner.username %}">{{ project.owner.username }}</a>
</span>
<span class="project-stat"><i class="fas fa-download"></i> {{ project.downloads }}</span>
<span class="project-stat"><i class="fas fa-tag"></i> {{ versions|length }} version{{ versions|length|pluralize }}</span>
</div>
{% if can_edit %}
<div class="project-actions">
<a href="{% url 'library:version_upload' project.slug %}" class="btn btn-primary"><i class="fas fa-upload"></i> New version</a>
<a href="{% url 'library:asset_upload' project.slug %}" class="btn btn-secondary"><i class="fas fa-images"></i> Add media</a>
<a href="{% url 'library:project_edit' project.slug %}" class="btn btn-secondary"><i class="fas fa-pen"></i> Edit</a>
{% if request.user == project.owner %}
<a href="{% url 'library:contributors' project.slug %}" class="btn btn-secondary"><i class="fas fa-users"></i> Contributors</a>
<a href="{% url 'library:project_delete' project.slug %}" class="btn btn-danger"><i class="fas fa-trash"></i></a>
{% endif %}
</div>
{% endif %}
</div>
</div>
{% if tags %}
<div class="project-tags card">
{% regroup tags by tag.category as tag_groups %}
{% for group in tag_groups %}
<div class="tag-group">
<span class="tag-group-label">
<span class="tag-dot" style="background: {{ group.grouper.color }};"></span>
{{ group.grouper.name }}
</span>
{% for link in group.list %}
<a href="{% url 'library:browse' %}?tag={{ link.tag.name }}" class="tag-chip"
style="border-color: {{ group.grouper.color }}; color: {{ group.grouper.color }};">{{ link.tag.name }}</a>
{% endfor %}
</div>
{% endfor %}
</div>
{% endif %}
<div class="tab-bar" role="tablist">
<button class="tab-btn active" data-tab="{% if project.category == 'guide' %}guide{% else %}description{% endif %}" role="tab">
{% if project.category == 'guide' %}Guide{% else %}Description{% endif %}
</button>
<button class="tab-btn" data-tab="versions" role="tab">Versions</button>
{% if animation_manifest %}<button class="tab-btn" data-tab="animations" role="tab">Animations</button>{% endif %}
{% if project.category == 'modpack' %}<button class="tab-btn" data-tab="mods" role="tab">Mods</button>{% endif %}
<button class="tab-btn" data-tab="gallery" role="tab">Gallery</button>
</div>
<section class="tab-panel active" id="tab-{% if project.category == 'guide' %}guide{% else %}description{% endif %}">
<div class="card">
{% if project.category == 'guide' %}
<div class="guide-docs">
{% if guide_docs %}
<div class="guide-switcher" role="tablist">
{% for doc in guide_docs %}
<button type="button" class="guide-switch-btn{% if forloop.first %} active{% endif %}"
data-url="{% url 'library:guide_doc' project.slug doc.version.pk doc.file.uuid %}">{{ doc.filename }}</button>
{% endfor %}
</div>
<div class="markdown-body guide-content" id="guide-content">
<p class="empty-hint">Loading…</p>
</div>
{% else %}
<p class="empty-hint">No guide documents uploaded yet.</p>
{% endif %}
</div>
{% elif project.description %}
<div class="markdown-body project-description">{{ project.description|markdown }}</div>
{% else %}
<p class="empty-hint">No description yet.</p>
{% endif %}
</div>
</section>
<section class="tab-panel" id="tab-versions">
<div class="card">
<h2><i class="fas fa-history"></i> Versions</h2>
{% for version in versions %}
<div class="version-row">
<div class="version-info">
<div class="version-name-row">
<strong class="version-name">{{ version.version_name }}</strong>
{% if forloop.first %}<span class="latest-badge">latest</span>{% endif %}
{% if version.pack_format %}<span class="pack-format-badge">pack format {{ version.pack_format }}</span>{% endif %}
</div>
<span class="version-date"><i class="fas fa-clock"></i> {{ version.created_at|date:"M j, Y" }}</span>
<span class="version-downloads"><i class="fas fa-download"></i> {{ version.downloads }}</span>
</div>
{% if version.pack_description %}
<p class="pack-description"><i class="fas fa-info-circle"></i> {{ version.pack_description }}</p>
{% endif %}
{% if version.changelog %}
<div class="markdown-body version-changelog">{{ version.changelog|markdown }}</div>
{% endif %}
<div class="version-files">
{% for vf in version.files.all %}
<div class="version-file">
{% if vf.is_png %}
<img class="version-file-thumb" src="{% url 'library:file_request' vf.file.uuid %}" alt="{{ vf.filename }}">
{% elif vf.is_markdown %}
<i class="fas fa-file-lines version-file-icon"></i>
{% else %}
<i class="fas fa-file-archive version-file-icon"></i>
{% endif %}
<span class="version-file-name">{{ vf.filename }}</span>
<a class="btn btn-primary btn-sm" href="{% url 'library:version_file_download' project.slug version.pk vf.file.uuid %}" title="Download {{ vf.filename }}">
<i class="fas fa-download"></i>
</a>
</div>
{% endfor %}
</div>
</div>
{% empty %}
<p class="empty-hint">No versions yet.</p>
{% endfor %}
</div>
</section>
{% if animation_manifest %}
<section class="tab-panel" id="tab-animations">
<div class="card">
<div class="animations-header">
<h2><i class="fas fa-film"></i> Animations</h2>
{% if animation_manifest.name %}
<p class="anim-pack-name">{{ animation_manifest.name }}
{% if animation_manifest.version %}<span class="pack-format-badge">v{{ animation_manifest.version }}</span>{% endif %}
</p>
{% endif %}
<p class="anim-authors">
<i class="fas fa-user-pen"></i> by
{% for author in animation_manifest.authors %}{{ author }}{% if not forloop.last %}, {% endif %}{% empty %}{{ project.owner.username }}{% endfor %}
</p>
{% if animation_manifest.description %}<p class="anim-description">{{ animation_manifest.description }}</p>{% endif %}
{% if not animation_manifest.animation_id %}
<p class="anim-warning"><i class="fas fa-exclamation-triangle"></i> This pack has no <code>animationframework.id</code> — it can't be reached through the update-check API.</p>
{% endif %}
</div>
{% if anim_stats %}
<div class="animations-summary">
<span><strong>{{ anim_stats.total }}</strong> animations</span>
<span><strong>{{ anim_stats.player }}</strong> Player × Player</span>
<span><strong>{{ anim_stats.entity }}</strong> Entity × Player</span>
<span><strong>{{ anim_stats.solo }}</strong> solo</span>
{% if anim_stats.flagged %}<span class="anim-summary-flagged"><strong>{{ anim_stats.flagged }}</strong> flagged ⚠</span>{% endif %}
<span><strong>{{ anim_stats.tags|length }}</strong> tags</span>
</div>
{% endif %}
<div class="anim-grid">
{% for anim in animation_manifest.animations %}
<div class="anim-card">
<div class="anim-card-head">
<strong class="anim-name">{{ anim.name }}</strong>
<span class="anim-type-badge">{{ anim.type }}</span>
{% if anim.problem_tags %}
<span class="anim-problem" title="{{ anim.problem_tags|join:', ' }}"><i class="fas fa-exclamation-triangle"></i></span>
{% endif %}
</div>
<div class="anim-tags">
{% for tag in anim.content_tags %}
<span class="tag-chip" style="border-color: var(--md-sys-color-content, #d62828); color: var(--md-sys-color-content, #d62828);">{{ tag }}</span>
{% endfor %}
{% for tag in anim.animation_tags %}
<span class="tag-chip">{{ tag }}</span>
{% endfor %}
</div>
<div class="anim-actors">
{% for actor in anim.actors %}
<span class="actor-chip">
{% if actor.entity == 'player' %}
<i class="fas fa-user"></i> Player
{% else %}
<i class="fas fa-paw"></i> {{ actor.entity }}
{% endif %}
{% if actor.gender %}{% if actor.gender == 'male' %} ♂{% elif actor.gender == 'female' %} ♀{% endif %}{% endif %}
{% if actor.activity %}<small>· {{ actor.activity }}</small>{% endif %}
{% if actor.injector %}
<span class="injector-badge"{% if actor.injector != 'injector' %} title="Injector: {{ actor.injector_name }}"{% endif %}>
{% if actor.injector == 'injector' %}injector{% else %}{{ actor.injector_name }} ({{ actor.injector }}){% endif %}
</span>
{% endif %}
{% if actor.receiver %}<small>· receiver</small>{% endif %}
</span>
{% endfor %}
</div>
<div class="anim-stages">
<span class="stages-count">{{ anim.stages|length }} stage{{ anim.stages|length|pluralize }}</span>
{% for s in anim.stages %}
<span class="stage-chip"{% if s.climax %} title="climax stage"{% endif %}>
{% if s.climax %}<i class="fas fa-star"></i>{% endif %}{{ s.stage }}{% if s.loop %}↻{% endif %}{% if s.cycle_seconds %} {{ s.cycle_seconds }}s{% endif %}
</span>
{% endfor %}
</div>
{% if anim.weight or anim.block_requirements or anim.water %}
<div class="anim-extras">
{% if anim.weight %}<span class="anim-extra">weight {{ anim.weight }}</span>{% endif %}
{% if anim.block_requirements %}<span class="anim-extra"><i class="fas fa-cubes"></i> requires block support</span>{% endif %}
{% if anim.water %}<span class="anim-extra"><i class="fas fa-water"></i> water</span>{% endif %}
</div>
{% endif %}
</div>
{% endfor %}
</div>
</div>
</section>
{% endif %}
{% if project.category == 'modpack' %}
<section class="tab-panel" id="tab-mods">
<div class="card">
<h2><i class="fas fa-cubes"></i> Mods</h2>
{% for version in versions %}
{% if version.mods_manifest.files %}
<div class="mods-version">
<h3>{{ version.version_name }} <span class="mods-source-badge">{{ version.mods_manifest.source }}</span></h3>
<ul class="mods-list">
{% for mod in version.mods_manifest.files %}
<li><i class="fas fa-cube"></i> {{ mod.name }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% empty %}
<p class="empty-hint">No mods detected in the uploaded packs.</p>
{% endfor %}
</div>
</section>
{% endif %}
<section class="tab-panel" id="tab-gallery">
<div class="card">
<h2><i class="fas fa-images"></i> Gallery</h2>
<div class="gallery-grid">
{% for asset in assets %}
<figure class="gallery-item">
{% if asset.is_video %}
<video controls preload="metadata" src="{{ asset.file_url }}"></video>
{% else %}
<a href="{{ asset.file_url }}" target="_blank"><img src="{{ asset.file_url }}" alt="{{ asset.caption }}"></a>
{% endif %}
{% if asset.caption %}<figcaption>{{ asset.caption }}</figcaption>{% endif %}
{% if can_edit %}
<a href="{% url 'library:asset_delete' project.slug asset.pk %}" class="gallery-delete" title="Delete media"><i class="fas fa-trash"></i></a>
{% endif %}
</figure>
{% empty %}
<p class="empty-hint">No media yet.</p>
{% endfor %}
</div>
</div>
</section>
{% endblock %}
{% block extra_js %}
<script>
(function () {
const buttons = document.querySelectorAll('.tab-btn');
buttons.forEach(btn => {
btn.addEventListener('click', () => {
buttons.forEach(b => b.classList.remove('active'));
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
btn.classList.add('active');
document.getElementById('tab-' + btn.dataset.tab).classList.add('active');
});
});
// Gallery deletion without a page reload, behind a confirmation dialog.
document.querySelectorAll('.gallery-delete').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
if (!confirm('Delete this media? This cannot be undone.')) return;
const xhr = new XMLHttpRequest();
xhr.open('POST', link.getAttribute('href'));
xhr.setRequestHeader('X-CSRFToken', getCookie('csrftoken'));
xhr.onload = function () {
if (xhr.status === 200) {
const figure = link.closest('figure.gallery-item');
if (figure) figure.remove();
} else {
alert('Could not delete media.');
}
};
xhr.onerror = function () {
alert('Network error while deleting media.');
};
xhr.send();
});
});
// Guide document file-switcher (fetches + renders the selected .md).
const guideContent = document.getElementById('guide-content');
if (guideContent) {
const guideBtns = document.querySelectorAll('.guide-switch-btn');
function loadGuide(url) {
guideContent.innerHTML = '<p class="empty-hint">Loading…</p>';
fetch(url, { headers: { 'Accept': 'text/html' } })
.then(r => { if (!r.ok) throw new Error('http'); return r.text(); })
.then(html => { guideContent.innerHTML = html; })
.catch(() => { guideContent.innerHTML = '<p class="empty-hint">Could not load this document.</p>'; });
}
guideBtns.forEach(btn => {
btn.addEventListener('click', () => {
guideBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
loadGuide(btn.dataset.url);
});
});
const first = guideBtns[0];
if (first) loadGuide(first.dataset.url);
}
})();
</script>
{% endblock %}