ArchiveBox/archivebox/templates/admin/snapshots_grid.html
2026-07-25 14:50:15 -07:00

294 lines
8.6 KiB
HTML

{% load i18n admin_urls static admin_list %}
{% load core_tags %}
{% block extrastyle %}
<style>
#changelist-search #searchbar {
min-height: 24px;
}
.cards {
padding-top: 10px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); /* see notes below */
grid-auto-rows: minmax(200px, auto);
grid-gap: 14px 14px;
}
.cards .card {
position: relative;
max-height: none;
overflow: hidden;
background-color: #fffcfc;
border: 1px solid #f1efef;
border-radius: 4px;
box-shadow: 4px 4px 2px 2px rgba(0, 0, 0, 0.01);
text-align: center;
color: #5d5e5e;
}
.cards .card.selected-card {
border: 3px solid #2196f3;
box-shadow: 2px 3px 6px 2px rgba(0, 0, 221, 0.14);
}
.cards .card .card-thumbnail {
display: block;
width: 100%;
height: 245px;
overflow: hidden;
border-radius: 4px;
background-color: #fffcfc;
}
.cards .card .card-thumbnail img {
width: 100%;
height: auto;
border: 0;
}
.cards .card .card-thumbnail img.loading-preview {
width: 48px;
height: 48px;
margin-top: 98px;
object-fit: contain;
}
.cards .card .card-thumbnail.missing img {
display: none;
}
.cards .card .card-thumbnail.missing::after {
content: "No preview captured";
display: grid;
place-items: center;
height: 100%;
color: #94a3b8;
font-size: 12px;
background: linear-gradient(135deg, #f8fafc, #f1f5f9);
}
.cards .card .card-thumbnail .missing-preview {
display: grid;
place-items: center;
height: 100%;
color: #94a3b8;
font-size: 12px;
background: linear-gradient(135deg, #f8fafc, #f1f5f9);
}
.cards .card .card-tags {
width: 100%;
}
.cards .card .card-tags span {
display: inline-block;
padding: 2px 5px;
border-radius: 5px;
opacity: 0.95;
background-color: #bfdfff;
color: #679ac2;
font-size: 12px;
margin-bottom: 3px;
}
.cards .card .card-footer {
width: 100%;
position: absolute;
bottom: 0;
text-align: center;
}
.cards .card .card-media {
position: relative;
overflow: hidden;
}
.cards .card .card-media .card-tags {
position: absolute;
left: 0;
right: 0;
bottom: 0;
z-index: 2;
text-align: center;
pointer-events: none;
}
.cards .card .card-title {
padding: 4px 5px;
background-color: #fffcfc;
/*height: 50px;
vertical-align: middle;
line-height: 50px;*/
}
.cards .card .card-title h4 {
color: initial;
display: block;
vertical-align: middle;
line-height: normal;
margin: 0px;
padding: 5px 0px;
font-size: 13.5px;
font-weight: 400;
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
max-height: 46px;
}
.cards .card .card-title h4 .title-text {
user-select: all;
}
.cards .card .card-title .link-favicon {
height: 15px;
margin: 2px;
vertical-align: -5px;
display: inline-block;
}
.cards .card .card-info {
padding: 6px 8px;
/*border-top: 1px solid #ddd;*/
background-color: #fffcfc;
font-size: 11px;
color: #333;
}
.cards .card .card-meta {
display: grid;
grid-template-columns: 18px minmax(0, 1fr) auto;
gap: 8px;
align-items: center;
text-align: center;
}
.cards .card .card-meta .card-date,
.cards .card .card-meta .card-size {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.cards .card .card-info input[type=checkbox] {
width: 18px;
height: 18px;
margin: 0;
}
.cards .card .card-info label {
display: inline-block;
height: 20px;
width: 145px;
font-size: 11px;
}
.cards .card .card-info .timestamp {
font-weight: 600;
}
.cards .card .card-url {
display: block;
width: 100%;
padding: 0 8px 6px;
font-size: 10px;
opacity: 0.4;
user-select: all;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
background: #fffcfc;
box-sizing: border-box;
}
.cards .card .card-outputs {
min-height: 24px;
padding: 5px 8px 7px;
background: #fffcfc;
text-align: center;
}
.cards .card .card-progress {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 0;
}
.cards .card .card-progress .progress-text {
font-size: 11px;
color: #3b82f6;
font-weight: 500;
}
.cards .card.archiving {
border-color: #3b82f6;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
}
.cards .card .card-footer code {
display: inline-block;
width: 100%;
margin-top: 2px;
font-size: 10px;
opacity: 0.4;
user-select: all;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
{% endblock %}
{% block content %}
<section class="cards">
{% for obj in results %}
<div class="card{% if obj.status == 'started' %} archiving{% endif %}">
<div class="card-info card-meta">
<input type="checkbox" class="action-select" name="_selected_action" value="{{obj.pk}}"/>
<a href="{% url 'admin:core_snapshot_change' obj.pk %}" class="card-date">
<span class="timestamp">{{obj.bookmarked_at}}</span>
</a>
<span class="card-size">{{ obj.archive_size | file_size }}</span>
</div>
<div class="card-title" title="{{obj.title}}">
<a href="{% snapshot_base_url obj %}/index.html">
<h4>
{% if obj.is_archived %}
<img src="{% snapshot_base_url obj %}/favicon.ico" onerror="this.style.display='none'" class="link-favicon" decoding="async"/>
{% else %}
<img src="{% static 'spinner.gif' %}" onerror="this.style.display='none'" class="link-favicon" decoding="async"/>
{% endif %}
<span class="title-text">{{obj.title|default:'Pending...' }}</span>
</h4>
</a>
</div>
<code class="card-url" title="{{obj.url}}">{{obj.url}}</code>
<div class="card-media">
<a href="{% snapshot_base_url obj %}/index.html" class="card-thumbnail">
{% snapshot_archiveresult_url obj 'screenshot' 'screenshot.png' as screenshot_url %}
{% snapshot_archiveresult_url obj 'chrome_extension_screenshot' 'screenshot-1.png' as extension_screenshot_1_url %}
{% snapshot_archiveresult_url obj 'chrome_extension_screenshot' 'screenshot.png' as extension_screenshot_url %}
{% snapshot_archiveresult_url obj 'favicon' 'favicon.ico' as favicon_url %}
{% if screenshot_url or extension_screenshot_1_url or extension_screenshot_url or favicon_url %}
<img
src="{% firstof screenshot_url extension_screenshot_1_url extension_screenshot_url favicon_url %}"
alt="{{obj.title|default:'Not yet archived...'}}"
data-fallbacks="{{ extension_screenshot_1_url }},{{ extension_screenshot_url }},{{ favicon_url }}"
onerror="const fallbacks=(this.dataset.fallbacks || '').split(',').filter(Boolean); if (fallbacks.length) { this.dataset.fallbacks=fallbacks.slice(1).join(','); this.src=fallbacks[0]; } else { this.onerror=null; this.closest('.card-thumbnail').classList.add('missing'); this.remove(); }"
/>
{% elif obj.status == 'started' %}
<img src="{% static 'spinner.gif' %}" class="loading-preview" alt="Archiving in progress"/>
{% else %}
<span class="missing-preview">No preview captured</span>
{% endif %}
</a>
{% if obj.tags_str %}
<div class="card-tags">
{% for tag in obj.tags_str|split:',' %}
<span>
{{tag}}
</span>
{% endfor %}
</div>
{% endif %}
</div>
<div class="card-outputs">
{% if obj.status == 'started' %}
<div class="card-progress">
<span class="snapshot-progress-spinner"></span>
<span class="progress-text">Archiving...</span>
</div>
{% else %}
{{ obj.icons|safe }}
{% endif %}
</div>
</div>
{% endfor %}
</section>
<br/>
{% endblock %}