feat(web): storage admin, search and catalog pages (M2)
ci / ts / ts (packages + web) (push) Successful in 1m56s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m59s

- Storage admin: local folders with a folder picker and NFS sources,
  connection test, scan and full scan with live progress, enable,
  disable, delete
- path pattern editor with reorder, examples and a preview table that
  shows which pattern matched and what came from tags; compile errors
  land under the broken pattern
- Library page with counters, scans and unreadable files; search
  reindex for admins
- search box in the top bar: combobox with keyboard navigation, 150 ms
  debounce, cancelled stale requests, "/" and Ctrl+K, layout fix hint
- search page with tabs and paging, artist and album pages, album
  cards and track lists; unavailable tracks are dimmed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Opus 5.5 committed 2026-10-20 07:01:52 +03:00
1 parent c1160cccd3
commit a685b1b7ca
27 files changed
+2505 -9

No files matched your search

+301
View File
@@ -0,0 +1,301 @@
.list {
display: flex;
flex-direction: column;
gap: 14px;
}
.card {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 14px 20px;
padding: 20px 22px;
border-radius: var(--radius-lg);
background: var(--surface-a);
border: 1px solid var(--line);
}
.cardHead {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.cardTitle {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
font-size: 17px;
font-weight: 600;
}
.location {
font-family: var(--font-mono);
font-size: 13px;
color: var(--text2);
overflow-wrap: anywhere;
}
.message {
font-size: 13px;
color: var(--danger);
}
.cardActions {
display: flex;
align-items: flex-start;
gap: 8px;
flex-wrap: wrap;
justify-content: flex-end;
}
.cardFoot {
grid-column: 1 / -1;
display: flex;
flex-direction: column;
gap: 6px;
}
.typeBadge {
display: inline-flex;
align-items: center;
height: 22px;
padding: 0 9px;
border-radius: var(--radius-pill);
background: var(--surface3);
color: var(--text2);
font-size: 12px;
font-weight: 600;
}
.status {
display: inline-flex;
align-items: center;
gap: 7px;
font-size: 13px;
font-weight: 600;
}
.status::before {
content: '';
width: 7px;
height: 7px;
border-radius: 50%;
background: currentColor;
}
.st_mounted {
color: var(--ok);
}
.st_error {
color: var(--danger);
}
.st_pending {
color: var(--warn);
}
.st_disabled {
color: var(--text3);
}
.scan {
display: flex;
flex-direction: column;
gap: 6px;
}
.scanText,
.scanSummary {
font-size: 13px;
color: var(--text2);
}
.scanError {
font-size: 13px;
color: var(--danger);
}
.bar {
height: 6px;
border-radius: 3px;
background: var(--surface3);
overflow: hidden;
}
.bar span {
display: block;
height: 100%;
background: linear-gradient(90deg, var(--accent), var(--violet));
transition: width 0.4s;
}
.test {
font-size: 13px;
}
.testOk {
color: var(--ok);
}
.testFail {
color: var(--danger);
}
.picker {
display: flex;
flex-direction: column;
gap: 6px;
max-height: 240px;
overflow-y: auto;
padding: 6px;
border-radius: var(--radius-md);
background: var(--surface2);
border: 1px solid var(--line2);
}
.crumbs {
display: flex;
align-items: center;
gap: 4px;
flex-wrap: wrap;
padding: 4px 6px;
font-family: var(--font-mono);
font-size: 13px;
color: var(--text2);
}
.dir {
display: flex;
align-items: center;
gap: 8px;
height: 34px;
padding: 0 8px;
border-radius: 8px;
text-align: left;
font-size: 14px;
}
.dir:hover {
background: var(--hover);
}
.dirIcon {
color: var(--accent2);
}
.label {
font-size: 13px;
font-weight: 600;
color: var(--text2);
}
.field {
display: flex;
flex-direction: column;
gap: 6px;
}
.tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 12px;
}
.tile {
display: flex;
flex-direction: column;
gap: 4px;
padding: 16px 18px;
border-radius: var(--radius-lg);
background: var(--surface-a);
border: 1px solid var(--line);
}
.tileValue {
font-family: var(--font-display);
font-size: 26px;
font-weight: 600;
font-variant-numeric: tabular-nums;
}
.tileLabel {
font-size: 13px;
color: var(--text2);
}
.patternRow {
display: grid;
grid-template-columns: 28px minmax(0, 1fr) auto;
align-items: start;
gap: 10px;
}
.patternNum {
height: 48px;
margin-top: 22px;
display: grid;
place-items: center;
color: var(--text3);
font-variant-numeric: tabular-nums;
}
.patternTools {
display: flex;
align-items: center;
gap: 2px;
height: 48px;
}
.check {
display: inline-flex;
align-items: center;
gap: 6px;
margin-right: 6px;
font-size: 13px;
color: var(--text2);
}
.groups {
font-family: var(--font-mono);
font-size: 12px;
color: var(--text3);
}
.match {
font-family: var(--font-mono);
font-size: 12px;
overflow-wrap: anywhere;
}
.noMatch {
color: var(--warn);
}
.result {
display: flex;
flex-direction: column;
gap: 2px;
font-size: 13px;
}
.result b {
font-weight: 600;
}
.fromTags {
color: var(--text3);
font-size: 12px;
}
@media (max-width: 767px) {
.card {
grid-template-columns: 1fr;
}
.cardActions {
justify-content: flex-start;
}
}