5 Commits
6 changed files with 40 additions and 13 deletions

No files matched your search

+3 -3
View File
@@ -43,7 +43,7 @@ Create a directory for OmniArr and add this `docker-compose.yml`:
```yaml
services:
omniarr:
image: wonderalice/omniarr:1.2.2
image: wonderalice/omniarr:1.2.4
container_name: omniarr
restart: unless-stopped
ports:
@@ -90,11 +90,11 @@ the mount loses that state when the container is replaced.
The current release is available as:
```text
wonderalice/omniarr:1.2.2
wonderalice/omniarr:1.2.4
wonderalice/omniarr:latest
```
Pin `1.2.2` in production for repeatable updates. Use `latest` only when you want
Pin `1.2.4` in production for repeatable updates. Use `latest` only when you want
Compose to follow the newest published image.
## Build locally
+1 -1
View File
@@ -1,6 +1,6 @@
services:
omniarr:
image: wonderalice/omniarr:1.2.2
image: wonderalice/omniarr:1.2.4
# Para construir desde código fuente en su lugar, descomenta estas líneas:
# build:
# context: .
+5 -2
View File
@@ -817,14 +817,17 @@ async function serveApp(pathname: string, response: ServerResponse): Promise<voi
try {
const content = await readFile(filePath);
response.writeHead(200, {
'Content-Type': contentType(filePath)
'Content-Type': contentType(filePath),
// Hashed bundles never change; the entry page must always be revalidated so a new release reaches every browser.
'Cache-Control': relativePath.startsWith('/assets/') ? 'public, max-age=31536000, immutable' : 'no-cache'
});
response.end(content);
} catch {
try {
const indexHtml = await readFile(resolve(process.cwd(), 'dist', 'index.html'));
response.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
'Content-Type': 'text/html; charset=utf-8',
'Cache-Control': 'no-cache'
});
response.end(indexHtml);
} catch {
+4 -1
View File
@@ -4153,7 +4153,10 @@ function jellyfinToCard(
const backdropItemId = item.ParentBackdropItemId || item.Id;
const backdropTag = item.ParentBackdropImageTags?.[0] || item.BackdropImageTags?.[0] || posterTag;
const progress = Math.round(item.UserData?.PlayedPercentage || 0);
const tmdbId = Number(item.ProviderIds?.Tmdb) || (item.SeriesId ? tmdbIdByItemId?.get(item.SeriesId) : undefined);
// An episode's own TMDb id is not its series' id, and artwork is looked up per series.
const tmdbId = item.Type === 'Episode'
? (item.SeriesId ? tmdbIdByItemId?.get(item.SeriesId) : undefined)
: Number(item.ProviderIds?.Tmdb) || (item.SeriesId ? tmdbIdByItemId?.get(item.SeriesId) : undefined);
const episodeCode = formatEpisodeCode(item.ParentIndexNumber, item.IndexNumber);
return {
+23 -3
View File
@@ -1786,7 +1786,7 @@ function MediaTile(props: {
</div>
) : null}
{props.card.hoverLabel && !continueEpisodeMeta ? <div className="media-hover-label">{props.card.hoverLabel}</div> : null}
{props.card.progress ? <div className="tile-progress"><div className="tile-progress-fill" style={{ width: `${props.card.progress}%` }} /></div> : null}
{props.isContinueWatching && props.card.progress ? <div className="tile-progress"><div className="tile-progress-fill" style={{ width: `${props.card.progress}%` }} /></div> : null}
</button>
{!props.isContinueWatching && (props.styleType === 'landscape' || props.styleType === 'poster') ? <WishlistHeart card={props.card} className="media-wishlist-button" /> : null}
</article>
@@ -2108,6 +2108,26 @@ function TitleDetailsModal(props: {
const backgroundImage = details?.backdropUrl || props.card.backdropUrl || props.card.posterUrl
const logoUrl = details?.logoUrl || props.card.logoUrl
const displayTitle = details?.title || props.card.title
// Titles opened from Continue Watching arrive as a Jellyfin episode card, which
// cannot be wishlisted; the series the details resolved to can.
const wishlistCard: MediaCard = props.card.mediaType === 'episode' && details?.mediaType === 'series'
? {
...props.card,
id: details.providerId ? `jellyfin-${details.providerId}` : details.id,
tmdbId: details.tmdbId ?? props.card.tmdbId,
providerId: details.providerId ?? props.card.providerId,
title: details.title,
subtitle: '',
overview: details.overview,
mediaType: 'series',
posterUrl: details.posterUrl,
backdropUrl: details.backdropUrl,
logoUrl: details.logoUrl ?? props.card.logoUrl,
progress: undefined,
secondaryValue: undefined,
hoverLabel: undefined
}
: props.card
const displaySubtitle = details?.subtitle || props.card.subtitle
const displayOverview = details?.overview || props.card.overview || 'No details available yet.'
const displayYear = props.card.releaseDate?.slice(0, 4)
@@ -2716,7 +2736,7 @@ function TitleDetailsModal(props: {
</p>
<div className="title-details-summary-header">
{details?.mediaType !== 'series' ? (
<WishlistHeart card={props.card} className="title-season-action title-wishlist-button" divider="after" />
<WishlistHeart card={wishlistCard} className="title-season-action title-wishlist-button" divider="after" />
) : null}
{canPlay && details?.mediaType !== 'series' ? (
<ActionButton className="title-summary-header-play" onClick={() => setPlayerTarget({ id: playId!, title: playLabel })} variant="play">
@@ -2838,7 +2858,7 @@ function TitleDetailsModal(props: {
</div>
<>
<div className="title-season-actions">
<WishlistHeart card={props.card} className="title-season-action title-wishlist-button" divider="before" />
<WishlistHeart card={wishlistCard} className="title-season-action title-wishlist-button" divider="before" />
{shouldShowDownloadSeasonAction || activeSeasonMeta?.isDownloading ? <div className="title-season-divider title-season-action-divider" aria-hidden="true" /> : null}
{shouldShowDownloadSeasonAction || activeSeasonMeta?.isDownloading ? (
activeSeasonDownloadStatus === 'downloading' ? (
+4 -3
View File
@@ -6041,10 +6041,11 @@ img {
color: #ff5c7a;
}
/* Touch screens have no hover, so the heart is always visible there. */
@media (hover: none) {
/* Phones and touch screens have no hover, and a heart on every poster is noise;
the wishlist stays reachable from the title details. */
@media (hover: none), (max-width: 980px) {
.media-wishlist-button {
opacity: 1;
display: none;
}
}