Responsiveness tweaks
This commit is contained in:
+7
-19
@@ -1,13 +1,17 @@
|
||||
<#
|
||||
Deploy embycovers to an Asustor NAS over SSH/SCP from Windows PowerShell.
|
||||
|
||||
Syncs project files to the NAS, then runs `docker compose build` and
|
||||
`docker compose up -d` on the remote. All runtime configuration (Emby URL,
|
||||
API key, ports, volumes) comes from docker-compose.yml — edit it there.
|
||||
|
||||
Prerequisites on your Windows machine:
|
||||
- OpenSSH Client installed. Test with: ssh -V
|
||||
- SSH enabled on the Asustor NAS.
|
||||
- Docker / Docker Compose available on the Asustor NAS.
|
||||
|
||||
Example:
|
||||
.\deploy.ps1 -NasHost MATT-NAS -NasUser admin -EmbyUrl http://192.168.1.50:8096 -EmbyApiKey abcdef123456
|
||||
.\deploy.ps1 -NasHost MATT-NAS -NasUser ssh
|
||||
#>
|
||||
|
||||
param(
|
||||
@@ -17,15 +21,7 @@ param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$NasUser,
|
||||
|
||||
[string]$RemoteAppDir = "/share/Docker/embycovers",
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$EmbyUrl,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$EmbyApiKey,
|
||||
|
||||
[int]$HostPort = 8500
|
||||
[string]$RemoteAppDir = "/share/Docker/embycovers"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
@@ -43,8 +39,6 @@ $LocalDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$Remote = "$NasUser@$NasHost"
|
||||
|
||||
Write-Host "Deploying embycovers to ${Remote}:$RemoteAppDir"
|
||||
Write-Host "Emby URL: $EmbyUrl"
|
||||
Write-Host "Host port: $HostPort"
|
||||
|
||||
ssh $Remote @"
|
||||
set -e
|
||||
@@ -72,16 +66,11 @@ if ($LogoFiles) {
|
||||
Invoke-Expression $ScpCmd
|
||||
}
|
||||
|
||||
# Patch docker-compose.yml on the NAS with the supplied Emby settings, then build & run.
|
||||
# Build and run on the NAS using values from docker-compose.yml.
|
||||
$RemoteCommands = @"
|
||||
set -e
|
||||
cd '$RemoteAppDir'
|
||||
|
||||
# Patch compose env vars and host port from deployment parameters.
|
||||
sed -i "s#EMBY_URL=.*#EMBY_URL=$EmbyUrl#g" docker-compose.yml
|
||||
sed -i "s#EMBY_API_KEY=.*#EMBY_API_KEY=$EmbyApiKey#g" docker-compose.yml
|
||||
sed -i "s#\"8500:8500\"#\"$HostPort:8500\"#g" docker-compose.yml
|
||||
|
||||
if command -v docker-compose >/dev/null 2>&1; then
|
||||
docker-compose build
|
||||
docker-compose up -d
|
||||
@@ -94,6 +83,5 @@ fi
|
||||
ssh $Remote $RemoteCommands
|
||||
|
||||
Write-Host "Deployment complete."
|
||||
Write-Host "App should be reachable at: http://${NasHost}:$HostPort"
|
||||
Write-Host "To view logs:"
|
||||
Write-Host " ssh $Remote 'cd $RemoteAppDir && docker compose logs -f'"
|
||||
|
||||
+17
-10
@@ -1,18 +1,25 @@
|
||||
services:
|
||||
emby-thumb-gen:
|
||||
embycovers:
|
||||
build: .
|
||||
container_name: emby-thumb-gen
|
||||
container_name: embytoolkit
|
||||
ports:
|
||||
- "8500:8500"
|
||||
environment:
|
||||
- EMBY_URL=http://your-emby-server:8096
|
||||
- EMBY_API_KEY=your-api-key-here
|
||||
- TZ=Pacific/Auckland
|
||||
- EMBY_URL=http://10.0.0.2:8096
|
||||
- EMBY_API_KEY=b9af54b630f6448289ab96422add567a
|
||||
# Optional external artwork providers:
|
||||
# - TMDB_BEARER_TOKEN=your-tmdb-read-access-token
|
||||
# - TMDB_API_KEY=your-tmdb-v3-api-key
|
||||
# - GOOGLE_CUSTOM_SEARCH_API_KEY=your-google-api-key
|
||||
# - GOOGLE_CUSTOM_SEARCH_ENGINE_ID=your-google-search-engine-id
|
||||
# - TMDB_BEARER_TOKEN=
|
||||
# - TMDB_API_KEY=
|
||||
# - GOOGLE_CUSTOM_SEARCH_API_KEY=
|
||||
# - GOOGLE_CUSTOM_SEARCH_ENGINE_ID=
|
||||
volumes:
|
||||
- ./output:/app/output
|
||||
- ./cache:/app/cache
|
||||
- /share/Docker/embytoolkit/output:/app/output
|
||||
- /share/Docker/embytoolkit/cache:/app/cache
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- npm_network
|
||||
|
||||
networks:
|
||||
npm_network:
|
||||
external: true
|
||||
|
||||
+132
-9
@@ -28,18 +28,60 @@
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html, body { height: 100%; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
display: grid;
|
||||
grid-template-columns: 220px 1fr;
|
||||
grid-template-columns: 240px 1fr;
|
||||
height: 100vh;
|
||||
height: 100svh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ── Mobile top bar (hidden on desktop) ── */
|
||||
.app-topbar {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 14px;
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid var(--border);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 30;
|
||||
}
|
||||
.app-topbar-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
.icon-btn {
|
||||
width: 38px; height: 38px;
|
||||
display: inline-grid; place-items: center;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface2);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
}
|
||||
.icon-btn:hover { background: var(--surface3); }
|
||||
|
||||
.nav-scrim {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,0.55);
|
||||
z-index: 40;
|
||||
opacity: 0;
|
||||
transition: opacity 0.18s ease;
|
||||
}
|
||||
.nav-scrim.show { opacity: 1; }
|
||||
|
||||
/* ── App Nav Sidebar ── */
|
||||
.app-nav {
|
||||
display: flex;
|
||||
@@ -108,9 +150,14 @@
|
||||
.dot.off { background: var(--red); box-shadow: 0 0 6px var(--red); }
|
||||
|
||||
.page {
|
||||
padding: 20px 22px 26px;
|
||||
padding: 24px clamp(16px, 2.4vw, 36px) 32px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.page-inner {
|
||||
width: 100%;
|
||||
max-width: 1680px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.hero {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
@@ -203,8 +250,14 @@
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 14px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(100%, 340px), 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
@media (min-width: 1600px) {
|
||||
.grid { grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 18px; }
|
||||
}
|
||||
@media (min-width: 2200px) {
|
||||
.grid { grid-template-columns: repeat(auto-fill, minmax(420px, 1fr)); }
|
||||
}
|
||||
|
||||
.card {
|
||||
@@ -351,31 +404,89 @@
|
||||
.toast.ok { background: var(--green-bg); border: 1px solid var(--green); color: var(--green); }
|
||||
.toast.err { background: rgba(248,113,113,.1); border: 1px solid var(--red); color: var(--red); }
|
||||
|
||||
/* Ultra-wide tweak: widen the content cap a touch more on huge displays */
|
||||
@media (min-width: 2400px) {
|
||||
.page-inner { max-width: 1920px; }
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
body { grid-template-columns: 200px 1fr; }
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.hero, .toolbar, .pager {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.toolbar-actions, .pager-actions {
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
/* ── Mobile: drawer nav ── */
|
||||
@media (max-width: 860px) {
|
||||
body {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
}
|
||||
.app-topbar { display: flex; }
|
||||
.app-nav {
|
||||
position: fixed;
|
||||
top: 0; left: 0; bottom: 0;
|
||||
width: min(82vw, 300px);
|
||||
z-index: 50;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.22s cubic-bezier(0.4,0,0.2,1);
|
||||
box-shadow: 0 30px 60px rgba(0,0,0,0.4);
|
||||
}
|
||||
body.nav-open .app-nav { transform: translateX(0); }
|
||||
body.nav-open .nav-scrim { display: block; }
|
||||
.page { padding: 16px; }
|
||||
.header { padding: 0 16px; }
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.toolbar { padding: 12px; }
|
||||
.toolbar-actions { gap: 8px; }
|
||||
.toolbar-actions .btn,
|
||||
.toolbar-actions .chip-toggle { flex: 1 1 auto; justify-content: center; }
|
||||
.card {
|
||||
grid-template-columns: 84px minmax(0, 1fr);
|
||||
grid-template-columns: 92px minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
min-height: 0;
|
||||
}
|
||||
.card-poster { width: 84px; }
|
||||
.card-poster { width: 92px; }
|
||||
.card-title { font-size: 16px; }
|
||||
.card-actions { flex-wrap: wrap; }
|
||||
.card-actions .btn { width: 100%; }
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.card {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.card-poster {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
object-fit: cover;
|
||||
}
|
||||
.hero-copy h2 { font-size: 22px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="app-topbar">
|
||||
<button class="icon-btn" id="navToggle" aria-label="Open navigation">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||||
</button>
|
||||
<div class="app-topbar-brand">
|
||||
<span>EmbyToolkit</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="nav-scrim" id="navScrim"></div>
|
||||
|
||||
<nav class="app-nav">
|
||||
<div class="app-nav-brand">
|
||||
<div class="app-nav-logo">
|
||||
@@ -408,6 +519,7 @@
|
||||
</nav>
|
||||
|
||||
<main class="page">
|
||||
<div class="page-inner">
|
||||
<section class="hero">
|
||||
<div class="hero-copy">
|
||||
<h2>Current Airing Shows</h2>
|
||||
@@ -448,6 +560,7 @@
|
||||
<button class="btn" id="btnNext" disabled>Next</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
@@ -483,6 +596,16 @@ const btnPrev = $('#btnPrev');
|
||||
const btnNext = $('#btnNext');
|
||||
const toast = $('#toast');
|
||||
|
||||
const navToggle = $('#navToggle');
|
||||
const navScrim = $('#navScrim');
|
||||
if (navToggle && navScrim) {
|
||||
const closeNav = () => document.body.classList.remove('nav-open');
|
||||
navToggle.addEventListener('click', () => document.body.classList.toggle('nav-open'));
|
||||
navScrim.addEventListener('click', closeNav);
|
||||
document.querySelectorAll('.app-nav-item').forEach((link) => link.addEventListener('click', closeNav));
|
||||
window.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeNav(); });
|
||||
}
|
||||
|
||||
function esc(value) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = value ?? '';
|
||||
|
||||
Reference in New Issue
Block a user