mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
#680 - added auto organize page
This commit is contained in:
parent
fda9b3d93d
commit
9ab4adcd33
10 changed files with 481 additions and 159 deletions
31
ApiClient.js
31
ApiClient.js
|
@ -666,6 +666,37 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
|
|||
});
|
||||
};
|
||||
|
||||
self.getFileOrganizationResults = function (options) {
|
||||
|
||||
var url = self.getUrl("Library/FileOrganization", options || {});
|
||||
|
||||
return self.ajax({
|
||||
type: "GET",
|
||||
url: url,
|
||||
dataType: "json"
|
||||
});
|
||||
};
|
||||
|
||||
self.deleteOriginalFileFromOrganizationResult = function (id) {
|
||||
|
||||
var url = self.getUrl("Library/FileOrganizations/" + id + "/File");
|
||||
|
||||
return self.ajax({
|
||||
type: "DELETE",
|
||||
url: url
|
||||
});
|
||||
};
|
||||
|
||||
self.performOrganization = function (id) {
|
||||
|
||||
var url = self.getUrl("Library/FileOrganizations/" + id + "/Organize");
|
||||
|
||||
return self.ajax({
|
||||
type: "POST",
|
||||
url: url
|
||||
});
|
||||
};
|
||||
|
||||
self.getLiveTvSeriesTimer = function (id) {
|
||||
|
||||
if (!id) {
|
||||
|
|
|
@ -76,3 +76,10 @@
|
|||
background-color: #eeeeee; /* non-RGBA fallback */
|
||||
background-color: rgba(0,0,0,.1);
|
||||
}
|
||||
|
||||
|
||||
.stripedTable tbody tr:nth-child(odd) td,
|
||||
.stripedTable tbody tr:nth-child(odd) th {
|
||||
background-color: #eeeeee; /* non-RGBA fallback */
|
||||
background-color: rgba(0,0,0,.04);
|
||||
}
|
||||
|
|
|
@ -171,7 +171,7 @@
|
|||
}
|
||||
|
||||
.labelPageSize {
|
||||
margin-left: 1em;
|
||||
margin-left: 1em!important;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -846,3 +846,12 @@ progress {
|
|||
max-width: 1100px;
|
||||
}
|
||||
}
|
||||
|
||||
.organizerButtonCell {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.organizerButton {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
|
@ -1,30 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Media Library</title>
|
||||
<title>Auto-Organize</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="libraryFileOrganizerPage" data-role="page" class="page type-interior mediaLibraryPage">
|
||||
<div id="libraryFileOrganizerPage" data-role="page" class="page type-interior organizePage">
|
||||
|
||||
<div data-role="content">
|
||||
<div class="content-primary">
|
||||
<div data-role="controlgroup" data-type="horizontal" class="localnav" data-mini="true">
|
||||
<a href="library.html" data-role="button">Media Folders</a>
|
||||
<a href="librarysettings.html" data-role="button">Settings</a>
|
||||
<a href="#" data-role="button" class="ui-btn-active">File Organizer</a>
|
||||
<a href="libraryfileorganizerlog.html" data-role="button">Activity Log</a>
|
||||
<a href="#" data-role="button" class="ui-btn-active">TV Settings</a>
|
||||
</div>
|
||||
|
||||
<form class="libraryFileOrganizerForm">
|
||||
|
||||
<p>File organizing allows the server to monitor your download folders for new files and move them to your media directories.</p>
|
||||
<p>The file organizer monitors your download folders for new files and moves them to your media directories.</p>
|
||||
|
||||
<div data-role="controlgroup" data-type="horizontal" data-mini="true" class="sortingTabs" style="display: none;">
|
||||
<input type="radio" name="radioSortingSettingsTab" class="radioSortingSettingsTab" id="radioTvSortingSettings" value="tv" checked="checked">
|
||||
<label for="radioTvSortingSettings">TV Sorting</label>
|
||||
<input type="radio" name="radioSortingSettingsTab" class="radioSortingSettingsTab" id="radioMovieSortingSettings" value="movies">
|
||||
<label for="radioMovieSortingSettings">Movie Sorting</label>
|
||||
</div>
|
||||
<div class="tvTab tab">
|
||||
<p>TV file organizing will only add episodes to existing series. It will not create new series folders.</p>
|
||||
<ul data-role="listview" class="ulForm" style="margin-bottom: 0!important;">
|
||||
<li>
|
||||
|
@ -44,6 +36,7 @@
|
|||
<li>
|
||||
<label for="txtMinFileSize">Minimum file size (MB): </label>
|
||||
<input type="number" id="txtMinFileSize" name="txtMinFileSize" pattern="[0-9]*" required="required" min="0" data-mini="true" />
|
||||
<div class="fieldDescription">Files under this size will be ignored.</div>
|
||||
</li>
|
||||
<li>
|
||||
<label for="txtSeasonFolderPattern">Season folder pattern: </label>
|
||||
|
@ -77,52 +70,52 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Series Name</th>
|
||||
<td>Series Name</td>
|
||||
<td>%sn</td>
|
||||
<td>Series Name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Series Name</th>
|
||||
<td>Series Name</td>
|
||||
<td>%s.n</td>
|
||||
<td>Series.Name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Series Name</th>
|
||||
<td>Series Name</td>
|
||||
<td>%s_n</td>
|
||||
<td>Series_Name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Season Number</th>
|
||||
<td>Season Number</td>
|
||||
<td>%s</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Season Number</th>
|
||||
<td>Season Number</td>
|
||||
<td>%0s</td>
|
||||
<td>01</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Episode Number</th>
|
||||
<td>Episode Number</td>
|
||||
<td>%e</td>
|
||||
<td>4</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Episode Number</th>
|
||||
<td>Episode Number</td>
|
||||
<td>%0e</td>
|
||||
<td>04</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Episode Name</th>
|
||||
<td>Episode Name</td>
|
||||
<td>%en</td>
|
||||
<td>Episode Name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Episode Name</th>
|
||||
<td>Episode Name</td>
|
||||
<td>%e.n</td>
|
||||
<td>Episode.Name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Episode Name</th>
|
||||
<td>Episode Name</td>
|
||||
<td>%e_n</td>
|
||||
<td>Episode_Name</td>
|
||||
</tr>
|
||||
|
@ -147,7 +140,6 @@
|
|||
<div class="fieldDescription">With trial mode enabled, file organizations will be logged but not executed.</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ul data-role="listview" class="ulForm">
|
||||
<li>
|
||||
|
|
41
dashboard-ui/libraryfileorganizerlog.html
Normal file
41
dashboard-ui/libraryfileorganizerlog.html
Normal file
|
@ -0,0 +1,41 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Auto-Organize</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="libraryFileOrganizerLogPage" data-role="page" class="page type-interior adminPage organizePage">
|
||||
|
||||
<div data-role="content">
|
||||
<div class="content-primary">
|
||||
|
||||
<div data-role="controlgroup" data-type="horizontal" class="localnav" data-mini="true">
|
||||
<a href="#" data-role="button" class="ui-btn-active">Activity Log</a>
|
||||
<a href="libraryfileorganizer.html" data-role="button">TV Settings</a>
|
||||
</div>
|
||||
|
||||
<div style="margin: -15px 0 1em;text-align:left;">
|
||||
<div class="listTopPaging">
|
||||
</div>
|
||||
</div>
|
||||
<table id="movie-table" data-role="table" data-mode="reflow" class="tblOrganizationResults stripedTable ui-responsive table-stroke">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-priority="2">Date</th>
|
||||
<th data-priority="1">Source</th>
|
||||
<th data-priority="3">Destination</th>
|
||||
<th data-priority="1">Result</th>
|
||||
<th data-priority="1"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="resultBody">
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
<div class="listBottomPaging">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1426,7 +1426,7 @@
|
|||
return html;
|
||||
},
|
||||
|
||||
getPagingHtml: function (query, totalRecordCount, updatePageSizeSetting, pageSizes) {
|
||||
getPagingHtml: function (query, totalRecordCount, updatePageSizeSetting, pageSizes, showLimit) {
|
||||
|
||||
if (query.Limit && updatePageSizeSetting !== false) {
|
||||
localStorage.setItem('pagesize', query.Limit);
|
||||
|
@ -1451,7 +1451,7 @@
|
|||
var recordsEnd = Math.min(query.StartIndex + query.Limit, totalRecordCount);
|
||||
|
||||
// 20 is the minimum page size
|
||||
var showControls = totalRecordCount > 20;
|
||||
var showControls = totalRecordCount > 20 || query.Limit < totalRecordCount;
|
||||
|
||||
html += '<div class="listPaging">';
|
||||
|
||||
|
@ -1461,16 +1461,20 @@
|
|||
html += startAtDisplay + '-' + recordsEnd + ' of ' + totalRecordCount;
|
||||
|
||||
if (showControls) {
|
||||
html += ', page ' + dropdownHtml + ' of ' + pageCount;
|
||||
//html += ', page ' + dropdownHtml + ' of ' + pageCount;
|
||||
}
|
||||
|
||||
html += '</span>';
|
||||
|
||||
if (showControls) {
|
||||
|
||||
html += '<div data-role="controlgroup" data-type="horizontal" style="display:inline-block;">';
|
||||
html += '<button data-icon="arrow-l" data-iconpos="notext" data-inline="true" data-mini="true" class="btnPreviousPage" ' + (query.StartIndex ? '' : 'disabled') + '>Previous Page</button>';
|
||||
|
||||
html += '<button data-icon="arrow-r" data-iconpos="notext" data-inline="true" data-mini="true" class="btnNextPage" ' + (query.StartIndex + query.Limit > totalRecordCount ? 'disabled' : '') + '>Next Page</button>';
|
||||
html += '</div>';
|
||||
|
||||
if (showLimit !== false) {
|
||||
var id = "selectPageSize" + new Date().getTime();
|
||||
|
||||
var options = '';
|
||||
|
@ -1495,6 +1499,7 @@
|
|||
// Add styles to defeat jquery mobile
|
||||
html += '<label style="display:inline;font-size:inherit;" class="labelPageSize" for="' + id + '">Limit: </label><select class="selectPageSize" id="' + id + '" data-enhance="false" data-role="none">' + options + '</select>';
|
||||
}
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
|
|
|
@ -52,13 +52,13 @@
|
|||
|
||||
var page = this;
|
||||
|
||||
$('#txtSeasonFolderPattern', page).on('change keypress', function() {
|
||||
$('#txtSeasonFolderPattern', page).on('change keyup', function () {
|
||||
|
||||
updateSeasonPatternHelp(page, this.value);
|
||||
|
||||
});
|
||||
|
||||
$('#txtEpisodePattern', page).on('change keypress', function () {
|
||||
$('#txtEpisodePattern', page).on('change keyup', function () {
|
||||
|
||||
updateEpisodePatternHelp(page, this.value);
|
||||
|
||||
|
|
237
dashboard-ui/scripts/libraryfileorganizerlog.js
Normal file
237
dashboard-ui/scripts/libraryfileorganizerlog.js
Normal file
|
@ -0,0 +1,237 @@
|
|||
(function ($, document, window) {
|
||||
|
||||
var query = {
|
||||
|
||||
StartIndex: 0,
|
||||
Limit: 50
|
||||
};
|
||||
|
||||
var currentResult;
|
||||
|
||||
function showStatusMessage(id) {
|
||||
|
||||
var item = currentResult.Items.filter(function (i) {
|
||||
return i.Id == id;
|
||||
|
||||
})[0];
|
||||
|
||||
Dashboard.alert({
|
||||
|
||||
title: getStatusText(item, false),
|
||||
message: item.StatusMessage
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function deleteOriginalFile(page, id) {
|
||||
|
||||
var item = currentResult.Items.filter(function (i) {
|
||||
return i.Id == id;
|
||||
|
||||
})[0];
|
||||
|
||||
var message = 'The following file will be deleted:<p>' + item.OriginalPath + '</p><p>Are you sure you wish to proceed?</p>';
|
||||
|
||||
Dashboard.confirm(message, "Delete File", function (confirmResult) {
|
||||
|
||||
if (confirmResult) {
|
||||
|
||||
Dashboard.showLoadingMsg();
|
||||
|
||||
ApiClient.deleteOriginalFileFromOrganizationResult(id).done(function () {
|
||||
|
||||
Dashboard.hideLoadingMsg();
|
||||
|
||||
reloadItems(page);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function organizeFile(page, id) {
|
||||
|
||||
var item = currentResult.Items.filter(function (i) {
|
||||
return i.Id == id;
|
||||
|
||||
})[0];
|
||||
|
||||
var message = 'The following file will be moved from:<p>' + item.OriginalPath + '</p><p>to:</p><p>' + item.TargetPath + '</p><p>Are you sure you wish to proceed?</p>';
|
||||
|
||||
Dashboard.confirm(message, "Organize File", function (confirmResult) {
|
||||
|
||||
if (confirmResult) {
|
||||
|
||||
Dashboard.showLoadingMsg();
|
||||
|
||||
ApiClient.performOrganization(id).done(function () {
|
||||
|
||||
Dashboard.hideLoadingMsg();
|
||||
|
||||
reloadItems(page);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function reloadItems(page) {
|
||||
|
||||
Dashboard.showLoadingMsg();
|
||||
|
||||
ApiClient.getFileOrganizationResults(query).done(function (result) {
|
||||
|
||||
currentResult = result;
|
||||
renderResults(page, result);
|
||||
|
||||
Dashboard.hideLoadingMsg();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function getStatusText(item, enhance) {
|
||||
|
||||
var status = item.Status;
|
||||
|
||||
var color = null;
|
||||
|
||||
if (status == 'SkippedTrial') {
|
||||
status = 'Trial';
|
||||
}
|
||||
else if (status == 'SkippedExisting') {
|
||||
status = 'Skipped';
|
||||
}
|
||||
else if (status == 'Failure') {
|
||||
color = '#cc0000';
|
||||
status = 'Failed';
|
||||
}
|
||||
if (status == 'Success') {
|
||||
color = 'green';
|
||||
status = 'Success';
|
||||
}
|
||||
|
||||
if (enhance && enhance) {
|
||||
|
||||
if (item.StatusMessage) {
|
||||
|
||||
return '<a style="color:' + color + ';" data-resultid="' + item.Id + '" href="#" class="btnShowStatusMessage">' + status + '</a>';
|
||||
} else {
|
||||
return '<span data-resultid="' + item.Id + '" style="color:' + color + ';">' + status + '</span>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
function renderResults(page, result) {
|
||||
|
||||
var rows = result.Items.map(function (item) {
|
||||
|
||||
var html = '';
|
||||
|
||||
html += '<tr>';
|
||||
|
||||
html += '<td>';
|
||||
|
||||
var date = parseISO8601Date(item.Date, { toLocal: true });
|
||||
html += date.toLocaleDateString();
|
||||
|
||||
html += '</td>';
|
||||
|
||||
html += '<td>';
|
||||
html += item.OriginalFileName || '';
|
||||
html += '</td>';
|
||||
|
||||
html += '<td>';
|
||||
html += item.TargetPath || '';
|
||||
html += '</td>';
|
||||
|
||||
html += '<td>';
|
||||
html += getStatusText(item, true);
|
||||
html += '</td>';
|
||||
|
||||
html += '<td class="organizerButtonCell">';
|
||||
|
||||
|
||||
if (item.Status == 'SkippedTrial' || item.Status == 'SkippedExisting') {
|
||||
html += '<button data-resultid="' + item.Id + '" type="button" data-inline="true" data-icon="action" data-mini="true" data-iconpos="notext" class="btnProcessResult organizerButton" title="Organize File">Process</button>';
|
||||
} else {
|
||||
html += '<button style="visibility:hidden;" type="button" data-inline="true" data-icon="info" data-mini="true" data-iconpos="notext" class="organizerButton"></button>';
|
||||
}
|
||||
|
||||
if (item.Status != 'Success') {
|
||||
html += '<button data-resultid="' + item.Id + '" type="button" data-inline="true" data-icon="delete" data-mini="true" data-iconpos="notext" class="btnDeleteResult organizerButton" title="Delete">Delete File</button>';
|
||||
}
|
||||
|
||||
html += '</td>';
|
||||
|
||||
html += '</tr>';
|
||||
|
||||
return html;
|
||||
}).join('');
|
||||
|
||||
var elem = $('.resultBody', page).html(rows).parents('.tblOrganizationResults').table("refresh").trigger('create');
|
||||
|
||||
$('.btnShowStatusMessage', elem).on('click', function () {
|
||||
|
||||
var id = this.getAttribute('data-resultid');
|
||||
|
||||
showStatusMessage(id);
|
||||
});
|
||||
|
||||
$('.btnProcessResult', elem).on('click', function () {
|
||||
|
||||
var id = this.getAttribute('data-resultid');
|
||||
|
||||
organizeFile(page, id);
|
||||
});
|
||||
|
||||
$('.btnDeleteResult', elem).on('click', function () {
|
||||
|
||||
var id = this.getAttribute('data-resultid');
|
||||
|
||||
deleteOriginalFile(page, id);
|
||||
});
|
||||
|
||||
var pagingHtml = LibraryBrowser.getPagingHtml(query, result.TotalRecordCount, false, [], false);
|
||||
$('.listTopPaging', page).html(pagingHtml).trigger('create');
|
||||
|
||||
if (result.TotalRecordCount > query.Limit && result.TotalRecordCount > 50) {
|
||||
$('.listBottomPaging', page).html(pagingHtml).trigger('create');
|
||||
} else {
|
||||
$('.listBottomPaging', page).empty();
|
||||
}
|
||||
|
||||
$('.selectPage', page).on('change', function () {
|
||||
query.StartIndex = (parseInt(this.value) - 1) * query.Limit;
|
||||
reloadItems(page);
|
||||
});
|
||||
|
||||
$('.btnNextPage', page).on('click', function () {
|
||||
query.StartIndex += query.Limit;
|
||||
reloadItems(page);
|
||||
});
|
||||
|
||||
$('.btnPreviousPage', page).on('click', function () {
|
||||
query.StartIndex -= query.Limit;
|
||||
reloadItems(page);
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on('pageshow', "#libraryFileOrganizerLogPage", function () {
|
||||
|
||||
var page = this;
|
||||
|
||||
reloadItems(page);
|
||||
|
||||
}).on('pagehide', "#libraryFileOrganizerLogPage", function () {
|
||||
|
||||
currentResult = null;
|
||||
});
|
||||
|
||||
})(jQuery, document, window);
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="MediaBrowser.ApiClient.Javascript" version="3.0.238" targetFramework="net45" />
|
||||
<package id="MediaBrowser.ApiClient.Javascript" version="3.0.240" targetFramework="net45" />
|
||||
</packages>
|
Loading…
Add table
Add a link
Reference in a new issue