Koha/koha-tmpl/intranet-tmpl/prog/en/modules/tools/upload-images.tt
Owen Leonard 3de4031de1 Bug 25025: Drag-and-drop cover image upload
This patch updates the Upload local cover image page so that the user
has the option of dragging a file from a folder on their computer
instead of using a file upload button.

The patch also adds a preview of uploaded single images and display of
existing images on the record you're adding to.

To test, apply the patch and make sure the LocalCoverImages system
preference is enabled.

- Go to Tools -> Upload local cover image and test the following
  processes:

- Upload single cover image, specifying a biblionumber
  - Test dragging an image from a file on your computer
  - Test clicking the "Drop files here or click..." link.
    - You should see a preview of the image file on the screen, with
      information about the file: file name, image type, file size.
      - Click "Process images"
        - with "Existing covers will be replaced" checked
        - with "Existing covers will be replaced" unchecked

  When the upload process completes you should see information about the
  title in the page heading and a thumbnail of the cover in the sidebar.
  - Test that the image can be deleted from this page. You should be
    redirected back to this page with the same title still selected.

- Upload a zip file of images
  - Test dragging an image from a file on your computer
  - Test clicking the "Drop files here or click..." link.
    - A zip file can't be previewed onscreen but you should see the same
      file information and a Font Awesome "zip file" icon.
      - Click "Process images"

From the bibliographic detail page, click the "Images" tab, and click
"Upload." With this workflow the field asking for a biblionumber should
not appear.

From the bibliographic detail page, in the holdings table, choose Edit
-> Upload image. This process should be the same as above but should
provide item information on the screen. Confirm that images are uploaded
correctly to the specific item.

Test with AllowMultipleCovers enabled and disabled to confirm that the
"Existing covers will be replaced" checkbox is enabled only when
multiple covers are possible.

Signed-off-by: Solène Desvaux <solene.desvaux@biblibre.com>

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Fridolin Somers <fridolin.somers@biblibre.com>
2022-03-01 22:47:46 -10:00

541 lines
24 KiB
Text

