Show image thumbnails in the playlist Add Content card
The "Add Content" card listed available media as an icon plus a filename, so the only way to tell two media items apart was to read near-identical filenames. Images now render an actual thumbnail. Previews are shown for every image type, not just JPEG: the library is a mix of JPEG and PNG (the PDF/PPTX converter emits PNG), so a JPEG-only rule would have left most items without a preview. Non-images keep the existing emoji icon. A missing or unreadable file falls back to the icon via onerror, and images are `loading="lazy"` since the list is scrollable. This surfaced two layout bugs in the same card, both now fixed: - .content-grid used bare `2fr 1fr`. A grid track has an automatic min-content floor, so the long unbreakable media filenames forced the track wider than the viewport — the page had a horizontal scrollbar (scrollWidth 1475 in a 1185px viewport). minmax(0, ...) fixes it. - Long names were truncated with ellipsis, which hides the distinguishing part: media filenames differ at the END (..._slide_003.png). Names now wrap with overflow-wrap: anywhere instead. The row is flex-wrap so that on a narrow card the Add button drops to its own line rather than squeezing the name. Verified: 5/5 items render an <img> whose URL returns HTTP 200 (including a nested edited_media/ JPEG), no element overlaps at 1000-1500px, and dark mode picks up the theme variables.
This commit is contained in:
@@ -39,7 +39,11 @@
|
|||||||
|
|
||||||
.content-grid {
|
.content-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 2fr 1fr;
|
/* minmax(0, …) is required: a bare `2fr 1fr` track has an automatic
|
||||||
|
min-content floor, so the long, unbreakable media filenames in the
|
||||||
|
Add Content card forced the track wider than the viewport and gave
|
||||||
|
the whole page a horizontal scrollbar. */
|
||||||
|
grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +91,56 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
/* On a narrow viewport the thumbnail + name + button no longer fit on
|
||||||
|
one line; wrapping drops the Add button below instead of squeezing
|
||||||
|
the name under it. */
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Left group: thumbnail + name/size. min-width:0 lets long names ellipsise
|
||||||
|
instead of pushing the Add button out of the card. */
|
||||||
|
.avail-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.avail-thumb {
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #e9ecef;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.avail-thumb img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.avail-info {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.avail-name {
|
||||||
|
font-size: 14px;
|
||||||
|
/* Long media filenames carry the meaningful part at the END
|
||||||
|
(…_slide_003.png), so breaking mid-word is better than ellipsis,
|
||||||
|
which would hide the distinguishing characters. `anywhere` allows
|
||||||
|
breaks inside the unbroken run and keeps min-content width tiny. */
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
.avail-meta {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Audio toggle styles */
|
/* Audio toggle styles */
|
||||||
@@ -216,6 +270,15 @@
|
|||||||
color: #e2e8f0;
|
color: #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.dark-mode .avail-thumb {
|
||||||
|
background: #2d3748;
|
||||||
|
border-color: #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .avail-meta {
|
||||||
|
color: #a0aec0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Dark mode for duration spinner */
|
/* Dark mode for duration spinner */
|
||||||
body.dark-mode .duration-display {
|
body.dark-mode .duration-display {
|
||||||
background: #2d3748;
|
background: #2d3748;
|
||||||
@@ -428,21 +491,32 @@
|
|||||||
<div class="available-content">
|
<div class="available-content">
|
||||||
{% for content in available_content %}
|
{% for content in available_content %}
|
||||||
<div class="content-item">
|
<div class="content-item">
|
||||||
<div>
|
<div class="avail-main">
|
||||||
<div>
|
<div class="avail-thumb">
|
||||||
{% if content.content_type == 'image' %}📷
|
{% if content.content_type == 'image' %}
|
||||||
{% elif content.content_type == 'video' %}🎥
|
{# Preview the real image so the user can see what
|
||||||
{% elif content.content_type == 'pdf' %}📄
|
is being added. Falls back to the camera icon
|
||||||
{% else %}📁{% endif %}
|
if the file is missing or fails to load. #}
|
||||||
{{ content.original_display_name }}
|
<img src="{{ url_for('static', filename='uploads/' + content.current_media_path) }}"
|
||||||
|
alt="{{ content.original_display_name }}"
|
||||||
|
loading="lazy"
|
||||||
|
onerror="this.parentElement.innerHTML='<span>📷</span>';">
|
||||||
|
{% elif content.content_type == 'video' %}<span>🎥</span>
|
||||||
|
{% elif content.content_type == 'pdf' %}<span>📄</span>
|
||||||
|
{% else %}<span>📁</span>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: 12px; color: #999;">
|
<div class="avail-info">
|
||||||
{{ content.file_size_mb }} MB
|
<div class="avail-name" title="{{ content.original_display_name }}">
|
||||||
|
{{ content.original_display_name }}
|
||||||
|
</div>
|
||||||
|
<div class="avail-meta">
|
||||||
|
{{ content.file_size_mb }} MB
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form method="POST"
|
<form method="POST"
|
||||||
action="{{ url_for('content.add_content_to_playlist', playlist_id=playlist.id) }}"
|
action="{{ url_for('content.add_content_to_playlist', playlist_id=playlist.id) }}"
|
||||||
style="display: inline;">
|
style="display: inline; flex: 0 0 auto;">
|
||||||
<input type="hidden" name="content_id" value="{{ content.id }}">
|
<input type="hidden" name="content_id" value="{{ content.id }}">
|
||||||
<input type="hidden" name="duration" value="{{ content.duration }}">
|
<input type="hidden" name="duration" value="{{ content.duration }}">
|
||||||
<button type="submit" class="btn btn-success btn-sm">
|
<button type="submit" class="btn btn-success btn-sm">
|
||||||
|
|||||||
Reference in New Issue
Block a user