This commit is contained in:
@@ -64,6 +64,15 @@
|
||||
:class="{ error: $store.torrents.error }"
|
||||
x-text="$store.torrents.message"></p>
|
||||
</div>
|
||||
<button class="torrent-modal-close"
|
||||
@click="$store.torrents.close()"
|
||||
title="{{ t.player_close }}"
|
||||
aria-label="{{ t.player_close }}">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"/>
|
||||
<line x1="6" y1="6" x2="18" y2="18"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="torrent-client-status">
|
||||
<span class="torrent-status-pill"
|
||||
:class="{ active: $store.torrents.activeCount() > 0 }"
|
||||
@@ -109,8 +118,6 @@
|
||||
:style="'width:' + $store.torrents.progressValue(job) + '%'"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="torrent-session-remove"
|
||||
@click.stop="$store.torrents.removeSession(job.id)">{{ t.player_delete }}</button>
|
||||
</div>
|
||||
</template>
|
||||
<button type="button"
|
||||
@@ -119,7 +126,7 @@
|
||||
@click="$store.torrents.addNew()"
|
||||
:disabled="$store.torrents.loading">
|
||||
<span class="torrent-session-add-icon">+</span>
|
||||
<span>{{ t.player_add_torrent }}</span>
|
||||
<span>{{ t.player_upload }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -135,9 +142,10 @@
|
||||
<div class="torrent-import-panel">
|
||||
<div class="torrent-modal-grid">
|
||||
<div>
|
||||
<label for="torrent-file-input">{{ t.player_torrent_file }}</label>
|
||||
<input id="torrent-file-input" type="file" accept=".torrent,application/x-bittorrent"
|
||||
@change="$store.torrents.file = $event.target.files[0] || null">
|
||||
<label for="local-file-input">{{ t.player_local_files }}</label>
|
||||
<input id="local-file-input" type="file" multiple accept="audio/*,.mp3,.flac,.wav,.m4a,.ogg,.opus,.aac"
|
||||
@change="$store.torrents.setLocalFiles($event.target.files)">
|
||||
<div class="torrent-upload-summary" x-text="$store.torrents.localUploadSummary()"></div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="torrent-magnet-input">{{ t.player_magnet_link }}</label>
|
||||
@@ -145,10 +153,26 @@
|
||||
x-model="$store.torrents.magnet"
|
||||
placeholder="magnet:?xt=urn:btih:...">
|
||||
</div>
|
||||
<div>
|
||||
<label for="torrent-file-input">{{ t.player_torrent_file }}</label>
|
||||
<input id="torrent-file-input" type="file" accept=".torrent,application/x-bittorrent"
|
||||
@change="$store.torrents.file = $event.target.files[0] || null">
|
||||
</div>
|
||||
</div>
|
||||
<div class="torrent-upload-progress"
|
||||
x-show="$store.torrents.uploadProgress > 0 || ($store.torrents.localFiles.length > 0 && $store.torrents.loading)">
|
||||
<div class="torrent-progress-head">
|
||||
<span x-text="$store.torrents.uploadProgress >= 100 ? T.uploadComplete : T.uploadingFiles"></span>
|
||||
<span x-text="$store.torrents.uploadProgressText"></span>
|
||||
</div>
|
||||
<div class="torrent-progress-track">
|
||||
<div class="torrent-progress-bar"
|
||||
:style="'width:' + $store.torrents.uploadProgress + '%'"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="torrent-actions">
|
||||
<button class="modal-btn modal-btn-primary" @click="$store.torrents.preview()" :disabled="$store.torrents.loading">
|
||||
{{ t.player_preview_content }}
|
||||
{{ t.player_upload_content }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -201,12 +225,19 @@
|
||||
<div class="torrent-preview-meta"
|
||||
x-text="$store.torrents.previewData.files.length + ' {{ t.player_files_count }} - ' + $store.torrents.bytes($store.torrents.previewData.total_size)"></div>
|
||||
</div>
|
||||
<button class="modal-btn"
|
||||
:class="$store.torrents.actionButtonClass()"
|
||||
@click="$store.torrents.toggleDownloadAction()"
|
||||
:disabled="$store.torrents.actionButtonDisabled()">
|
||||
<span x-text="$store.torrents.actionButtonText()"></span>
|
||||
</button>
|
||||
<div class="torrent-preview-actions">
|
||||
<button class="modal-btn"
|
||||
:class="$store.torrents.actionButtonClass()"
|
||||
@click="$store.torrents.toggleDownloadAction()"
|
||||
:disabled="$store.torrents.actionButtonDisabled()">
|
||||
<span x-text="$store.torrents.actionButtonText()"></span>
|
||||
</button>
|
||||
<button class="modal-btn modal-btn-danger"
|
||||
@click="$store.torrents.removeSession($store.torrents.previewData.id)"
|
||||
:disabled="$store.torrents.loading">
|
||||
{{ t.player_delete }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="torrent-tree-toolbar">
|
||||
<div class="torrent-selected-summary"
|
||||
|
||||
@@ -30,7 +30,11 @@ const T = {
|
||||
queued: "{{ t.player_queued }}",
|
||||
saved: "{{ t.player_saved }}",
|
||||
chooseSavedOrAddTorrent: "{{ t.player_choose_saved_or_add_torrent }}",
|
||||
uploadFailed: "{{ t.player_upload_failed }}",
|
||||
uploadComplete: "{{ t.player_upload_complete }}",
|
||||
uploadingFiles: "{{ t.player_uploading_files }}",
|
||||
preview: "{{ t.player_preview }}",
|
||||
resolving: "{{ t.player_resolving }}",
|
||||
downloading: "{{ t.player_downloading }}",
|
||||
moving: "{{ t.player_moving }}",
|
||||
completed: "{{ t.player_completed }}",
|
||||
@@ -1083,6 +1087,7 @@ document.addEventListener('alpine:init', () => {
|
||||
Alpine.store('torrents', {
|
||||
modal: false,
|
||||
file: null,
|
||||
localFiles: [],
|
||||
magnet: '',
|
||||
sessions: [],
|
||||
loadingSessions: false,
|
||||
@@ -1101,6 +1106,8 @@ document.addEventListener('alpine:init', () => {
|
||||
queuedTasks: 0,
|
||||
processingTasks: 0,
|
||||
loadingAgentStatus: false,
|
||||
uploadProgress: 0,
|
||||
uploadProgressText: '',
|
||||
|
||||
open() {
|
||||
this.modal = true;
|
||||
@@ -1132,7 +1139,10 @@ document.addEventListener('alpine:init', () => {
|
||||
this._stopPoll();
|
||||
this.workspaceMode = 'new';
|
||||
this.file = null;
|
||||
this.localFiles = [];
|
||||
this.magnet = '';
|
||||
this.uploadProgress = 0;
|
||||
this.uploadProgressText = '';
|
||||
this.currentJob = null;
|
||||
this.previewData = null;
|
||||
this.treeRoot = null;
|
||||
@@ -1208,6 +1218,7 @@ document.addEventListener('alpine:init', () => {
|
||||
statusLabel(job) {
|
||||
const labels = {
|
||||
preview: T.preview,
|
||||
resolving: T.resolving,
|
||||
downloading: T.downloading,
|
||||
moving: T.moving,
|
||||
completed: T.completed,
|
||||
@@ -1283,12 +1294,18 @@ document.addEventListener('alpine:init', () => {
|
||||
},
|
||||
|
||||
actionButtonText() {
|
||||
if (this.normalizedStatus(this.currentJob) === 'resolving') return T.resolving;
|
||||
if (this.isCurrentCompletedLocked()) return T.completed;
|
||||
return this.isCurrentDownloading() ? T.pauseDownload : T.downloadSelected;
|
||||
},
|
||||
|
||||
actionButtonDisabled() {
|
||||
return this.loading || this.isCurrentCompletedLocked();
|
||||
return this.loading
|
||||
|| this.isCurrentCompletedLocked()
|
||||
|| this.normalizedStatus(this.currentJob) === 'resolving'
|
||||
|| !this.previewData
|
||||
|| !Array.isArray(this.previewData.files)
|
||||
|| this.previewData.files.length === 0;
|
||||
},
|
||||
|
||||
toggleDownloadAction() {
|
||||
@@ -1336,7 +1353,7 @@ document.addEventListener('alpine:init', () => {
|
||||
_rememberJob(job) {
|
||||
if (!job || !job.id) return;
|
||||
const rest = this.sessions.filter(item => item.id !== job.id);
|
||||
this.sessions = [job, ...rest].sort((a, b) => String(b.updated_at || '').localeCompare(String(a.updated_at || '')));
|
||||
this.sessions = [job, ...rest].sort((a, b) => String(b.created_at || '').localeCompare(String(a.created_at || '')));
|
||||
if (this.currentJob && this.currentJob.id === job.id) this.currentJob = job;
|
||||
},
|
||||
|
||||
@@ -1359,7 +1376,10 @@ document.addEventListener('alpine:init', () => {
|
||||
const job = data.job || null;
|
||||
this.workspaceMode = 'session';
|
||||
this.file = null;
|
||||
this.localFiles = [];
|
||||
this.magnet = '';
|
||||
this.uploadProgress = 0;
|
||||
this.uploadProgressText = '';
|
||||
this.previewData = preview;
|
||||
this.currentJob = job;
|
||||
const selected = Array.isArray(data.selected_files) && data.selected_files.length
|
||||
@@ -1378,6 +1398,7 @@ document.addEventListener('alpine:init', () => {
|
||||
if (!res.ok) throw new Error(data.error || T.loadTorrentsFailed);
|
||||
this.sessions = Array.isArray(data) ? data : [];
|
||||
this._syncCurrentJobFromSessions();
|
||||
await this._refreshResolvedSelection();
|
||||
} catch (err) {
|
||||
this._setMessage(err.message || String(err), true);
|
||||
} finally {
|
||||
@@ -1385,6 +1406,19 @@ document.addEventListener('alpine:init', () => {
|
||||
}
|
||||
},
|
||||
|
||||
async _refreshResolvedSelection() {
|
||||
if (!this.currentJob || !this.previewData || (this.previewData.files || []).length > 0) return;
|
||||
const selected = this.sessions.find(job => job.id === this.currentJob.id);
|
||||
if (!selected || this.normalizedStatus(selected) === 'resolving') return;
|
||||
try {
|
||||
const res = await fetch(`/api/player/torrents/session/${selected.id}`);
|
||||
const data = await res.json();
|
||||
if (!res.ok) return;
|
||||
this._applySession(data);
|
||||
this._setMessage(T.allFilesSelected);
|
||||
} catch {}
|
||||
},
|
||||
|
||||
async openSession(id) {
|
||||
if (!id || this.loading) return;
|
||||
this._stopPoll();
|
||||
@@ -1442,8 +1476,76 @@ document.addEventListener('alpine:init', () => {
|
||||
});
|
||||
},
|
||||
|
||||
setLocalFiles(files) {
|
||||
this.localFiles = Array.from(files || []);
|
||||
},
|
||||
|
||||
localUploadBytes() {
|
||||
return this.localFiles.reduce((sum, file) => sum + Number(file.size || 0), 0);
|
||||
},
|
||||
|
||||
localUploadSummary() {
|
||||
const count = this.localFiles.length;
|
||||
if (count === 0) return '';
|
||||
return count + ' ' + T.selected + ' - ' + this.bytes(this.localUploadBytes());
|
||||
},
|
||||
|
||||
uploadLocalFile(file, loadedBefore, totalBytes) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/api/player/uploads/local');
|
||||
xhr.setRequestHeader('Content-Type', file.type || 'application/octet-stream');
|
||||
xhr.setRequestHeader('X-Furumusic-Filename', encodeURIComponent(file.name || 'upload.mp3'));
|
||||
xhr.upload.onprogress = event => {
|
||||
if (!event.lengthComputable || totalBytes <= 0) return;
|
||||
const loaded = loadedBefore + event.loaded;
|
||||
this.uploadProgress = Math.max(0, Math.min(100, loaded / totalBytes * 100));
|
||||
this.uploadProgressText = this.uploadProgress.toFixed(1) + '%';
|
||||
};
|
||||
xhr.onload = () => {
|
||||
let data = {};
|
||||
try { data = JSON.parse(xhr.responseText || '{}'); } catch {}
|
||||
if (xhr.status >= 200 && xhr.status < 300) resolve(data);
|
||||
else reject(new Error(data.error || T.uploadFailed));
|
||||
};
|
||||
xhr.onerror = () => reject(new Error(T.uploadFailed));
|
||||
xhr.send(file);
|
||||
});
|
||||
},
|
||||
|
||||
async uploadLocalFiles() {
|
||||
if (this.loading || this.localFiles.length === 0) return;
|
||||
this.loading = true;
|
||||
this.uploadProgress = 0;
|
||||
this.uploadProgressText = '0.0%';
|
||||
this._setMessage(T.uploadingFiles);
|
||||
const totalBytes = this.localUploadBytes();
|
||||
let loadedBefore = 0;
|
||||
try {
|
||||
for (const file of this.localFiles) {
|
||||
await this.uploadLocalFile(file, loadedBefore, totalBytes);
|
||||
loadedBefore += Number(file.size || 0);
|
||||
this.uploadProgress = totalBytes > 0 ? Math.min(100, loadedBefore / totalBytes * 100) : 100;
|
||||
this.uploadProgressText = this.uploadProgress.toFixed(1) + '%';
|
||||
}
|
||||
this.localFiles = [];
|
||||
this.uploadProgress = 100;
|
||||
this.uploadProgressText = '100.0%';
|
||||
this._setMessage(T.uploadComplete);
|
||||
await this.loadAgentStatus();
|
||||
} catch (err) {
|
||||
this._setMessage(err.message || String(err), true);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async preview() {
|
||||
if (this.loading) return;
|
||||
if (this.localFiles.length > 0) {
|
||||
await this.uploadLocalFiles();
|
||||
return;
|
||||
}
|
||||
const magnet = this.magnet.trim();
|
||||
if (!this.file && !magnet) {
|
||||
this._setMessage(T.chooseTorrent, true);
|
||||
@@ -1472,7 +1574,7 @@ document.addEventListener('alpine:init', () => {
|
||||
if (!res.ok) throw new Error(data.error || T.previewFailed);
|
||||
|
||||
this._applySession(data);
|
||||
this._setMessage(T.allFilesSelected);
|
||||
this._setMessage((data.preview?.files || []).length ? T.allFilesSelected : T.resolving);
|
||||
await this.loadSessions();
|
||||
} catch (err) {
|
||||
this._setMessage(err.message || String(err), true);
|
||||
|
||||
@@ -1741,6 +1741,11 @@ button.user-stat:hover {
|
||||
color: #111;
|
||||
}
|
||||
.modal-btn-ghost { background: transparent; color: var(--text-secondary); }
|
||||
.modal-btn-danger {
|
||||
background: rgba(229,96,96,0.16);
|
||||
color: #ffb9b9;
|
||||
border: 1px solid rgba(229,96,96,0.32);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
@@ -1749,9 +1754,10 @@ button.user-stat:hover {
|
||||
}
|
||||
|
||||
.torrent-modal {
|
||||
width: min(860px, calc(100vw - 32px));
|
||||
max-width: 860px;
|
||||
max-height: min(88dvh, 760px);
|
||||
width: min(1180px, calc(100vw - 48px));
|
||||
max-width: 1180px;
|
||||
height: min(820px, calc(100dvh - 64px));
|
||||
max-height: calc(100dvh - 64px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -1761,6 +1767,8 @@ button.user-stat:hover {
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
margin-bottom: 12px;
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.torrent-modal-head h3 {
|
||||
@@ -1792,6 +1800,25 @@ button.user-stat:hover {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.torrent-modal-close {
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 999px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.torrent-modal-close svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.torrent-status-pill.active {
|
||||
border-color: rgba(29,185,84,0.42);
|
||||
color: #9ff0b9;
|
||||
@@ -1816,9 +1843,10 @@ button.user-stat:hover {
|
||||
|
||||
.torrent-manager-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(210px, 260px) minmax(0, 1fr);
|
||||
grid-template-columns: minmax(260px, 320px) minmax(0, 1fr);
|
||||
gap: 14px;
|
||||
min-height: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.torrent-manager-sidebar,
|
||||
@@ -1851,13 +1879,14 @@ button.user-stat:hover {
|
||||
|
||||
.torrent-session-list {
|
||||
overflow-y: auto;
|
||||
min-height: 150px;
|
||||
max-height: min(52vh, 470px);
|
||||
min-height: 0;
|
||||
max-height: none;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.torrent-session-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
@@ -1935,19 +1964,28 @@ button.user-stat:hover {
|
||||
}
|
||||
|
||||
.torrent-status-badge.status-preview {
|
||||
background: rgba(122,162,255,0.14);
|
||||
color: #a8c0ff;
|
||||
background: rgba(122,162,255,0.16);
|
||||
color: #adc3ff;
|
||||
}
|
||||
|
||||
.torrent-status-badge.status-downloading,
|
||||
.torrent-status-badge.status-moving {
|
||||
.torrent-status-badge.status-resolving {
|
||||
background: rgba(182,141,255,0.16);
|
||||
color: #d0b6ff;
|
||||
}
|
||||
|
||||
.torrent-status-badge.status-downloading {
|
||||
background: rgba(29,185,84,0.16);
|
||||
color: #9ff0b9;
|
||||
}
|
||||
|
||||
.torrent-status-badge.status-moving {
|
||||
background: rgba(75,198,240,0.16);
|
||||
color: #a8e8ff;
|
||||
}
|
||||
|
||||
.torrent-status-badge.status-completed {
|
||||
background: rgba(105,214,161,0.2);
|
||||
color: #b8ffd2;
|
||||
background: rgba(110,211,123,0.16);
|
||||
color: #b8f7be;
|
||||
}
|
||||
|
||||
.torrent-status-badge.status-paused {
|
||||
@@ -1985,23 +2023,6 @@ button.user-stat:hover {
|
||||
transition: width 0.25s ease;
|
||||
}
|
||||
|
||||
.torrent-session-remove {
|
||||
align-self: flex-start;
|
||||
border: 1px solid rgba(229,96,96,0.24);
|
||||
background: rgba(229,96,96,0.12);
|
||||
color: #ffb9b9;
|
||||
border-radius: 5px;
|
||||
padding: 4px 7px;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.torrent-session-remove:hover {
|
||||
background: rgba(229,96,96,0.2);
|
||||
color: #ffd7d7;
|
||||
}
|
||||
|
||||
.torrent-progress-card {
|
||||
margin-top: 10px;
|
||||
padding: 10px 12px;
|
||||
@@ -2143,6 +2164,21 @@ button.user-stat:hover {
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.torrent-upload-summary {
|
||||
min-height: 16px;
|
||||
margin-top: 5px;
|
||||
color: var(--text-subdued);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.torrent-upload-progress {
|
||||
margin-top: 10px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.torrent-modal label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
@@ -2187,6 +2223,13 @@ button.user-stat:hover {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.torrent-preview-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.torrent-preview-title {
|
||||
min-width: 0;
|
||||
font-size: 14px;
|
||||
@@ -2243,7 +2286,7 @@ button.user-stat:hover {
|
||||
margin-top: 10px;
|
||||
overflow-y: auto;
|
||||
min-height: 140px;
|
||||
max-height: min(46vh, 420px);
|
||||
max-height: none;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-primary);
|
||||
@@ -2442,7 +2485,7 @@ button.user-stat:hover {
|
||||
}
|
||||
|
||||
.torrent-modal {
|
||||
width: calc(100vw - 24px);
|
||||
width: min(1180px, calc(100vw - 32px));
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
@@ -2786,34 +2829,49 @@ button.user-stat:hover {
|
||||
}
|
||||
|
||||
.info-modal,
|
||||
.torrent-modal,
|
||||
.history-modal {
|
||||
width: min(400px, calc(100vw - 24px));
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.torrent-modal {
|
||||
max-height: min(82dvh, 640px);
|
||||
padding: 20px;
|
||||
width: 100vw;
|
||||
max-width: none;
|
||||
height: 100dvh;
|
||||
max-height: none;
|
||||
border-radius: 0;
|
||||
padding: calc(14px + env(safe-area-inset-top)) 14px calc(14px + env(safe-area-inset-bottom));
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.torrent-modal-head {
|
||||
flex-direction: column;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 8px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.torrent-client-status {
|
||||
grid-column: 1 / -1;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.torrent-modal-close {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.torrent-manager-layout {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.torrent-session-list {
|
||||
max-height: 148px;
|
||||
min-height: 96px;
|
||||
max-height: none;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.torrent-manager-sidebar {
|
||||
flex: 0 0 178px;
|
||||
}
|
||||
|
||||
.torrent-progress-head {
|
||||
@@ -2867,8 +2925,8 @@ button.user-stat:hover {
|
||||
}
|
||||
|
||||
.torrent-file-tree {
|
||||
min-height: 120px;
|
||||
max-height: min(32dvh, 260px);
|
||||
min-height: 0;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.torrent-tree-row {
|
||||
|
||||
Reference in New Issue
Block a user