Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions website/models/artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,39 @@ def raw_file_label(self):
return None
return self.RAW_FILE_LABELS.get(ext, ext.lstrip('.').upper())

@staticmethod
def _safe_file_size(file_field):
"""
Size of a FileField's file in bytes, or None if there is no file or it
can't be read.

Reading ``FieldFile.size`` hits storage (a stat) and raises
``FileNotFoundError`` when the file has gone missing on disk — which
happens in practice on the servers (the fuzzy ``serve_pdf`` view exists
precisely because publication files get renamed/removed). The public
preview card (#840) reads these sizes at render time for every card on
a listing, so a single missing file must degrade to "no size shown"
rather than 500 the whole page.
"""
if not file_field:
return None
try:
return file_field.size
except (OSError, ValueError):
return None

@property
def pdf_file_size(self):
"""Size of ``pdf_file`` in bytes, or None if empty/missing. See
:meth:`_safe_file_size`."""
return self._safe_file_size(self.pdf_file)

@property
def raw_file_size(self):
"""Size of ``raw_file`` in bytes, or None if empty/missing. See
:meth:`_safe_file_size`."""
return self._safe_file_size(self.raw_file)

def __str__(self):
if self.id and self.authors.exists():
return "{}, '{}', {} {}".format(self.get_first_author_last_name(), self.title, self.forum_name, self.date)
Expand Down
73 changes: 72 additions & 1 deletion website/static/website/css/publications.css
Original file line number Diff line number Diff line change
Expand Up @@ -684,4 +684,75 @@
border-width: 2px;
border-color: var(--color-primary);
font-weight: var(--font-weight-medium);
}
}
/* ============================================================================
ARTIFACT PREVIEW CARD (#840)
============================================================================
Poster / Talk preview popover opened by thumbnailPreview.js: a thumbnail
plus download actions (PDF, raw file, Source). It reuses Bootstrap's
`.popover` base class (border, shadow, arrow); the rules below size the card
and style its contents. */

.artifact-preview-popover {
/* Compact card. Talk slides are landscape, so width drives the size; keep it
modest. Posters (often portrait) are bounded by the image max-height. */
max-width: 272px;
/* Bootstrap sets .popover { display: none }; the JS reveals it. Keep it
above page chrome but below modals. */
z-index: 1060;
}

.artifact-preview-popover .popover-content {
padding: 6px;
}

.artifact-preview-image-link {
display: block;
cursor: pointer;
}

.artifact-preview-image {
display: block;
width: 100%;
height: auto;
/* Never let a tall poster run past the viewport. */
max-height: 45vh;
object-fit: contain;
border-radius: var(--border-radius-sm, 3px);
}

.artifact-preview-actions {
display: flex;
flex-direction: column;
gap: 2px;
margin-top: 6px;
}

.artifact-preview-action {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 8px;
border-radius: var(--border-radius-sm, 3px);
color: var(--color-text-primary);
text-decoration: none;
white-space: nowrap;
}

.artifact-preview-action:hover,
.artifact-preview-action:focus {
background-color: var(--color-bg-muted, #f0f0f0);
text-decoration: none;
}

.artifact-preview-action:focus-visible {
outline: var(--focus-ring-width, 2px) solid var(--focus-ring-color, #1b6ec2);
outline-offset: var(--focus-ring-offset, 2px);
}

/* File size, pushed to the trailing edge of each action row. */
.artifact-preview-size {
margin-left: auto;
color: var(--color-text-secondary, #666);
font-size: 0.85em;
}
Loading
Loading