mirror of
https://github.com/plexguide/Huntarr.io.git
synced 2026-04-20 04:36:51 -04:00
851 lines
33 KiB
HTML
851 lines
33 KiB
HTML
<section id="settingsSizesSection" class="content-section" style="display: none;">
|
|
<div id="sizesManagementContainer" class="app-content-panel">
|
|
<div id="settings-sizes-no-indexers" class="settings-no-indexers" style="display: none;">
|
|
<i class="fas fa-satellite-dish" aria-hidden="true"></i>
|
|
<p class="no-instances-title">No indexers configured</p>
|
|
<p class="no-instances-desc">Configure at least one indexer in the Index Master Pool to use this section.</p>
|
|
<a href="./#indexer-hunt" class="no-instances-action-btn"><i class="fas fa-plus"></i> Add Indexer</a>
|
|
</div>
|
|
<div id="settings-sizes-no-clients" class="settings-no-clients" style="display: none;">
|
|
<i class="fas fa-download" aria-hidden="true"></i>
|
|
<p class="no-instances-title">No clients configured</p>
|
|
<p class="no-instances-desc">Configure at least one download client in Settings → Clients to use this section.</p>
|
|
<a href="./#settings-clients" class="no-instances-action-btn"><i class="fas fa-cog"></i> Configure Clients</a>
|
|
</div>
|
|
<div id="settings-sizes-content-wrapper" style="display: none;">
|
|
{% from 'components/page_header_partial.html' import page_header %}
|
|
{{ page_header(back_href='./#media-hunt-settings', parent_icon='fas fa-weight-hanging', parent_name='Media Hunt', current_name='Sizes', save_button_id='sizes-save-btn') }}
|
|
<div class="media-hunt-settings-instance-bar" style="margin-bottom: 16px;">
|
|
<select id="settings-sizes-instance-select" class="control-select" aria-label="Instance">
|
|
<option value="">Loading...</option>
|
|
</select>
|
|
</div>
|
|
<div class="settings-group sizes-settings-group">
|
|
<div class="sizes-header">
|
|
<div class="sizes-header-left">
|
|
<h3><i class="fas fa-weight-hanging"></i> Sizes</h3>
|
|
<p class="sizes-subtitle">Set file size limits per quality. Values are in <strong>MB per minute</strong> of runtime. Limits are automatically adjusted for the media length.</p>
|
|
</div>
|
|
<div class="sizes-header-actions">
|
|
<button type="button" class="btn-sizes-reset" id="sizes-reset-btn" title="Reset all to defaults">
|
|
<i class="fas fa-undo"></i> Reset Defaults
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="sizes-quality-list" class="sizes-quality-list">
|
|
<!-- Populated by JS -->
|
|
<div class="sizes-loading"><i class="fas fa-spinner fa-spin"></i> Loading...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<style>
|
|
#settingsSizesSection {
|
|
width: 100%;
|
|
min-height: 100vh;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
}
|
|
#settingsSizesSection.active {
|
|
display: block !important;
|
|
}
|
|
#sizesManagementContainer {
|
|
width: 100%;
|
|
padding: 20px;
|
|
margin: 0;
|
|
background-color: transparent;
|
|
box-shadow: none;
|
|
border: none;
|
|
}
|
|
.sizes-settings-group {
|
|
background: rgba(15, 23, 42, 0.4);
|
|
border: 1px solid rgba(148, 163, 184, 0.08);
|
|
border-radius: 12px;
|
|
padding: 24px;
|
|
margin: 12px 0 20px 0;
|
|
}
|
|
.sizes-header {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
margin-bottom: 24px;
|
|
padding-bottom: 16px;
|
|
border-bottom: 1px solid rgba(148, 163, 184, 0.12);
|
|
flex-wrap: wrap;
|
|
}
|
|
.sizes-header-left h3 {
|
|
margin: 0 0 6px 0;
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
color: #f1f5f9;
|
|
background: none;
|
|
box-shadow: none;
|
|
border: none;
|
|
padding: 0;
|
|
}
|
|
.sizes-header-left h3 i {
|
|
margin-right: 8px;
|
|
color: #6366f1;
|
|
}
|
|
.sizes-subtitle {
|
|
margin: 0;
|
|
font-size: 0.82rem;
|
|
color: #94a3b8;
|
|
max-width: 600px;
|
|
line-height: 1.4;
|
|
}
|
|
.sizes-header-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
align-items: center;
|
|
flex-shrink: 0;
|
|
}
|
|
.btn-sizes-reset {
|
|
background: rgba(71, 85, 105, 0.4);
|
|
border: 1px solid rgba(148, 163, 184, 0.2);
|
|
color: #94a3b8;
|
|
padding: 8px 14px;
|
|
border-radius: 8px;
|
|
font-size: 0.82rem;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
.btn-sizes-reset:hover {
|
|
background: rgba(71, 85, 105, 0.6);
|
|
color: #f1f5f9;
|
|
}
|
|
/* Group headers */
|
|
.sizes-group-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
margin: 20px 0 10px 0;
|
|
padding: 0;
|
|
}
|
|
.sizes-group-header:first-child {
|
|
margin-top: 0;
|
|
}
|
|
.sizes-group-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 5px 12px;
|
|
border-radius: 6px;
|
|
font-size: 0.78rem;
|
|
font-weight: 600;
|
|
letter-spacing: 0.02em;
|
|
text-transform: uppercase;
|
|
color: #f1f5f9;
|
|
}
|
|
.sizes-group-badge-low { background: rgba(100, 116, 139, 0.4); color: #94a3b8; }
|
|
.sizes-group-badge-sd { background: rgba(234, 179, 8, 0.2); color: #fbbf24; }
|
|
.sizes-group-badge-720 { background: rgba(34, 197, 94, 0.2); color: #4ade80; }
|
|
.sizes-group-badge-1080 { background: rgba(99, 102, 241, 0.2); color: #a5b4fc; }
|
|
.sizes-group-badge-2160 { background: rgba(168, 85, 247, 0.2); color: #c084fc; }
|
|
.sizes-group-badge-ultra { background: rgba(236, 72, 153, 0.2); color: #f472b6; }
|
|
.sizes-group-line {
|
|
flex: 1;
|
|
height: 1px;
|
|
background: rgba(148, 163, 184, 0.1);
|
|
}
|
|
|
|
/* Quality rows */
|
|
.sizes-quality-row {
|
|
display: grid;
|
|
grid-template-columns: 140px 1fr;
|
|
align-items: center;
|
|
gap: 16px;
|
|
padding: 10px 14px;
|
|
border-radius: 8px;
|
|
margin-bottom: 4px;
|
|
transition: background 0.15s;
|
|
}
|
|
.sizes-quality-row:hover {
|
|
background: rgba(30, 41, 59, 0.5);
|
|
}
|
|
.sizes-quality-name {
|
|
font-size: 0.88rem;
|
|
font-weight: 500;
|
|
color: #e2e8f0;
|
|
white-space: nowrap;
|
|
}
|
|
.sizes-slider-group {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
width: 100%;
|
|
}
|
|
.sizes-input-pair {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
flex-shrink: 0;
|
|
}
|
|
.sizes-input-label {
|
|
font-size: 0.7rem;
|
|
color: #64748b;
|
|
text-transform: uppercase;
|
|
font-weight: 600;
|
|
letter-spacing: 0.04em;
|
|
width: 26px;
|
|
text-align: right;
|
|
}
|
|
.sizes-input {
|
|
width: 60px;
|
|
padding: 5px 6px;
|
|
font-size: 0.82rem;
|
|
color: #f1f5f9;
|
|
background: rgba(15, 23, 42, 0.6);
|
|
border: 1px solid rgba(148, 163, 184, 0.2);
|
|
border-radius: 6px;
|
|
text-align: center;
|
|
-moz-appearance: textfield;
|
|
}
|
|
.sizes-input::-webkit-outer-spin-button,
|
|
.sizes-input::-webkit-inner-spin-button {
|
|
-webkit-appearance: none;
|
|
margin: 0;
|
|
}
|
|
.sizes-input:focus {
|
|
outline: none;
|
|
border-color: #6366f1;
|
|
}
|
|
|
|
/* Range track - the visual bar between min and max */
|
|
.sizes-range-container {
|
|
flex: 1;
|
|
position: relative;
|
|
height: 28px;
|
|
min-width: 120px;
|
|
}
|
|
.sizes-range-track {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 0;
|
|
right: 0;
|
|
height: 6px;
|
|
transform: translateY(-50%);
|
|
background: rgba(51, 65, 85, 0.6);
|
|
border-radius: 3px;
|
|
overflow: hidden;
|
|
}
|
|
.sizes-range-fill {
|
|
position: absolute;
|
|
top: 0;
|
|
height: 100%;
|
|
border-radius: 3px;
|
|
transition: left 0.1s, width 0.1s;
|
|
}
|
|
.sizes-range-fill-active {
|
|
background: linear-gradient(90deg, #6366f1, #818cf8);
|
|
}
|
|
.sizes-range-fill-preferred {
|
|
position: absolute;
|
|
top: 0;
|
|
width: 3px;
|
|
height: 100%;
|
|
background: #fbbf24;
|
|
border-radius: 2px;
|
|
transition: left 0.1s;
|
|
z-index: 2;
|
|
}
|
|
/* Thumb markers */
|
|
.sizes-range-thumb {
|
|
position: absolute;
|
|
top: 50%;
|
|
width: 14px;
|
|
height: 14px;
|
|
border-radius: 50%;
|
|
transform: translate(-50%, -50%);
|
|
cursor: grab;
|
|
z-index: 3;
|
|
transition: left 0.1s, box-shadow 0.15s;
|
|
border: 2px solid;
|
|
}
|
|
.sizes-range-thumb:active {
|
|
cursor: grabbing;
|
|
}
|
|
.sizes-range-thumb-min {
|
|
background: #1e293b;
|
|
border-color: #6366f1;
|
|
}
|
|
.sizes-range-thumb-min:hover {
|
|
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.25);
|
|
}
|
|
.sizes-range-thumb-preferred {
|
|
background: #fbbf24;
|
|
border-color: #f59e0b;
|
|
width: 12px;
|
|
height: 12px;
|
|
z-index: 4;
|
|
}
|
|
.sizes-range-thumb-preferred:hover {
|
|
box-shadow: 0 0 0 4px rgba(251, 191, 36, 0.25);
|
|
}
|
|
.sizes-range-thumb-max {
|
|
background: #1e293b;
|
|
border-color: #6366f1;
|
|
}
|
|
.sizes-range-thumb-max:hover {
|
|
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.25);
|
|
}
|
|
|
|
/* Legend */
|
|
.sizes-legend {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 18px;
|
|
margin-bottom: 16px;
|
|
padding: 8px 14px;
|
|
border-radius: 8px;
|
|
background: rgba(30, 41, 59, 0.3);
|
|
}
|
|
.sizes-legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-size: 0.75rem;
|
|
color: #94a3b8;
|
|
}
|
|
.sizes-legend-dot {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
}
|
|
.sizes-legend-dot-min { background: #6366f1; }
|
|
.sizes-legend-dot-preferred { background: #fbbf24; }
|
|
.sizes-legend-dot-max { background: #6366f1; }
|
|
.sizes-legend-dot-range { background: linear-gradient(90deg, #6366f1, #818cf8); width: 24px; border-radius: 3px; height: 6px; }
|
|
|
|
.sizes-loading {
|
|
text-align: center;
|
|
padding: 40px;
|
|
color: #64748b;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
/* Column labels row */
|
|
.sizes-column-labels {
|
|
display: grid;
|
|
grid-template-columns: 140px 1fr;
|
|
gap: 16px;
|
|
padding: 0 14px 6px 14px;
|
|
}
|
|
.sizes-column-labels-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
.sizes-column-label {
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
color: #64748b;
|
|
}
|
|
.sizes-column-label-min { width: 86px; text-align: center; flex-shrink: 0; }
|
|
.sizes-column-label-slider { flex: 1; text-align: center; min-width: 120px; }
|
|
.sizes-column-label-pref { width: 86px; text-align: center; flex-shrink: 0; }
|
|
.sizes-column-label-max { width: 86px; text-align: center; flex-shrink: 0; }
|
|
|
|
/* Responsive */
|
|
@media (max-width: 768px) {
|
|
.sizes-quality-row {
|
|
grid-template-columns: 1fr;
|
|
gap: 8px;
|
|
padding: 12px 10px;
|
|
}
|
|
.sizes-slider-group {
|
|
flex-wrap: wrap;
|
|
}
|
|
.sizes-range-container {
|
|
min-width: 100%;
|
|
order: -1;
|
|
}
|
|
.sizes-header {
|
|
flex-direction: column;
|
|
}
|
|
.sizes-column-labels {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
(function() {
|
|
'use strict';
|
|
|
|
var _sizesData = [];
|
|
var _sizesDirty = false;
|
|
var _sizesMax = 400;
|
|
var _sizesMode = 'movie';
|
|
var _sizesInstanceId = null;
|
|
|
|
function _getSelectedInstance() {
|
|
var sel = document.getElementById('settings-sizes-instance-select');
|
|
if (!sel || !sel.value) return { mode: _sizesMode, id: null };
|
|
var parts = sel.value.split(':');
|
|
return { mode: parts[0] || 'movie', id: parts[1] || null };
|
|
}
|
|
|
|
function _getApiBase() {
|
|
var inst = _getSelectedInstance();
|
|
_sizesMode = inst.mode;
|
|
_sizesInstanceId = inst.id;
|
|
if (inst.mode === 'tv') return './api/tv-hunt/sizes';
|
|
return './api/sizes';
|
|
}
|
|
|
|
function _appendInstanceParam(url) {
|
|
var inst = _getSelectedInstance();
|
|
if (!inst.id) return url;
|
|
var sep = url.indexOf('?') === -1 ? '?' : '&';
|
|
return url + sep + 'instance_id=' + encodeURIComponent(inst.id);
|
|
}
|
|
|
|
function markDirty() {
|
|
_sizesDirty = true;
|
|
var btn = document.getElementById('sizes-save-btn');
|
|
if (btn) btn.disabled = false;
|
|
}
|
|
|
|
function markClean() {
|
|
_sizesDirty = false;
|
|
var btn = document.getElementById('sizes-save-btn');
|
|
if (btn) btn.disabled = true;
|
|
}
|
|
|
|
function groupBadgeClass(group) {
|
|
var g = (group || '').toLowerCase().replace(/\s+/g, '');
|
|
if (g === 'lowquality') return 'sizes-group-badge-low';
|
|
if (g === 'sd') return 'sizes-group-badge-sd';
|
|
if (g === '720p') return 'sizes-group-badge-720';
|
|
if (g === '1080p') return 'sizes-group-badge-1080';
|
|
if (g === '2160p') return 'sizes-group-badge-2160';
|
|
if (g === 'ultra') return 'sizes-group-badge-ultra';
|
|
return 'sizes-group-badge-low';
|
|
}
|
|
|
|
function clamp(val, lo, hi) {
|
|
return Math.max(lo, Math.min(hi, val));
|
|
}
|
|
|
|
function pctOf(val) {
|
|
return (val / _sizesMax) * 100;
|
|
}
|
|
|
|
function updateVisual(row, item) {
|
|
var track = row.querySelector('.sizes-range-fill-active');
|
|
var prefMark = row.querySelector('.sizes-range-fill-preferred');
|
|
var thumbMin = row.querySelector('.sizes-range-thumb-min');
|
|
var thumbPref = row.querySelector('.sizes-range-thumb-preferred');
|
|
var thumbMax = row.querySelector('.sizes-range-thumb-max');
|
|
var minPct = pctOf(item.min);
|
|
var prefPct = pctOf(item.preferred);
|
|
var maxPct = pctOf(item.max);
|
|
if (track) {
|
|
track.style.left = minPct + '%';
|
|
track.style.width = (maxPct - minPct) + '%';
|
|
}
|
|
if (prefMark) prefMark.style.left = prefPct + '%';
|
|
if (thumbMin) thumbMin.style.left = minPct + '%';
|
|
if (thumbPref) thumbPref.style.left = prefPct + '%';
|
|
if (thumbMax) thumbMax.style.left = maxPct + '%';
|
|
}
|
|
|
|
function makeDraggable(thumb, item, field, row, minInput, prefInput, maxInput) {
|
|
var dragging = false;
|
|
var container = row.querySelector('.sizes-range-container');
|
|
|
|
function onMove(clientX) {
|
|
var rect = container.getBoundingClientRect();
|
|
var pct = ((clientX - rect.left) / rect.width);
|
|
var val = Math.round(pct * _sizesMax);
|
|
if (field === 'min') {
|
|
val = clamp(val, 0, item.preferred);
|
|
item.min = val;
|
|
if (minInput) minInput.value = val;
|
|
} else if (field === 'preferred') {
|
|
val = clamp(val, item.min, item.max);
|
|
item.preferred = val;
|
|
if (prefInput) prefInput.value = val;
|
|
} else {
|
|
val = clamp(val, item.preferred, _sizesMax);
|
|
item.max = val;
|
|
if (maxInput) maxInput.value = val;
|
|
}
|
|
updateVisual(row, item);
|
|
markDirty();
|
|
}
|
|
|
|
thumb.addEventListener('mousedown', function(e) {
|
|
e.preventDefault();
|
|
dragging = true;
|
|
document.addEventListener('mousemove', handleMouseMove);
|
|
document.addEventListener('mouseup', handleMouseUp);
|
|
});
|
|
thumb.addEventListener('touchstart', function(e) {
|
|
dragging = true;
|
|
document.addEventListener('touchmove', handleTouchMove, {passive: false});
|
|
document.addEventListener('touchend', handleTouchEnd);
|
|
});
|
|
|
|
function handleMouseMove(e) { if (dragging) onMove(e.clientX); }
|
|
function handleMouseUp() { dragging = false; document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }
|
|
function handleTouchMove(e) { if (dragging) { e.preventDefault(); onMove(e.touches[0].clientX); } }
|
|
function handleTouchEnd() { dragging = false; document.removeEventListener('touchmove', handleTouchMove); document.removeEventListener('touchend', handleTouchEnd); }
|
|
}
|
|
|
|
function renderSizes(sizes) {
|
|
_sizesData = sizes;
|
|
var container = document.getElementById('sizes-quality-list');
|
|
if (!container) return;
|
|
var html = '';
|
|
|
|
html += '<div class="sizes-legend">' +
|
|
'<div class="sizes-legend-item"><span class="sizes-legend-dot sizes-legend-dot-range"></span> Accepted range</div>' +
|
|
'<div class="sizes-legend-item"><span class="sizes-legend-dot sizes-legend-dot-preferred"></span> Preferred</div>' +
|
|
'<span style="flex:1"></span>' +
|
|
'<div class="sizes-legend-item" style="color: #64748b; font-size: 0.72rem;">MB per minute of runtime · Max scale: ' + _sizesMax + '</div>' +
|
|
'</div>';
|
|
|
|
html += '<div class="sizes-column-labels"><div></div><div class="sizes-column-labels-right">' +
|
|
'<div class="sizes-column-label sizes-column-label-min">Min</div>' +
|
|
'<div class="sizes-column-label sizes-column-label-slider">Range</div>' +
|
|
'<div class="sizes-column-label sizes-column-label-pref">Preferred</div>' +
|
|
'<div class="sizes-column-label sizes-column-label-max">Max</div>' +
|
|
'</div></div>';
|
|
|
|
var lastGroup = '';
|
|
for (var i = 0; i < sizes.length; i++) {
|
|
var s = sizes[i];
|
|
if (s.group && s.group !== lastGroup) {
|
|
lastGroup = s.group;
|
|
html += '<div class="sizes-group-header">' +
|
|
'<span class="sizes-group-badge ' + groupBadgeClass(s.group) + '">' + escapeHtml(s.group) + '</span>' +
|
|
'<span class="sizes-group-line"></span></div>';
|
|
}
|
|
var minPct = pctOf(s.min);
|
|
var prefPct = pctOf(s.preferred);
|
|
var maxPct = pctOf(s.max);
|
|
html += '<div class="sizes-quality-row" data-index="' + i + '">' +
|
|
'<div class="sizes-quality-name">' + escapeHtml(s.name) + '</div>' +
|
|
'<div class="sizes-slider-group">' +
|
|
'<div class="sizes-input-pair"><span class="sizes-input-label">Min</span>' +
|
|
'<input type="number" class="sizes-input sizes-input-min" min="0" max="' + _sizesMax + '" value="' + s.min + '"></div>' +
|
|
'<div class="sizes-range-container">' +
|
|
'<div class="sizes-range-track">' +
|
|
'<div class="sizes-range-fill sizes-range-fill-active" style="left:' + minPct + '%;width:' + (maxPct - minPct) + '%"></div>' +
|
|
'<div class="sizes-range-fill-preferred" style="left:' + prefPct + '%"></div>' +
|
|
'</div>' +
|
|
'<div class="sizes-range-thumb sizes-range-thumb-min" style="left:' + minPct + '%"></div>' +
|
|
'<div class="sizes-range-thumb sizes-range-thumb-preferred" style="left:' + prefPct + '%"></div>' +
|
|
'<div class="sizes-range-thumb sizes-range-thumb-max" style="left:' + maxPct + '%"></div>' +
|
|
'</div>' +
|
|
'<div class="sizes-input-pair"><span class="sizes-input-label">Pref</span>' +
|
|
'<input type="number" class="sizes-input sizes-input-preferred" min="0" max="' + _sizesMax + '" value="' + s.preferred + '"></div>' +
|
|
'<div class="sizes-input-pair"><span class="sizes-input-label">Max</span>' +
|
|
'<input type="number" class="sizes-input sizes-input-max" min="1" max="' + _sizesMax + '" value="' + s.max + '"></div>' +
|
|
'</div>' +
|
|
'</div>';
|
|
}
|
|
container.innerHTML = html;
|
|
|
|
var rows = container.querySelectorAll('.sizes-quality-row');
|
|
rows.forEach(function(row) {
|
|
var idx = parseInt(row.getAttribute('data-index'), 10);
|
|
var item = _sizesData[idx];
|
|
if (!item) return;
|
|
|
|
var minInput = row.querySelector('.sizes-input-min');
|
|
var prefInput = row.querySelector('.sizes-input-preferred');
|
|
var maxInput = row.querySelector('.sizes-input-max');
|
|
var thumbMin = row.querySelector('.sizes-range-thumb-min');
|
|
var thumbPref = row.querySelector('.sizes-range-thumb-preferred');
|
|
var thumbMax = row.querySelector('.sizes-range-thumb-max');
|
|
|
|
function syncFromInputs() {
|
|
var mn = parseInt(minInput.value, 10); if (isNaN(mn)) mn = 0;
|
|
var pf = parseInt(prefInput.value, 10); if (isNaN(pf)) pf = 0;
|
|
var mx = parseInt(maxInput.value, 10); if (isNaN(mx)) mx = 1;
|
|
mn = clamp(mn, 0, _sizesMax);
|
|
mx = clamp(mx, 1, _sizesMax);
|
|
if (mn > mx) mn = mx;
|
|
pf = clamp(pf, mn, mx);
|
|
item.min = mn; item.preferred = pf; item.max = mx;
|
|
minInput.value = mn; prefInput.value = pf; maxInput.value = mx;
|
|
updateVisual(row, item);
|
|
markDirty();
|
|
}
|
|
[minInput, prefInput, maxInput].forEach(function(inp) {
|
|
if (inp) {
|
|
inp.addEventListener('change', syncFromInputs);
|
|
inp.addEventListener('input', function() {
|
|
var mn = parseInt(minInput.value, 10) || 0;
|
|
var pf = parseInt(prefInput.value, 10) || 0;
|
|
var mx = parseInt(maxInput.value, 10) || 1;
|
|
mn = clamp(mn, 0, _sizesMax);
|
|
mx = clamp(mx, 1, _sizesMax);
|
|
pf = clamp(pf, mn, mx);
|
|
var tempItem = {min: mn, preferred: pf, max: mx};
|
|
updateVisual(row, tempItem);
|
|
markDirty();
|
|
});
|
|
}
|
|
});
|
|
|
|
if (thumbMin) makeDraggable(thumbMin, item, 'min', row, minInput, prefInput, maxInput);
|
|
if (thumbPref) makeDraggable(thumbPref, item, 'preferred', row, minInput, prefInput, maxInput);
|
|
if (thumbMax) makeDraggable(thumbMax, item, 'max', row, minInput, prefInput, maxInput);
|
|
});
|
|
}
|
|
|
|
function escapeHtml(s) {
|
|
if (s == null) return '';
|
|
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
}
|
|
|
|
function safeJsonFetch(url, fallback) {
|
|
return fetch(url, { cache: 'no-store' }).then(function(r) { return r.json(); }).catch(function() { return fallback || {}; });
|
|
}
|
|
|
|
function populateCombinedInstanceDropdown(preferMode) {
|
|
var selectEl = document.getElementById('settings-sizes-instance-select');
|
|
if (!selectEl) return;
|
|
selectEl.innerHTML = '<option value="">Loading...</option>';
|
|
var ts = Date.now();
|
|
Promise.all([
|
|
safeJsonFetch('./api/movie-hunt/instances?t=' + ts, { instances: [] }),
|
|
safeJsonFetch('./api/tv-hunt/instances?t=' + ts, { instances: [] }),
|
|
safeJsonFetch('./api/movie-hunt/instances/current?t=' + ts, { current_instance_id: null }),
|
|
safeJsonFetch('./api/tv-hunt/instances/current?t=' + ts, { current_instance_id: null })
|
|
]).then(function(results) {
|
|
var movieList = (results[0].instances || []).map(function(inst) {
|
|
return { value: 'movie:' + inst.id, label: 'Movie - ' + (inst.name || 'Instance ' + inst.id) };
|
|
});
|
|
var tvList = (results[1].instances || []).map(function(inst) {
|
|
return { value: 'tv:' + inst.id, label: 'TV - ' + (inst.name || 'Instance ' + inst.id) };
|
|
});
|
|
var combined = movieList.concat(tvList);
|
|
combined.sort(function(a, b) { return (a.label || '').localeCompare(b.label || '', undefined, { sensitivity: 'base' }); });
|
|
|
|
var currentMovie = results[2].current_instance_id != null ? Number(results[2].current_instance_id) : null;
|
|
var currentTv = results[3].current_instance_id != null ? Number(results[3].current_instance_id) : null;
|
|
|
|
selectEl.innerHTML = '';
|
|
if (combined.length === 0) {
|
|
var emptyOpt = document.createElement('option');
|
|
emptyOpt.value = '';
|
|
emptyOpt.textContent = 'No Movie or TV Hunt instances';
|
|
selectEl.appendChild(emptyOpt);
|
|
return;
|
|
}
|
|
combined.forEach(function(item) {
|
|
var opt = document.createElement('option');
|
|
opt.value = item.value;
|
|
opt.textContent = item.label;
|
|
selectEl.appendChild(opt);
|
|
});
|
|
|
|
var saved = '';
|
|
try { saved = localStorage.getItem('media-hunt-sizes-last-instance') || ''; } catch (e) {}
|
|
var selected = '';
|
|
if (preferMode === 'movie' && currentMovie != null) {
|
|
selected = 'movie:' + currentMovie;
|
|
if (!combined.some(function(i) { return i.value === selected; })) selected = combined[0].value;
|
|
} else if (preferMode === 'tv' && currentTv != null) {
|
|
selected = 'tv:' + currentTv;
|
|
if (!combined.some(function(i) { return i.value === selected; })) selected = combined[0].value;
|
|
} else if (saved && combined.some(function(i) { return i.value === saved; })) {
|
|
selected = saved;
|
|
} else if (currentMovie != null && combined.some(function(i) { return i.value === 'movie:' + currentMovie; })) {
|
|
selected = 'movie:' + currentMovie;
|
|
} else if (currentTv != null && combined.some(function(i) { return i.value === 'tv:' + currentTv; })) {
|
|
selected = 'tv:' + currentTv;
|
|
} else {
|
|
selected = combined[0].value;
|
|
}
|
|
selectEl.value = selected;
|
|
selected = selectEl.value || selected;
|
|
try { localStorage.setItem('media-hunt-sizes-last-instance', selected); } catch (e) {}
|
|
|
|
var inst = _getSelectedInstance();
|
|
_sizesMode = inst.mode;
|
|
_sizesInstanceId = inst.id;
|
|
doLoadSizes();
|
|
});
|
|
}
|
|
|
|
function onInstanceChange() {
|
|
var sel = document.getElementById('settings-sizes-instance-select');
|
|
if (sel) {
|
|
try { localStorage.setItem('media-hunt-sizes-last-instance', sel.value); } catch (e) {}
|
|
}
|
|
var inst = _getSelectedInstance();
|
|
_sizesMode = inst.mode;
|
|
_sizesInstanceId = inst.id;
|
|
markClean();
|
|
doLoadSizes();
|
|
}
|
|
|
|
function loadSizes(preferMode) {
|
|
var noIdxEl = document.getElementById('settings-sizes-no-indexers');
|
|
var noCliEl = document.getElementById('settings-sizes-no-clients');
|
|
var wrapperEl = document.getElementById('settings-sizes-content-wrapper');
|
|
Promise.all([
|
|
safeJsonFetch('./api/movie-hunt/instances', { instances: [] }),
|
|
safeJsonFetch('./api/tv-hunt/instances', { instances: [] })
|
|
]).then(function(results) {
|
|
var movieCount = (results[0].instances || []).length;
|
|
var tvCount = (results[1].instances || []).length;
|
|
if (movieCount === 0 && tvCount === 0) {
|
|
var selectEl = document.getElementById('settings-sizes-instance-select');
|
|
if (selectEl) {
|
|
selectEl.innerHTML = '';
|
|
var emptyOpt = document.createElement('option');
|
|
emptyOpt.value = '';
|
|
emptyOpt.textContent = 'No Movie or TV Hunt instances';
|
|
selectEl.appendChild(emptyOpt);
|
|
}
|
|
if (noIdxEl) noIdxEl.style.display = 'none';
|
|
if (noCliEl) noCliEl.style.display = 'none';
|
|
if (wrapperEl) wrapperEl.style.display = '';
|
|
return;
|
|
}
|
|
if (noIdxEl) noIdxEl.style.display = 'none';
|
|
if (noCliEl) noCliEl.style.display = 'none';
|
|
if (wrapperEl) wrapperEl.style.display = '';
|
|
populateCombinedInstanceDropdown(preferMode || 'movie');
|
|
}).catch(function() {
|
|
if (noIdxEl) noIdxEl.style.display = 'none';
|
|
if (noCliEl) noCliEl.style.display = '';
|
|
if (wrapperEl) wrapperEl.style.display = 'none';
|
|
});
|
|
}
|
|
|
|
function doLoadSizes() {
|
|
var url = _appendInstanceParam(_getApiBase());
|
|
fetch(url, { cache: 'no-store' })
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (data.success && data.sizes) {
|
|
renderSizes(data.sizes);
|
|
markClean();
|
|
} else if (data.error) {
|
|
var container = document.getElementById('sizes-quality-list');
|
|
if (container) container.innerHTML = '<div class="sizes-loading" style="color:#ef4444;">' + escapeHtml(data.error) + '</div>';
|
|
}
|
|
})
|
|
.catch(function(err) {
|
|
console.error('[Sizes] Failed to load:', err);
|
|
var container = document.getElementById('sizes-quality-list');
|
|
if (container) container.innerHTML = '<div class="sizes-loading" style="color:#ef4444;">Failed to load sizes. <a href="#" onclick="location.reload()" style="color:#6366f1;">Retry</a></div>';
|
|
});
|
|
}
|
|
|
|
|
|
function saveSizes() {
|
|
var btn = document.getElementById('sizes-save-btn');
|
|
if (btn) btn.disabled = true;
|
|
var url = _appendInstanceParam(_getApiBase());
|
|
fetch(url, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ sizes: _sizesData })
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (data.success) {
|
|
_sizesData = data.sizes;
|
|
markClean();
|
|
if (window.huntarrUI && window.huntarrUI.showNotification) {
|
|
window.huntarrUI.showNotification('Sizes saved.', 'success');
|
|
}
|
|
} else {
|
|
if (btn) btn.disabled = false;
|
|
if (window.huntarrUI && window.huntarrUI.showNotification) {
|
|
window.huntarrUI.showNotification(data.error || 'Failed to save sizes.', 'error');
|
|
}
|
|
}
|
|
})
|
|
.catch(function() {
|
|
if (btn) btn.disabled = false;
|
|
if (window.huntarrUI && window.huntarrUI.showNotification) {
|
|
window.huntarrUI.showNotification('Failed to save sizes.', 'error');
|
|
}
|
|
});
|
|
}
|
|
|
|
function resetSizes() {
|
|
function doReset() {
|
|
var url = _appendInstanceParam(_getApiBase() + '/reset');
|
|
fetch(url, { method: 'POST' })
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (data.success && data.sizes) {
|
|
renderSizes(data.sizes);
|
|
markClean();
|
|
if (window.huntarrUI && window.huntarrUI.showNotification) {
|
|
window.huntarrUI.showNotification('Sizes reset to defaults.', 'success');
|
|
}
|
|
}
|
|
})
|
|
.catch(function() {
|
|
if (window.huntarrUI && window.huntarrUI.showNotification) {
|
|
window.huntarrUI.showNotification('Failed to reset sizes.', 'error');
|
|
}
|
|
});
|
|
}
|
|
if (window.HuntarrConfirm && window.HuntarrConfirm.show) {
|
|
window.HuntarrConfirm.show({
|
|
title: 'Reset Size Limits',
|
|
message: 'Reset all size limits to defaults for this instance?',
|
|
confirmLabel: 'Reset',
|
|
onConfirm: doReset
|
|
});
|
|
} else {
|
|
if (!confirm('Reset all size limits to defaults for this instance?')) return;
|
|
doReset();
|
|
}
|
|
}
|
|
|
|
function initSizes() {
|
|
var saveBtn = document.getElementById('sizes-save-btn');
|
|
var resetBtn = document.getElementById('sizes-reset-btn');
|
|
if (saveBtn && !saveBtn._sizesBound) {
|
|
saveBtn._sizesBound = true;
|
|
saveBtn.addEventListener('click', saveSizes);
|
|
}
|
|
if (resetBtn && !resetBtn._sizesBound) {
|
|
resetBtn._sizesBound = true;
|
|
resetBtn.addEventListener('click', resetSizes);
|
|
}
|
|
var selectEl = document.getElementById('settings-sizes-instance-select');
|
|
if (selectEl && !selectEl._sizesChangeBound) {
|
|
selectEl._sizesChangeBound = true;
|
|
selectEl.addEventListener('change', onInstanceChange);
|
|
}
|
|
}
|
|
|
|
window.SizesModule = {
|
|
load: loadSizes,
|
|
init: initSizes,
|
|
initOrRefresh: function(preferMode) {
|
|
_sizesMode = (preferMode === 'tv') ? 'tv' : 'movie';
|
|
initSizes();
|
|
loadSizes(preferMode);
|
|
}
|
|
};
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initSizes);
|
|
} else {
|
|
initSizes();
|
|
}
|
|
})();
|
|
</script>
|