Skip to content

Commit e8cd954

Browse files
aarroclaude
andcommitted
feat: consistent inputs, video-to-search click, clipboard fallback
- Unify name input style with rule-form controls (muted border, dark bg) - Clicking a video thumbnail in a collection loads its title into the Discover search bar (search state lifted to App, threaded via props) - Thumb strip items are now <button> elements for correct semantics - Add execCommand fallback for clipboard copy on non-HTTPS origins Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5115f09 commit e8cd954

4 files changed

Lines changed: 49 additions & 16 deletions

File tree

provider/ui/src/App.css

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,11 @@ select:focus {
277277
select option {
278278
background: var(--surface2);
279279
}
280+
/* Name input in expanded card matches rule-form control style */
281+
.card-body-top input[type="text"] {
282+
border-color: var(--muted);
283+
background: var(--bg);
284+
}
280285

281286
.rule-form {
282287
background: var(--surface2);
@@ -459,7 +464,12 @@ select option {
459464
}
460465
.thumb-strip-item {
461466
min-width: 0;
462-
cursor: default;
467+
cursor: pointer;
468+
background: transparent;
469+
border-radius: 0;
470+
padding: 0;
471+
text-align: left;
472+
width: 100%;
463473
}
464474
.thumb-strip-item img {
465475
width: 100%;

provider/ui/src/App.jsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export default function App() {
2020
const [saving, setSaving] = useState(false);
2121
const [rescanning, setRescanning] = useState(false);
2222
const [fixingThumbs, setFixingThumbs] = useState(false);
23+
const [search, setSearch] = useState("");
2324

2425
const load = useCallback(async () => {
2526
const [colRes, vidRes] = await Promise.all([fetch("/api/collections"), fetch("/api/videos")]);
@@ -122,10 +123,15 @@ export default function App() {
122123

123124
<div className="two-col">
124125
<div className="col-left">
125-
<Collections collections={data.collections} videos={videos} onChange={setCollections} />
126+
<Collections
127+
collections={data.collections}
128+
videos={videos}
129+
onChange={setCollections}
130+
onVideoSearch={setSearch}
131+
/>
126132
</div>
127133
<div className="col-right">
128-
<DiscoverPanel videos={videos} />
134+
<DiscoverPanel videos={videos} search={search} onSearch={setSearch} />
129135
</div>
130136
</div>
131137

provider/ui/src/Collections.jsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ function RuleForm({ rule, onChange, onRemove }) {
6767

6868
const THUMB_PAGE = 4;
6969

70-
function ThumbGrid({ videos }) {
70+
function ThumbGrid({ videos, onVideoSearch }) {
7171
const [expanded, setExpanded] = useState(false);
7272
if (videos.length === 0) {
7373
return <p className="empty thumb-grid-empty">No videos matched yet.</p>;
@@ -78,14 +78,20 @@ function ThumbGrid({ videos }) {
7878
<>
7979
<div className="thumb-grid">
8080
{shown.map((v) => (
81-
<div key={v.id} className="thumb-strip-item" title={v.title}>
81+
<button
82+
key={v.id}
83+
type="button"
84+
className="thumb-strip-item"
85+
title={v.title}
86+
onClick={() => onVideoSearch?.(v.title)}
87+
>
8288
{v.thumbnail ? (
8389
<img src={v.thumbnail} alt="" loading="lazy" />
8490
) : (
8591
<div className="thumb-strip-placeholder" />
8692
)}
8793
<div className="thumb-title">{v.title}</div>
88-
</div>
94+
</button>
8995
))}
9096
</div>
9197
{videos.length > THUMB_PAGE && (
@@ -101,7 +107,7 @@ function isAbsoluteUrl(url) {
101107
return typeof url === "string" && (url.startsWith("http://") || url.startsWith("https://"));
102108
}
103109

104-
function CollectionCard({ collection, videos, onChange, onDelete, otherNames, plexThumb }) {
110+
function CollectionCard({ collection, videos, onChange, onDelete, otherNames, plexThumb, onVideoSearch }) {
105111
const [expanded, setExpanded] = useState(false);
106112
const [editName, setEditName] = useState(collection.name);
107113
const [nameError, setNameError] = useState(null);
@@ -268,7 +274,7 @@ function CollectionCard({ collection, videos, onChange, onDelete, otherNames, pl
268274
</div>
269275

270276
{/* Matched video thumbnails */}
271-
<ThumbGrid videos={matched} />
277+
<ThumbGrid videos={matched} onVideoSearch={onVideoSearch} />
272278
</div>
273279

274280
{/* Full-width row 3: rules + actions */}
@@ -351,7 +357,7 @@ function AddCollectionForm({ onAdd, onCancel, existingNames }) {
351357
);
352358
}
353359

354-
export default function Collections({ collections, videos, onChange }) {
360+
export default function Collections({ collections, videos, onChange, onVideoSearch }) {
355361
const [adding, setAdding] = useState(false);
356362

357363
const updateAt = (i, c) => {
@@ -390,6 +396,7 @@ export default function Collections({ collections, videos, onChange }) {
390396
onDelete={() => deleteAt(i)}
391397
otherNames={collections.filter((_, idx) => idx !== i).map((x) => x.name)}
392398
plexThumb={c.plex_thumb}
399+
onVideoSearch={onVideoSearch}
393400
/>
394401
))}
395402

provider/ui/src/DiscoverPanel.jsx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { useEffect, useState } from "react";
22

3-
export default function DiscoverPanel({ videos }) {
4-
const [search, setSearch] = useState("");
3+
export default function DiscoverPanel({ videos, search, onSearch }) {
54
const [showAll, setShowAll] = useState(false);
65
const [expandedTags, setExpandedTags] = useState(new Set());
76
const [toast, setToast] = useState(null);
@@ -13,10 +12,21 @@ export default function DiscoverPanel({ videos }) {
1312
}, [toast]);
1413

1514
const copyTag = (tag) => {
16-
navigator.clipboard.writeText(tag).then(
17-
() => setToast(`Copied "${tag}"`),
18-
() => setToast(`Copy failed — paste manually: ${tag}`)
19-
);
15+
if (navigator.clipboard) {
16+
navigator.clipboard.writeText(tag).then(
17+
() => setToast(`Copied "${tag}"`),
18+
() => setToast(`Copy failed — paste manually: ${tag}`)
19+
);
20+
} else {
21+
const el = document.createElement("textarea");
22+
el.value = tag;
23+
el.style.cssText = "position:fixed;opacity:0";
24+
document.body.appendChild(el);
25+
el.select();
26+
const ok = document.execCommand("copy");
27+
document.body.removeChild(el);
28+
setToast(ok ? `Copied "${tag}"` : `Copy failed — paste manually: ${tag}`);
29+
}
2030
};
2131

2232
const toggleTags = (id) => {
@@ -50,7 +60,7 @@ export default function DiscoverPanel({ videos }) {
5060
type="text"
5161
placeholder="Search title, channel, or tags…"
5262
value={search}
53-
onChange={(e) => setSearch(e.target.value)}
63+
onChange={(e) => onSearch(e.target.value)}
5464
className="discover-search"
5565
/>
5666

0 commit comments

Comments
 (0)