release: publish Multi-Debrid Downloader v2.0.14

Add live English and German localization, queue availability and metadata resolution, responsive package controls, polished navigation and drag interactions, clearer history and account states, and a rebuilt public README. Harden Windows packaging with verified icons and version metadata, archive inspection, and expanded release tests.
This commit is contained in:
Sucukdeluxe
2026-08-10 19:42:49 +02:00
parent 069babfd54
commit a5758aa905
61 changed files with 9117 additions and 6754 deletions
+205 -204
View File
@@ -1,208 +1,209 @@
import type { ChangeEvent, ReactElement } from "react";
import {
DataTable,
DataTableBody,
DataTableEmpty,
DataTableHeader
} from "../../ui/DataTable";
import { Dialog } from "../../ui/Dialog";
import type { ChangeEvent, ReactElement } from "react";
import {
DataTable,
DataTableBody,
DataTableEmpty,
DataTableHeader
} from "../../ui/DataTable";
import { Dialog } from "../../ui/Dialog";
import { Toolbar, ToolbarGroup, ToolbarSearch } from "../../ui/Toolbar";
import type { CollectorViewModel } from "./collector-model";
import "./collector.css";
export interface CollectorViewActions {
onTabSelect: (tabId: string) => void;
onTabAdd: () => void;
onTabRemove: (tabId: string) => void;
onOpenInput: () => void;
onImportDlc: () => void;
onImportFile: () => void;
onExportQueue: () => void;
onSubmit: () => void;
onQueryChange: (value: string) => void;
onSelectionChange: (rowId: string) => void;
onRemoveSelected: () => void;
}
export type CollectorViewRegion = "all" | "sidebar" | "toolbar" | "content";
export interface CollectorViewProps {
model: CollectorViewModel;
actions: CollectorViewActions;
region?: CollectorViewRegion;
}
export interface CollectorInputDialogProps {
open: boolean;
tabName: string;
value: string;
onChange: (value: string) => void;
onClose: () => void;
onCommit: () => void;
}
import { SlidingSelection } from "../../ui/SlidingSelection";
import type { CollectorViewModel } from "./collector-model";
import "./collector.css";
export interface CollectorViewActions {
onTabSelect: (tabId: string) => void;
onTabAdd: () => void;
onTabRemove: (tabId: string) => void;
onOpenInput: () => void;
onImportDlc: () => void;
onImportFile: () => void;
onExportQueue: () => void;
onSubmit: () => void;
onQueryChange: (value: string) => void;
onSelectionChange: (rowId: string) => void;
onRemoveSelected: () => void;
}
export type CollectorViewRegion = "all" | "sidebar" | "toolbar" | "content";
export interface CollectorViewProps {
model: CollectorViewModel;
actions: CollectorViewActions;
region?: CollectorViewRegion;
}
export interface CollectorInputDialogProps {
open: boolean;
tabName: string;
value: string;
onChange: (value: string) => void;
onClose: () => void;
onCommit: () => void;
}
export function CollectorSidebar({ model, actions }: CollectorViewProps): ReactElement {
return (
<div aria-label="Sammlungen" className="collector-sidebar" data-visual-region="collector-sidebar">
<div className="collector-sidebar-heading">
<strong>Sammlungen</strong>
<span>{model.tabs.length}</span>
</div>
<div className="collector-sidebar-list">
<div aria-label="Sammlungen" className="collector-sidebar" data-visual-region="collector-sidebar">
<div className="collector-sidebar-heading">
<strong>Sammlungen</strong>
<span>{model.tabs.length}</span>
</div>
<SlidingSelection activeKey={model.activeTabId} axis="vertical" className="collector-sidebar-list">
{model.tabs.map((tab) => (
<div className={`collector-sidebar-item${tab.id === model.activeTabId ? " is-active" : ""}`} key={tab.id}>
<button
aria-current={tab.id === model.activeTabId ? "page" : undefined}
className="collector-sidebar-select"
onClick={() => actions.onTabSelect(tab.id)}
type="button"
>
<span>{tab.name}</span>
<span className="collector-sidebar-count">{tab.linkCount}</span>
</button>
{model.tabs.length > 1 ? (
<button
aria-label={`${tab.name} entfernen`}
className="collector-sidebar-remove"
onClick={() => actions.onTabRemove(tab.id)}
type="button"
>×</button>
) : null}
</div>
))}
</div>
<button className="collector-sidebar-add" onClick={actions.onTabAdd} type="button">Neue Sammlung</button>
</div>
);
}
export function CollectorToolbar({ model, actions }: CollectorViewProps): ReactElement {
return (
<Toolbar className="collector-toolbar" data-visual-region="collector-toolbar" label="Linksammler-Aktionen">
<ToolbarGroup label="Links erfassen">
<button className="collector-action collector-action-primary" disabled={model.busy} onClick={actions.onOpenInput} type="button">Links hinzufügen</button>
<button className="collector-action" disabled={model.busy} onClick={actions.onImportDlc} type="button">DLC importieren</button>
<button className="collector-action" disabled={model.busy} onClick={actions.onImportFile} type="button">Datei importieren</button>
</ToolbarGroup>
<ToolbarGroup label="Sammlung verarbeiten">
<button className="collector-action" disabled={model.busy} onClick={actions.onExportQueue} type="button">Queue exportieren</button>
<button className="collector-action" disabled={model.busy || model.tabs.length === 0} onClick={actions.onSubmit} type="button">An Downloads übergeben</button>
<button className="collector-action collector-action-danger" disabled={model.busy || model.selectedIds.length === 0} onClick={actions.onRemoveSelected} type="button">Auswahl entfernen</button>
</ToolbarGroup>
<ToolbarSearch
label="Links durchsuchen"
onChange={(event) => actions.onQueryChange(event.target.value)}
placeholder="Links durchsuchen"
value={model.query}
/>
</Toolbar>
);
}
export function CollectorContent({ model, actions }: CollectorViewProps): ReactElement {
const selected = new Set(model.selectedIds);
return (
<section className="collector-content" aria-label="Gesammelte Links">
<DataTable className="collector-table" label="Gesammelte Links">
<DataTableHeader className="collector-table-header">
<div className="collector-table-header-row" role="row">
<span aria-label="Auswahl" className="collector-column-select" role="columnheader" />
<span role="columnheader">Sammlung</span>
<span role="columnheader">URL oder Rohzeile</span>
<span role="columnheader">Zeile</span>
<span role="columnheader">Status</span>
</div>
</DataTableHeader>
<DataTableBody className="collector-table-body" data-visual-region="collector-table-body">
{model.busy ? (
<DataTableEmpty title="Links werden verarbeitet" description="Die laufende Aktion wird abgeschlossen." />
) : model.error ? (
<DataTableEmpty className="collector-table-error" title={model.error} description="Die lokale Sammlung bleibt unverändert." />
) : model.empty ? (
<DataTableEmpty
data-visual-region="collector-empty-state"
description={model.query ? "Passe die Suche an oder lösche den Filter." : "Füge Links hinzu oder importiere eine vorhandene Liste."}
title={model.query ? "Keine passenden Links" : "Noch keine Links"}
/>
) : (
model.rows.map((row) => (
<div className={`collector-row${selected.has(row.id) ? " is-selected" : ""}`} key={row.id} role="row">
<span className="collector-column-select" role="cell">
<input
aria-label="Link auswählen"
checked={selected.has(row.id)}
onChange={() => actions.onSelectionChange(row.id)}
type="checkbox"
/>
</span>
<span className="collector-row-source" role="cell">{row.tabName}</span>
<span className="collector-row-value" role="cell" title={row.value}>{row.value}</span>
<span className="collector-row-line" role="cell">{row.lineNumber}</span>
<span className="collector-row-status" role="cell">Lokal</span>
</div>
))
)}
</DataTableBody>
</DataTable>
</section>
);
}
export function CollectorView({ model, actions, region = "all" }: CollectorViewProps): ReactElement {
if (region === "sidebar") {
return <CollectorSidebar actions={actions} model={model} />;
}
if (region === "toolbar") {
return <CollectorToolbar actions={actions} model={model} />;
}
if (region === "content") {
return <CollectorContent actions={actions} model={model} />;
}
return (
<div className="collector-view">
<CollectorSidebar actions={actions} model={model} />
<div className="collector-view-main">
<CollectorToolbar actions={actions} model={model} />
<CollectorContent actions={actions} model={model} />
</div>
</div>
);
}
export function CollectorInputDialog({
open,
tabName,
value,
onChange,
onClose,
onCommit
}: CollectorInputDialogProps): ReactElement | null {
return (
<Dialog
actions={(
<>
<button className="collector-dialog-secondary" onClick={onClose} type="button">Abbrechen</button>
<button className="collector-dialog-primary" onClick={onCommit} type="button">Übernehmen</button>
</>
)}
description={`Links für ${tabName} lokal erfassen.`}
onClose={onClose}
open={open}
size="wide"
title="Links hinzufügen"
>
<label className="collector-input-label">
<span>Links</span>
<textarea
aria-label="Links"
autoFocus
className="collector-input"
onChange={(event: ChangeEvent<HTMLTextAreaElement>) => onChange(event.target.value)}
placeholder="Eine URL oder Rohzeile pro Zeile"
rows={12}
value={value}
/>
</label>
</Dialog>
);
}
<div className={`collector-sidebar-item${tab.id === model.activeTabId ? " is-active" : ""}`} data-sliding-selection-active={tab.id === model.activeTabId} data-sliding-selection-item="true" key={tab.id}>
<button
aria-current={tab.id === model.activeTabId ? "page" : undefined}
className="collector-sidebar-select"
onClick={() => actions.onTabSelect(tab.id)}
type="button"
>
<span>{tab.name}</span>
<span className="collector-sidebar-count">{tab.linkCount}</span>
</button>
{model.tabs.length > 1 ? (
<button
aria-label={`${tab.name} entfernen`}
className="collector-sidebar-remove"
onClick={() => actions.onTabRemove(tab.id)}
type="button"
>×</button>
) : null}
</div>
))}
</SlidingSelection>
<button className="collector-sidebar-add" onClick={actions.onTabAdd} type="button">Neue Sammlung</button>
</div>
);
}
export function CollectorToolbar({ model, actions }: CollectorViewProps): ReactElement {
return (
<Toolbar className="collector-toolbar" data-visual-region="collector-toolbar" label="Linksammler-Aktionen">
<ToolbarGroup label="Links erfassen">
<button className="collector-action collector-action-primary" disabled={model.busy} onClick={actions.onOpenInput} type="button">Links hinzufügen</button>
<button className="collector-action" disabled={model.busy} onClick={actions.onImportDlc} type="button">DLC importieren</button>
<button className="collector-action" disabled={model.busy} onClick={actions.onImportFile} type="button">Datei importieren</button>
</ToolbarGroup>
<ToolbarGroup label="Sammlung verarbeiten">
<button className="collector-action" disabled={model.busy} onClick={actions.onExportQueue} type="button">Queue exportieren</button>
<button className="collector-action" disabled={model.busy || model.tabs.length === 0} onClick={actions.onSubmit} type="button">An Downloads übergeben</button>
<button className="collector-action collector-action-danger" disabled={model.busy || model.selectedIds.length === 0} onClick={actions.onRemoveSelected} type="button">Auswahl entfernen</button>
</ToolbarGroup>
<ToolbarSearch
label="Links durchsuchen"
onChange={(event) => actions.onQueryChange(event.target.value)}
placeholder="Links durchsuchen"
value={model.query}
/>
</Toolbar>
);
}
export function CollectorContent({ model, actions }: CollectorViewProps): ReactElement {
const selected = new Set(model.selectedIds);
return (
<section className="collector-content" aria-label="Gesammelte Links">
<DataTable className="collector-table" label="Gesammelte Links">
<DataTableHeader className="collector-table-header">
<div className="collector-table-header-row" role="row">
<span aria-label="Auswahl" className="collector-column-select" role="columnheader" />
<span role="columnheader">Sammlung</span>
<span role="columnheader">URL oder Rohzeile</span>
<span role="columnheader">Zeile</span>
<span role="columnheader">Status</span>
</div>
</DataTableHeader>
<DataTableBody className="collector-table-body" data-visual-region="collector-table-body">
{model.busy ? (
<DataTableEmpty title="Links werden verarbeitet" description="Die laufende Aktion wird abgeschlossen." />
) : model.error ? (
<DataTableEmpty className="collector-table-error" title={model.error} description="Die lokale Sammlung bleibt unverändert." />
) : model.empty ? (
<DataTableEmpty
data-visual-region="collector-empty-state"
description={model.query ? "Passe die Suche an oder lösche den Filter." : "Füge Links hinzu oder importiere eine vorhandene Liste."}
title={model.query ? "Keine passenden Links" : "Noch keine Links"}
/>
) : (
model.rows.map((row) => (
<div className={`collector-row${selected.has(row.id) ? " is-selected" : ""}`} key={row.id} role="row">
<span className="collector-column-select" role="cell">
<input
aria-label="Link auswählen"
checked={selected.has(row.id)}
onChange={() => actions.onSelectionChange(row.id)}
type="checkbox"
/>
</span>
<span className="collector-row-source" role="cell">{row.tabName}</span>
<span className="collector-row-value" role="cell" title={row.value}>{row.value}</span>
<span className="collector-row-line" role="cell">{row.lineNumber}</span>
<span className="collector-row-status" role="cell">Lokal</span>
</div>
))
)}
</DataTableBody>
</DataTable>
</section>
);
}
export function CollectorView({ model, actions, region = "all" }: CollectorViewProps): ReactElement {
if (region === "sidebar") {
return <CollectorSidebar actions={actions} model={model} />;
}
if (region === "toolbar") {
return <CollectorToolbar actions={actions} model={model} />;
}
if (region === "content") {
return <CollectorContent actions={actions} model={model} />;
}
return (
<div className="collector-view">
<CollectorSidebar actions={actions} model={model} />
<div className="collector-view-main">
<CollectorToolbar actions={actions} model={model} />
<CollectorContent actions={actions} model={model} />
</div>
</div>
);
}
export function CollectorInputDialog({
open,
tabName,
value,
onChange,
onClose,
onCommit
}: CollectorInputDialogProps): ReactElement | null {
return (
<Dialog
actions={(
<>
<button className="collector-dialog-secondary" onClick={onClose} type="button">Abbrechen</button>
<button className="collector-dialog-primary" onClick={onCommit} type="button">Übernehmen</button>
</>
)}
description={`Links für ${tabName} lokal erfassen.`}
onClose={onClose}
open={open}
size="wide"
title="Links hinzufügen"
>
<label className="collector-input-label">
<span>Links</span>
<textarea
aria-label="Links"
autoFocus
className="collector-input"
onChange={(event: ChangeEvent<HTMLTextAreaElement>) => onChange(event.target.value)}
placeholder="Eine URL oder Rohzeile pro Zeile"
rows={12}
value={value}
/>
</label>
</Dialog>
);
}
+306 -306
View File
@@ -1,309 +1,309 @@
.collector-view {
display: grid;
grid-template-columns: 270px minmax(0, 1fr);
min-width: 0;
min-height: 520px;
overflow: hidden;
}
.collector-view-main {
display: grid;
grid-template-rows: auto minmax(0, 1fr);
min-width: 0;
}
.collector-sidebar {
display: flex;
flex-direction: column;
gap: 8px;
min-width: 0;
padding: 12px;
}
.collector-sidebar-heading {
align-items: center;
color: var(--ui-text-secondary);
display: flex;
font-size: 13px;
justify-content: space-between;
min-height: 28px;
}
.collector-sidebar-heading span,
.collector-sidebar-count {
color: var(--ui-text-muted);
font-variant-numeric: tabular-nums;
}
.collector-sidebar-list {
display: flex;
flex: 1;
flex-direction: column;
gap: 4px;
min-height: 0;
overflow-y: auto;
}
.collector-sidebar-item {
align-items: stretch;
border: 1px solid transparent;
border-radius: 6px;
display: flex;
min-height: 36px;
}
.collector-sidebar-item:hover {
background: var(--ui-hover);
}
.collector-view {
display: grid;
grid-template-columns: 270px minmax(0, 1fr);
min-width: 0;
min-height: 520px;
overflow: hidden;
}
.collector-view-main {
display: grid;
grid-template-rows: auto minmax(0, 1fr);
min-width: 0;
}
.collector-sidebar {
display: flex;
flex-direction: column;
gap: 8px;
min-width: 0;
padding: 12px;
}
.collector-sidebar-heading {
align-items: center;
color: var(--ui-text-secondary);
display: flex;
font-size: 13px;
justify-content: space-between;
min-height: 28px;
}
.collector-sidebar-heading span,
.collector-sidebar-count {
color: var(--ui-text-muted);
font-variant-numeric: tabular-nums;
}
.collector-sidebar-list {
display: flex;
flex: 1;
flex-direction: column;
gap: 4px;
min-height: 0;
overflow-y: auto;
}
.collector-sidebar-item {
align-items: stretch;
border: 1px solid transparent;
border-radius: 6px;
display: flex;
min-height: 36px;
}
.collector-sidebar-item:hover {
background: var(--ui-hover);
}
.collector-sidebar-item.is-active {
background: var(--ui-active);
border-color: var(--ui-border);
}
.collector-sidebar-select,
.collector-sidebar-remove,
.collector-sidebar-add {
background: transparent;
border: 0;
color: var(--ui-text-secondary);
font: inherit;
}
.collector-sidebar-select {
align-items: center;
display: flex;
flex: 1;
gap: 8px;
justify-content: space-between;
min-width: 0;
padding: 0 8px;
text-align: left;
}
.collector-sidebar-select span:first-child {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.collector-sidebar-remove {
border-left: 1px solid var(--ui-border);
min-width: 32px;
}
.collector-sidebar-add {
border: 1px solid var(--ui-border);
border-radius: 6px;
min-height: 36px;
padding: 0 10px;
text-align: left;
}
.collector-sidebar-add:hover,
.collector-sidebar-remove:hover {
background: var(--ui-hover);
color: var(--ui-text);
}
.collector-toolbar {
min-width: 0;
width: 100%;
}
.collector-action {
background: var(--ui-input);
border: 1px solid var(--ui-border);
border-radius: 6px;
color: var(--ui-text-secondary);
font: inherit;
height: 36px;
padding: 0 12px;
white-space: nowrap;
}
.collector-action:hover:not(:disabled) {
background: var(--ui-hover);
color: var(--ui-text);
}
.collector-action-primary {
background: var(--ui-primary);
border-color: var(--ui-primary);
color: #181A1F;
}
.collector-action-primary:hover:not(:disabled) {
background: var(--ui-primary-hover);
color: #181A1F;
}
.collector-action-danger:not(:disabled) {
border-color: color-mix(in srgb, var(--ui-danger) 70%, var(--ui-border));
color: var(--ui-danger);
}
.collector-action:disabled {
cursor: default;
opacity: 0.45;
}
.collector-content {
display: flex;
height: 100%;
min-height: 0;
min-width: 0;
overflow: hidden;
}
.collector-table {
height: 100%;
}
.collector-table-header-row,
.collector-row {
align-items: center;
display: grid;
grid-template-columns: 48px minmax(140px, 0.8fr) minmax(320px, 3fr) 90px 100px;
min-width: 760px;
}
.collector-table-header {
height: 41px;
}
.collector-table-header-row {
color: var(--ui-text-muted);
font-size: 11px;
font-weight: 700;
height: 41px;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.collector-table-header-row > span,
.collector-row > span {
min-width: 0;
padding: 0 12px;
}
.collector-table-body {
overflow: auto;
}
.collector-row {
border-bottom: 1px solid var(--ui-border);
color: var(--ui-text-secondary);
height: 48px;
}
.collector-row:hover {
background: var(--ui-hover);
}
.collector-row.is-selected {
background: var(--ui-active);
}
.collector-column-select {
display: grid;
place-items: center;
}
.collector-row-source {
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.collector-row-value {
color: var(--ui-text);
overflow: hidden;
text-overflow: ellipsis;
user-select: text;
white-space: nowrap;
}
.collector-row-line,
.collector-row-status {
color: var(--ui-text-muted);
font-variant-numeric: tabular-nums;
}
.collector-table-error .ui-data-table-empty-title {
color: var(--ui-danger);
}
.collector-input-label {
display: grid;
gap: 8px;
}
.collector-input-label > span {
color: var(--ui-text-secondary);
font-size: 13px;
font-weight: 600;
}
.collector-input {
background: var(--ui-input);
border: 1px solid var(--ui-border);
border-radius: 6px;
color: var(--ui-text);
font: inherit;
line-height: 1.5;
min-height: 240px;
padding: 12px;
resize: vertical;
user-select: text;
width: 100%;
}
.collector-dialog-primary,
.collector-dialog-secondary {
border: 1px solid var(--ui-border);
border-radius: 6px;
font: inherit;
height: 36px;
padding: 0 14px;
}
.collector-dialog-primary {
background: var(--ui-primary);
border-color: var(--ui-primary);
color: #181A1F;
}
.collector-dialog-secondary {
background: var(--ui-modal-secondary);
color: var(--ui-text-secondary);
}
@media (max-width: 1366px) {
.collector-view {
grid-template-columns: 56px minmax(0, 1fr);
}
.collector-sidebar {
overflow: hidden;
}
.collector-action {
padding: 0 9px;
}
.collector-table-header-row,
.collector-row {
grid-template-columns: 44px minmax(120px, 0.7fr) minmax(280px, 2.4fr) 70px 86px;
min-width: 660px;
}
}
@media (max-width: 1120px) {
.collector-table-header-row,
.collector-row {
grid-template-columns: 44px minmax(108px, 0.7fr) minmax(250px, 2.2fr) 66px 82px;
min-width: 610px;
}
border-color: transparent;
}
.collector-sidebar-select,
.collector-sidebar-remove,
.collector-sidebar-add {
background: transparent;
border: 0;
color: var(--ui-text-secondary);
font: inherit;
}
.collector-sidebar-select {
align-items: center;
display: flex;
flex: 1;
gap: 8px;
justify-content: space-between;
min-width: 0;
padding: 0 8px;
text-align: left;
}
.collector-sidebar-select span:first-child {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.collector-sidebar-remove {
border-left: 1px solid var(--ui-border);
min-width: 32px;
}
.collector-sidebar-add {
border: 1px solid var(--ui-border);
border-radius: 6px;
min-height: 36px;
padding: 0 10px;
text-align: left;
}
.collector-sidebar-add:hover,
.collector-sidebar-remove:hover {
background: var(--ui-hover);
color: var(--ui-text);
}
.collector-toolbar {
min-width: 0;
width: 100%;
}
.collector-action {
background: var(--ui-input);
border: 1px solid var(--ui-border);
border-radius: 6px;
color: var(--ui-text-secondary);
font: inherit;
height: 36px;
padding: 0 12px;
white-space: nowrap;
}
.collector-action:hover:not(:disabled) {
background: var(--ui-hover);
color: var(--ui-text);
}
.collector-action-primary {
background: var(--ui-primary);
border-color: var(--ui-primary);
color: #181A1F;
}
.collector-action-primary:hover:not(:disabled) {
background: var(--ui-primary-hover);
color: #181A1F;
}
.collector-action-danger:not(:disabled) {
border-color: color-mix(in srgb, var(--ui-danger) 70%, var(--ui-border));
color: var(--ui-danger);
}
.collector-action:disabled {
cursor: default;
opacity: 0.45;
}
.collector-content {
display: flex;
height: 100%;
min-height: 0;
min-width: 0;
overflow: hidden;
}
.collector-table {
height: 100%;
}
.collector-table-header-row,
.collector-row {
align-items: center;
display: grid;
grid-template-columns: 48px minmax(140px, 0.8fr) minmax(320px, 3fr) 90px 100px;
min-width: 760px;
}
.collector-table-header {
height: 41px;
}
.collector-table-header-row {
color: var(--ui-text-muted);
font-size: 11px;
font-weight: 700;
height: 41px;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.collector-table-header-row > span,
.collector-row > span {
min-width: 0;
padding: 0 12px;
}
.collector-table-body {
overflow: auto;
}
.collector-row {
border-bottom: 1px solid var(--ui-border);
color: var(--ui-text-secondary);
height: 48px;
}
.collector-row:hover {
background: var(--ui-hover);
}
.collector-row.is-selected {
background: var(--ui-active);
}
.collector-column-select {
display: grid;
place-items: center;
}
.collector-row-source {
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.collector-row-value {
color: var(--ui-text);
overflow: hidden;
text-overflow: ellipsis;
user-select: text;
white-space: nowrap;
}
.collector-row-line,
.collector-row-status {
color: var(--ui-text-muted);
font-variant-numeric: tabular-nums;
}
.collector-table-error .ui-data-table-empty-title {
color: var(--ui-danger);
}
.collector-input-label {
display: grid;
gap: 8px;
}
.collector-input-label > span {
color: var(--ui-text-secondary);
font-size: 13px;
font-weight: 600;
}
.collector-input {
background: var(--ui-input);
border: 1px solid var(--ui-border);
border-radius: 6px;
color: var(--ui-text);
font: inherit;
line-height: 1.5;
min-height: 240px;
padding: 12px;
resize: vertical;
user-select: text;
width: 100%;
}
.collector-dialog-primary,
.collector-dialog-secondary {
border: 1px solid var(--ui-border);
border-radius: 6px;
font: inherit;
height: 36px;
padding: 0 14px;
}
.collector-dialog-primary {
background: var(--ui-primary);
border-color: var(--ui-primary);
color: #181A1F;
}
.collector-dialog-secondary {
background: var(--ui-modal-secondary);
color: var(--ui-text-secondary);
}
@media (max-width: 1366px) {
.collector-view {
grid-template-columns: 56px minmax(0, 1fr);
}
.collector-sidebar {
overflow: hidden;
}
.collector-action {
padding: 0 9px;
}
.collector-table-header-row,
.collector-row {
grid-template-columns: 44px minmax(120px, 0.7fr) minmax(280px, 2.4fr) 70px 86px;
min-width: 660px;
}
}
@media (max-width: 1120px) {
.collector-table-header-row,
.collector-row {
grid-template-columns: 44px minmax(108px, 0.7fr) minmax(250px, 2.2fr) 66px 82px;
min-width: 610px;
}
}