Files
NoN-Site/nonpacks/templates/library/project_detail.html
T

536 lines
26 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 models_manifest %}<button class="tab-btn" data-tab="models" role="tab">Models/Textures</button>{% endif %}
{% if logical_manifest %}<button class="tab-btn" data-tab="logical" role="tab">Logical</button>{% endif %}
{% if mods_manifest %}<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 models_manifest %}
<section class="tab-panel" id="tab-models">
<div class="card">
<h2><i class="fas fa-cube"></i> Models &amp; Textures</h2>
<div class="model-grid">
{% for model in models_manifest.models %}
<div class="model-card">
<div class="model-card-head">
<strong>{{ model.name }}</strong>
<button type="button" class="btn btn-secondary btn-sm model-render-btn"
data-member="{{ model.member }}" data-name="{{ model.name }}"
data-default-texture="{{ model.default_texture }}"
data-base-url="{% url 'library:pack_asset' project.slug versions.0.pk 'ASSET' %}"
data-vanilla-base-url="{% url 'library:vanilla_asset' 'ASSET' %}"><i class="fas fa-cube"></i> Render</button>
</div>
<p class="model-meta">{{ model.bones }} bones · {{ model.cubes }} cubes</p>
<div class="model-textures">
{% for member in model.textures %}
<img src="{% url 'library:pack_asset' project.slug versions.0.pk member %}" alt="{{ member }}" loading="lazy" title="{{ member }}">
{% empty %}
<span class="empty-hint">No textures</span>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
{% if models_manifest.textures %}
<h3 class="models-textures-title">Pack textures</h3>
<div class="model-textures strip">
{% for texture in models_manifest.textures %}
<img src="{% url 'library:pack_asset' project.slug versions.0.pk texture %}" alt="{{ texture }}" loading="lazy" title="{{ texture }}">
{% endfor %}
</div>
{% endif %}
</div>
</section>
{% endif %}
{% if logical_manifest %}
<section class="tab-panel" id="tab-logical">
<div class="card">
<h2><i class="fas fa-box"></i> What this pack adds</h2>
{% if logical_manifest.items %}
<h3>Items ({{ logical_manifest.items|length }})</h3>
<div class="logical-items">
{% for item in logical_manifest.items %}
<span class="logical-item">
{% if item.texture %}
<img src="{% url 'library:pack_asset' project.slug versions.0.pk item.texture %}" alt="" loading="lazy">
{% endif %}
{{ item.name }}
</span>
{% endfor %}
</div>
{% endif %}
{% if logical_manifest.recipes %}
<h3>Recipes ({{ logical_manifest.recipes|length }})</h3>
<ul class="logical-list">
{% for r in logical_manifest.recipes %}
<li>
{% if r.disabled %}<span class="badge-disabled">disabled</span>{% endif %}
<code>{{ r.ingredients|join:' + ' }}</code><code>{{ r.result }}</code>
{% if r.type %}<small>({{ r.type }})</small>{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
{% if logical_manifest.custom_recipes %}
<h3>Custom crafting</h3>
<ul class="logical-list">
{% for r in logical_manifest.custom_recipes %}
<li>{{ r.name }} → <code>{{ r.reward }}</code></li>
{% endfor %}
</ul>
{% endif %}
{% if logical_manifest.advancements %}
<h3>Advancements</h3>
<ul class="logical-list">
{% for a in logical_manifest.advancements %}
<li>{{ a.name }}{% if a.reward %} → <code>{{ a.reward }}</code>{% endif %}</li>
{% endfor %}
</ul>
{% endif %}
{% if logical_manifest.functions %}
<h3>Functions ({{ logical_manifest.functions|length }}{% if logical_manifest.custom_functions %} + {{ logical_manifest.custom_functions }} custom{% endif %})</h3>
<div class="logical-tags">{% for f in logical_manifest.functions %}<code>{{ f }}</code>{% endfor %}</div>
{% endif %}
{% if logical_manifest.liquid_gains %}
<h3>Liquids</h3>
<div class="logical-tags">{% for l in logical_manifest.liquid_gains %}<code>{{ l }}</code>{% endfor %}</div>
{% endif %}
{% if logical_manifest.entity_profiles %}
<h3>Entity profiles</h3>
<div class="logical-tags">{% for e in logical_manifest.entity_profiles %}<code>{{ e }}</code>{% endfor %}</div>
{% endif %}
{% if logical_manifest.trinkets %}
<h3>Trinkets</h3>
<div class="logical-tags">{% for t in logical_manifest.trinkets %}<code>{{ t }}</code>{% endfor %}</div>
{% endif %}
</div>
</section>
{% endif %}
{% if mods_manifest %}
<section class="tab-panel" id="tab-mods">
<div class="card">
<h2><i class="fas fa-cubes"></i> Mods</h2>
{% if mods_manifest.files %}
<span class="mods-source-badge">{{ mods_manifest.source }}</span>
<ul class="mods-list">
{% for mod in mods_manifest.files %}
<li><i class="fas fa-cube"></i> {{ mod.name }}</li>
{% endfor %}
</ul>
{% else %}
<p class="empty-hint">No mods detected in the uploaded pack.</p>
{% endif %}
</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" data-media-url="{{ asset.file_url }}" data-media-type="{% if asset.is_video %}video{% else %}image{% endif %}" data-caption="{{ asset.caption }}">
{% if asset.is_video or asset.is_gif %}
<img class="gallery-thumb" src="{% url 'library:asset_thumbnail' project.slug asset.pk %}" alt="{{ asset.caption }}" loading="lazy">
{% else %}
<img src="{{ asset.file_url }}" alt="{{ asset.caption }}" loading="lazy">
{% endif %}
<span class="gallery-expand" title="Open fullscreen"><i class="fas fa-expand"></i></span>
{% 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>
<div class="modal-overlay" id="gallery-modal" hidden>
<div class="modal-content media-modal-content">
<button type="button" class="modal-close" data-close-modal aria-label="Close"><i class="fas fa-times"></i></button>
<div class="modal-media" id="gallery-modal-media"></div>
<p class="modal-caption" id="gallery-modal-caption"></p>
</div>
</div>
<div class="modal-overlay" id="model-modal" hidden>
<div class="modal-content model-modal-content">
<button type="button" class="modal-close" data-close-modal aria-label="Close"><i class="fas fa-times"></i></button>
<h3 class="model-modal-title" id="model-modal-title"></h3>
<div id="model-viewer-container"></div>
<p class="bio-help"><i class="fas fa-info-circle"></i> Drag to rotate · scroll to zoom</p>
<p class="bio-help model-disclaimer"><i class="fas fa-exclamation-triangle"></i> This is just a preview — if the model is broken, contact Jake so he can fix it.</p>
</div>
</div>
{% endblock %}
{% block extra_js %}
{% if models_manifest %}
<script src="{% static 'vendor/three.min.js' %}"></script>
<script src="{% static 'js/geo_builder.js' %}"></script>
<script src="{% static 'js/model_viewer.js' %}"></script>
{% endif %}
<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);
}
// Full-screen gallery modal (images/gifs/videos; videos muted).
const galleryModal = document.getElementById('gallery-modal');
if (galleryModal) {
const mediaEl = document.getElementById('gallery-modal-media');
const captionEl = document.getElementById('gallery-modal-caption');
document.querySelectorAll('.gallery-item[data-media-url]').forEach(item => {
item.addEventListener('click', (e) => {
if (e.target.closest('.gallery-delete')) return;
const url = item.dataset.mediaUrl;
const type = item.dataset.mediaType;
mediaEl.innerHTML = type === 'video'
? '<video controls muted playsinline autoplay src="' + url + '"></video>'
: '<img src="' + url + '" alt="">';
captionEl.textContent = item.dataset.caption || '';
galleryModal.hidden = false;
});
});
}
// Close buttons for modals.
document.querySelectorAll('[data-close-modal]').forEach(btn => {
btn.addEventListener('click', () => {
const modal = btn.closest('.modal-overlay');
if (modal) {
modal.hidden = true;
const media = modal.querySelector('.modal-media');
if (media) media.innerHTML = '';
if (modal.id === 'model-modal') {
const viewer = document.getElementById('model-viewer-container');
if (viewer) viewer.innerHTML = '';
if (window.PacksModelViewer) window.PacksModelViewer.dispose();
}
}
});
});
document.querySelectorAll('.modal-overlay').forEach(modal => {
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.hidden = true;
if (modal.id === 'model-modal' && window.PacksModelViewer) window.PacksModelViewer.dispose();
}
});
});
// Model renderer (Three.js showcase).
if (window.PacksModelViewer) {
document.querySelectorAll('.model-render-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.getElementById('model-modal-title').textContent = btn.dataset.name || '';
const container = document.getElementById('model-viewer-container');
container.innerHTML = '';
window.PacksModelViewer.render(
btn.dataset.member,
container,
{
baseUrl: btn.dataset.baseUrl,
vanillaBaseUrl: btn.dataset.vanillaBaseUrl,
defaultTexture: btn.dataset.defaultTexture || null,
}
);
document.getElementById('model-modal').hidden = false;
});
});
}
})();
</script>
{% endblock %}