[% USE raw %]
[% USE Asset %]
[% SET footerjs = 1 %]
[% INCLUDE 'doc-head-open.inc' %]
<title>
Koha &rsaquo;
Tools &rsaquo;
Upload local cover image
[% IF ( biblio ) %]
&rsaquo; [% INCLUDE 'biblio-title-head.inc' %]
[% END %]
</title>
[% INCLUDE 'doc-head-close.inc' %]
[% FILTER collapse %]
<style>
#fileuploadstatus,
#fileuploadfailed,
#jobpanel,
#jobstatus,
#jobfailed,
#fileuploadform,
#upload_file_type,
#upload_options,
#process_images {
display: none;
}
#uploadpanel {
clear:both;
margin-top: .9em;
}
#filedrag {
background-color: #FFF;
outline: 2px dashed #92b0b3;
outline-offset: -10px;
font-weight: bold;
font-size: 130%;
text-align: center;
position: relative;
padding: 50px 20px;
margin: 1em;
cursor: default;
}
#click_to_select:hover {
color: #538200;
cursor: pointer;
}
#filedrag.hover {
outline-offset: -20px;
outline-color: #c8dadf;
background-color: #fff;
}
#messages {
font-weight: normal;
}
.cover_preview {
margin:1em;
max-height:200px;
max-width:200px;
}
.progress_panel {
border: 0;
border-radius: 0;
margin: .9em;
background-color: #FFF;
}
.fa-file-archive-o {
color: #777;
font-size: 300%
}
</style>
[% END %]
</head>
<body id="tools_upload-images" class="tools">
[% INCLUDE 'header.inc' %]
[% INCLUDE 'cat-search.inc' %]
<nav id="breadcrumbs" aria-label="Breadcrumb" class="breadcrumb">
<ol>
<li>
<a href="/cgi-bin/koha/mainpage.pl">Home</a>
</li>
<li>
<a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a>
</li>
<li>
<a href="/cgi-bin/koha/tools/upload-cover-image.pl">Upload local cover image</a>
</li>
[% IF ( uploadimage ) %]
<li>
<a href="#" aria-current="page">
Upload results
</a>
</li>
[% IF ( biblionumber ) %]
<li>
<a href="#" aria-current="page"><em>[% INCLUDE 'biblio-title.inc' %]</em></a>
</li>
[% END %]
[% ELSIF itemnumber %]
<li>
<a href="#" aria-current="page">
Upload cover for itemnumber: [% itemnumber | html %]
</a>
</li>
[% ELSIF biblionumber %]
<li>
<a href="#" aria-current="page">
Upload cover for biblionumber: [% biblionumber | html %]
</a>
</li>
[% ELSE %]
<li>
<a href="#" aria-current="page">
Upload local cover image
</a>
</li>
[% END %]
</ol>
</nav>
<div class="main container-fluid">
<div class="row">
<div class="col-sm-10 col-sm-push-2">
<main>
[% UNLESS itemnumber || biblionumber %]
<h1>Upload local cover image</h1>
[% ELSIF biblio %]
<h1>
Upload local cover image for <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblionumber | uri %]"><em>[% INCLUDE 'biblio-title.inc' %]</em></a>
[% IF ( itemnumber ) %], Item number: [% itemnumber | html %][% END %]
</h1>
[% ELSIF itemnumber %]
<h1>Upload local cover image for item number: [% itemnumber | html %]</h1>
[% END %]
[% IF ( uploadimage ) %]
<h2>Image upload results :</h2>
<div id="upload_results">
[% IF ( error ) %]
<div class="dialog alert">
[% IF ( error == 'UZIPFAIL' ) %]
<p><strong>Failed to unzip archive.<br />Please ensure you are uploading a valid zip file and try again.</strong></p>
[% ELSIF ( error == 'OPNLINK' ) %]
<p><strong>Cannot open folder index (idlink.txt or datalink.txt) to read.<br />Please verify that it exists.</strong></p>
[% ELSIF ( error == 'OPNIMG' ) %]
<p><strong>Cannot process file as an image.<br />Please ensure you only upload GIF, JPEG, PNG, or XPM images.</strong></p>
[% ELSIF ( error == 'DELERR' ) %]
<p><strong>Unrecognized or missing field delimiter.<br />Please verify that you are using either a single quote or a tab.</strong></p>
[% ELSIF ( error == 'DBERR' ) %]
<p><strong>Unable to save image to database.</strong></p>
[% ELSE %]
<p><strong>An unknown error has occurred.<br />Please review the error log for more details.</strong></p>
[% END %]
</div>
[% END # /IF error %]
<h3>[% total | html %] images found</h3>
<ul>
[% FOREACH result IN results %]
<li><a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% result.biblionumber | uri %]">[% result.title | html %]</a></li>
[% END %]
</ul>
<hr />
</div> <!-- /#upload_results -->
[% END # /IF uploadimage %]
<ul>
[% UNLESS itemnumber || biblionumber %]
<li>Select an image file or ZIP file to upload. The tool will accept images in GIF, JPEG, PNG, and XPM formats.</li>
[% ELSE %]
<li>Select an image file to upload. The tool will accept images in GIF, JPEG, PNG, and XPM formats.</li>
[% END %]
</ul>
<div class="row">
[% IF ( cover_images ) %]
<div class="col-sm-9">
[% ELSE %]
<div class="col-sm-12">
[% END %]
<form method="post" action="/cgi-bin/koha/tools/upload-cover-image.pl" id="uploadfile" enctype="multipart/form-data">
<fieldset class="rows" >
<div id="fileuploadform">
<label for="fileToUpload">Select the file to upload: </label>
<input type="file" id="fileToUpload" name="fileToUpload" />
</div>
<div id="filedrag">
<a id="click_to_select" href="#">Drop files here or click to select a file</a>
<div id="messages"></div>
</div>
<div id="uploadpanel">
<div id="fileuploadstatus" class="progress_panel">Upload progress:
<progress max="100" value="0" id="fileuploadprogress">
</progress>
<span class="fileuploadpercent">0</span>%
</div>
<div id="fileuploadfailed"></div>
</div>
</fieldset>
</form> <!-- /#uploadfile -->
<form method="post" id="processfile" action="/cgi-bin/koha/tools/upload-cover-image.pl" enctype="multipart/form-data">
<input type="hidden" name="uploadedfileid" id="uploadedfileid" value="" />
<input type="hidden" name="runinbackground" id="runinbackground" value="" />
<input type="hidden" name="completedJobID" id="completedJobID" value="" />
[% IF itemnumber %]
<input type="hidden" id="itemnumber" name="itemnumber" value="[% itemnumber | html %]" />
<input type="hidden" name="filetype" value="image" />
[% ELSIF biblionumber %]
<input type="hidden" id="biblionumber" name="biblionumber" value="[% biblionumber | html %]" />
<input type="hidden" name="filetype" value="image" />
[% END %]
<fieldset id="upload_options" class="rows">
<ol>
[% UNLESS itemnumber || biblionumber %]
<li class="radio">
[% IF (filetype != 'image' ) %]
<input type="radio" id="zipfile" name="filetype" value="zip" checked="checked" />
[% ELSE %]
<input type="radio" id="zipfile" name="filetype" value="zip" />
[% END %]
<label for="zipfile">ZIP file</label>
</li>
<li class="radio">
[% IF (filetype == 'image' ) %]
<input type="radio" id="image" name="filetype" value="image" checked="checked" />
[% ELSE %]
<input type="radio" id="image" name="filetype" value="image" />
[% END %]
<label for="image">Image file</label>
</li>
<li id="biblionumber_entry">
<label for="biblionumber">Bibliographic record number: </label>
<input type="text" id="biblionumber" name="biblionumber" value="[% biblionumber | html %]" size="15" />
</li>
[% END %]
<li class="radio">
<label for="replace">
[% IF AllowMultipleCovers == 0 %]
<input type="checkbox" id="replace" name="replace" value="" disabled="disabled" checked="checked" />
Existing covers will be replaced
[% ELSE %]
<input type="checkbox" id="replace" name="replace" value="1" />
Replace existing covers
[% END %]
</li>
</ol>
</fieldset>
<fieldset id="process_images" class="action">
<button type="submit" class="btn btn-default btn-sm save_image">Process images</button>
[% IF ( biblionumber ) %]
<a class="cancel cancel_image" href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblionumber | uri %]">Cancel</a>
[% ELSE %]
<a class="cancel cancel_image" href="/cgi-bin/koha/tools/upload-cover-image.pl">Cancel</a>
[% END %]
</fieldset>
</form> <!-- /#processfile -->
</div> <!-- /.col-sm-9/.col-sm-12 -->
[% IF ( cover_images.size ) %]
<div class="col-sm-3">
<h3>Existing images</h3>
<ul class="thumbnails">
[% FOREACH img IN cover_images %]
[% IF img %]
<li id="imagenumber-[% img.imagenumber | html %]" class="thumbnail">
<a class="show_cover" data-coverimg="[% img.imagenumber | html %]" href="/cgi-bin/koha/catalogue/imageviewer.pl?biblionumber=[% biblionumber | html %]&amp;imagenumber=[% img.imagenumber | html %]">
[% IF ( imagenumber == img.imagenumber ) %]
<img class="selected" id="thumbnail_[% img.imagenumber | html %]" src="/cgi-bin/koha/catalogue/image.pl?imagenumber=[% img.imagenumber | html %]&amp;thumbnail=1" alt="Thumbnail" />
[% ELSE %]
<img id="thumbnail_[% img.imagenumber | html %]" src="/cgi-bin/koha/catalogue/image.pl?imagenumber=[% img.imagenumber | html %]&amp;thumbnail=1" alt="Thumbnail" />
[% END %]
</a>
<a href="#" class="remove" data-coverimg="[% img.imagenumber | html %]"><i class="fa fa-trash"></i> Delete image</a>
</li>
[% END # /IF img %]
[% END # /FOREACH img %]
</ul> <!-- /ul.thumbnails -->
</div> <!-- /.col-sm-3 -->
[% END # /IF images.size %]
</div> <!-- /.row -->
</main>
</div> <!-- /.col-sm-10.col-sm-push-2 -->
<div class="col-sm-2 col-sm-pull-10">
<aside>
[% INCLUDE 'tools-menu.inc' %]
</aside>
</div> <!-- /.col-sm-2.col-sm-pull-10 -->
</div> <!-- /.row -->
[% MACRO jsinclude BLOCK %]
[% Asset.js("js/tools-menu.js") | $raw %]
<script>
var interface = "[% interface | html %]";
var theme = "[% theme | html %]";
var biblionumber = "[% biblionumber | html %]";
$(document).ready(function(){
$("html").on("drop", function(e) {
e.preventDefault();
e.stopPropagation();
});
$("#zipfile").click(function(){
$("biblionumber_entry").hide();
});
$("#image").click(function(){
$("#biblionumber_entry").show();
});
$("#uploadfile").validate({
submitHandler: function(form) {
StartUpload();
return false;
}
});
$()
$("#filedrag").on("click", ".cancel_image", function(){
$("#click_to_select").show();
$("#messages").html("");
$("#fileToUpload").prop( "disabled", false );
$("#process_images, #fileuploadstatus").hide();
return false;
}).on("click", ".save_image", function(e){
e.preventDefault();
$("#processfile").submit();
});
$("html").on("drop", function(e) {
/* Prevent the default browser action when image is dropped */
/* i.e. don't navigate to a view of the local image */
e.preventDefault();
e.stopPropagation();
});
$('#filedrag').on('dragenter dragover dragleave', function (e) {
/* Handle various drag and drop events in "Drop files" area */
/* If event type is "dragover," add the "hover" class */
/* otherwise set no class name */
e.stopPropagation();
e.preventDefault();
e.target.className = (e.type == "dragover" ? "hover" : "");
});
$("#filedrag").click(function(){
/* Capture a click inside the drag and drop area */
/* Trigger the <input type="file"> action */
$("#fileToUpload").click();
});
// Drop
$('#filedrag').on('drop', function (e) {
e.stopPropagation();
e.preventDefault();
prepUpLoad(e);
});
// file selected
$("#fileToUpload").change(function(){
prepUpLoad();
});
$('.thumbnails .remove').on("click", function(e) {
e.preventDefault();
var result = confirm(_("Are you sure you want to delete this cover image?"));
var imagenumber = $(this).data("coverimg");
if ( result == true ) {
removeLocalImage(imagenumber);
}
});
});
function prepUpLoad( event ){
$("#click_to_select,#upload_results").hide();
$("#messages").html("");
if( event ){
var file = event.originalEvent.dataTransfer.files[0];
} else {
var file = $('#fileToUpload')[0].files[0];
}
$("#fileuploadstatus, #upload_options").show();
var fd = new FormData();
fd.append('file', file);
if( ParseFile( file ) ){
StartUpload( fd );
}
}
function StartUpload( fd ) {
$('#uploadform button.submit').prop('disabled',true);
$("#uploadedfileid").val('');
xhr= AjaxUpload( fd, $('#fileuploadprogress'), 'temp=1', cbUpload );
}
function cbUpload( status, fileid, errors ) {
if( status=='done' ) {
$("#uploadedfileid").val( fileid );
$('#fileToUpload').prop('disabled',true);
$("#process_images").show();
} else {
var errMsgs = [ _("Error code 0 not used"), _("File already exists"), _("Directory is not writeable"), _("Root directory for uploads not defined"), _("Temporary directory for uploads not defined") ];
var errCode = errors[$('#fileToUpload').prop('files')[0].name].code;
$("#fileuploadstatus").hide();
$("#fileuploadfailed").show();
$("#fileuploadfailed").text( _("Upload status: ") +
( status=='failed'? _("Failed") + " - (" + errCode + ") " + errMsgs[errCode]:
( status=='denied'? _("Denied"): status ))
);
$("#processfile").hide();
}
}
function AjaxUpload ( formData, progressbar, xtra, callback ) {
var xhr= new XMLHttpRequest();
var url= '/cgi-bin/koha/tools/upload-file.pl?' + xtra;
progressbar.val( 0 );
progressbar.next('.fileuploadpercent').text( '0' );
xhr.open('POST', url, true);
xhr.upload.onprogress = function (e) {
var p = Math.round( (e.loaded/e.total) * 100 );
progressbar.val( p );
progressbar.next('.fileuploadpercent').text( p );
}
xhr.onload = function (e) {
var data = JSON.parse( xhr.responseText );
if( data.status == 'done' ) {
progressbar.val( 100 );
progressbar.next('.fileuploadpercent').text( '100' );
}
callback( data.status, data.fileid, data.errors );
}
xhr.onerror = function (e) {
// Probably only fires for network failure
alert(_("An error occurred while uploading.") );
}
xhr.send( formData );
return xhr;
}
// output file information
function ParseFile(file) {
var valid = true;
if (file.type.indexOf("image") == 0) {
/* If the uploaded file is an image, show it */
var reader = new FileReader();
reader.onload = function(e) {
Output(
'<p><img class="cover_preview" src="' + e.target.result + '" /></p>'
);
}
$("#biblionumber_entry").show().find("input,label").addClass("required").prop("required", true );
$("#image").prop("checked", true ).change();
$("#zipfile").prop("checked", false );
reader.readAsDataURL(file);
} else if( file.type.indexOf("zip") > 0) {
Output(
'<p><i class="fa fa-file-archive-o" aria-hidden="true"></i></p>'
);
$("#biblionumber_entry").hide();
$("#image").prop("checked", false );
$("#zipfile").prop("checked", true );
} else {
Output(
'<div class="dialog alert"><strong>' + _("Error:") + ' </strong> ' + _("This tool only accepts ZIP files or GIF, JPEG, PNG, or XPM images.") + '</div>'
);
valid = false;
resetForm();
}
Output(
"<p>" + _("File name:") + " <strong>" + file.name + "</strong><br />" +
_("File type:") + " <strong>" + file.type + "</strong><br />" +
_("File size:") + " <strong>" + convertSize( file.size ) + "</strong>"
);
return valid;
}
// output information
function Output(msg) {
var m = document.getElementById("messages");
m.innerHTML = msg + m.innerHTML;
}
// Bytes conversion
function convertSize(size) {
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (size == 0) return '0 Byte';
var i = parseInt(Math.floor(Math.log(size) / Math.log(1024)));
return Math.round(size / Math.pow(1024, i), 2) + ' ' + sizes[i];
}
function removeLocalImage(imagenumber) {
var thumbnail = $("#imagenumber-" + imagenumber );
var copy = thumbnail.html();
thumbnail.find("img").css("opacity", ".2");
thumbnail.find("a.remove").html("<img style='display:inline-block' src='" + interface + "/" + theme + "/img/spinner-small.gif' alt='' />");
$.ajax({
url: "/cgi-bin/koha/svc/cover_images?action=delete&biblionumber=" + biblionumber + "&imagenumber=" + imagenumber,
success: function(data) {
$(data).each( function() {
if ( this.deleted == 1 ) {
location.href="/cgi-bin/koha/tools/upload-cover-image.pl?biblionumber=" + biblionumber;
} else {
thumbnail.html( copy );
alert(_("An error occurred on deleting this image"));
}
});
},
error: function(data) {
thumbnail.html( copy );
alert(_("An error occurred on deleting this image"));
}
});
}
function resetForm(){
$("#uploadpanel,#upload_options,#process_images").hide();
$("#click_to_select").show();
}
</script>
[% END %]
[% INCLUDE 'intranet-bottom.inc' %]