diff --git a/INSTALL b/INSTALL index 025bfc8270..4600eed3d7 100644 --- a/INSTALL +++ b/INSTALL @@ -316,6 +316,7 @@ Contents $ make install $ make install-mathjax-plugin ## optional $ make install-jquery-plugins ## optional + $ make install-plupload-plugin ## optional $ make install-ckeditor-plugin ## optional $ make install-pdfa-helper-files ## optional $ make install-mediaelement ## optional @@ -525,6 +526,14 @@ Contents Note that `unzip' is needed when installing jquery plugins. + $ make install-plupload-plugin ## optional + + This will automatically download and install in the proper + place plupload plugin, which is used in the Photos submission + interface. + + Note that `unzip' is needed when installing jquery plugins. + $ make install-ckeditor-plugin ## optional This will automatically download and install in the proper diff --git a/Makefile.am b/Makefile.am index 1707821a0b..b906bdab93 100644 --- a/Makefile.am +++ b/Makefile.am @@ -313,6 +313,72 @@ uninstall-pdfa-helper-files: @echo "** The PDF/A helper files were successfully uninstalled. **" @echo "***********************************************************" +install-plupload-plugin: + @echo "**********************************************************" + @echo "** Installing Uploader and other plugins **" + @echo "**********************************************************" + mkdir -p /tmp/invenio_js_frameworks + (cd /tmp/invenio_js_frameworks && \ + wget http://github.com/moxiecode/plupload/archive/v1.5.8.zip && \ + unzip v1.5.8.zip && \ + mkdir -p ${prefix}/var/www/js/plupload && \ + cp -r plupload-1.5.8/js/* ${prefix}/var/www/js/plupload/ && \ + cd /tmp && \ + rm -rf invenio_js_frameworks) + + @echo "***********************************************************" + @echo "** Uploader was successfully installed **" + @echo "***********************************************************" + + +uninstall-plupload-plugin: + @echo "***********************************************************" + @echo "** Unistalling Uploader **" + @echo "***********************************************************" + rm -rf /tmp/invenio_js_frameworks + rm -rf ${prefix}/var/www/js/plupload + @echo "***********************************************************" + @echo "** Uploader was successfully unistalled **" + @echo "***********************************************************" + + +install-crop-plugin: + @echo "**********************************************************" + @echo "** Installing Uploader and other plugins **" + @echo "**********************************************************" + rm -rf /tmp/invenio_js_frameworks + mkdir -p /tmp/invenio_js_frameworks + (cd /tmp/invenio_js_frameworks && \ + wget https://github.com/dimsemenov/Magnific-Popup/archive/master.zip && \ + unzip master.zip && \ + mkdir -p ${prefix}/var/www/static/magnific_popup && \ + cp -r Magnific-Popup-master/dist/* ${prefix}/var/www/static/magnific_popup && \ + wget https://github.com/tapmodo/Jcrop/zipball/v0.9.12 && \ + unzip v0.9.12 && \ + mkdir -p ${prefix}/var/www/static/crop && \ + cp -r tapmodo-Jcrop-1902fbc/js/* ${prefix}/var/www/static/crop && \ + cp -r tapmodo-Jcrop-1902fbc/css/* ${prefix}/var/www/static/crop && \ + wget -O json.zip https://github.com/douglascrockford/JSON-js/archive/master.zip && \ + unzip json.zip && \ + cp JSON-js-master/json2.js ${prefix}/var/www/js && \ + cd /tmp && \ + rm -rf invenio_js_frameworks) + + @echo "***********************************************************" + @echo "** Uploader was successfully installed **" + @echo "***********************************************************" + +uninstall-crop-plugin: + @echo "***********************************************************" + @echo "** Unistalling Uploader **" + @echo "***********************************************************" + rm -rf /tmp/invenio_js_frameworks + rm -rf ${prefix}/var/www/static/magnific_popup + rm -rf ${prefix}/var/www/static/crop + @echo "***********************************************************" + @echo "** Uploader was successfully unistalled **" + @echo "***********************************************************" + #Solrutils allows automatic installation, running and searching of an external Solr index. install-solrutils: @echo "***********************************************************" diff --git a/modules/bibsched/lib/tasklets/bst_create_icons.py b/modules/bibsched/lib/tasklets/bst_create_icons.py index 3b42eb78e4..07e64be8fe 100644 --- a/modules/bibsched/lib/tasklets/bst_create_icons.py +++ b/modules/bibsched/lib/tasklets/bst_create_icons.py @@ -98,18 +98,19 @@ def create_icons_for_record(recid, icon_sizes, icon_format_mappings=None, bibfiles = bibdoc.list_latest_files() bibdoc_formats = [bibfile.get_format() for bibfile in bibfiles] for bibfile in bibfiles: - if bibfile.get_subformat(): + if bibfile.get_subformat() not in ['', 'crop']: # this is a subformat, do nothing continue filepath = bibfile.get_full_path() #do not consider the dot in front of the format superformat = bibfile.get_format()[1:].lower() + if superformat.find(';') > -1: + superformat = superformat[:superformat.index(';')] bibfile_icon_formats = icon_format_mappings.get(superformat, icon_format_mappings.get('*', [superformat])) if isinstance(bibfile_icon_formats, str): bibfile_icon_formats = [bibfile_icon_formats] bibfile_icon_formats = [bibfile_icon_format for bibfile_icon_format in bibfile_icon_formats \ if bibfile_icon_format in CFG_ALLOWED_FILE_EXTENSIONS] - if add_default_icon and not default_icon_added_p: # add default icon try: @@ -129,7 +130,9 @@ def create_icons_for_record(recid, icon_sizes, icon_format_mappings=None, for icon_size in icon_sizes: washed_icon_size = icon_size.replace('>', '').replace('<', '').replace('^', '').replace('!', '') for icon_format in bibfile_icon_formats: - new_format = '.%s;%s-%s' % (icon_format, CFG_BIBDOCFILE_DEFAULT_ICON_SUBFORMAT, washed_icon_size) + # change the default subformat if it's crop + prefix = 'crop' if bibfile.get_subformat() == 'crop' else CFG_BIBDOCFILE_DEFAULT_ICON_SUBFORMAT + new_format = '.%s;%s-%s' % (icon_format, prefix, washed_icon_size) if new_format in bibdoc_formats: # the subformat already exists, do nothing continue @@ -239,7 +242,7 @@ def bst_create_icons(recid, icon_sizes, icon_format_mappings=None, else: write_message("Recid %s DONE." % recid) - task_update_progress("Done %d out of %d." % (i, len(recids))) + task_update_progress("Done %d out of %d." % (i+1, len(recids))) task_sleep_now_if_required(can_stop_too=True) if updated: diff --git a/modules/miscutil/lib/Makefile.am b/modules/miscutil/lib/Makefile.am index 8bf67da19c..8a58418afd 100644 --- a/modules/miscutil/lib/Makefile.am +++ b/modules/miscutil/lib/Makefile.am @@ -101,7 +101,8 @@ pylib_DATA = __init__.py \ hepdatadisplayutils.py \ hepdatautils_unit_tests.py \ filedownloadutils.py \ - filedownloadutils_unit_tests.py + filedownloadutils_unit_tests.py \ + get_image_size.py jsdir=$(localstatedir)/www/js diff --git a/modules/miscutil/lib/get_image_size.py b/modules/miscutil/lib/get_image_size.py new file mode 100644 index 0000000000..850a161cd0 --- /dev/null +++ b/modules/miscutil/lib/get_image_size.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +#------------------------------------------------------------------------------- +# Name: get_image_size +# Purpose: extract image dimentions given a file path +# +# Author: Paulo Scardine (based on code from Emmanuel VAÏSSE) +# +# Created: 26/09/2013 +# Copyright: (c) Paulo Scardine 2013 +# Licence: MIT +#------------------------------------------------------------------------------- +#!/usr/bin/env python +import os +import struct + +FILE_UNKNOWN = "Sorry, don't know how to get size for this file." + +class UnknownImageFormat(Exception): + pass + +def get_image_size(file_path): + """ + Return (width, height) for a given img file content - no external + dependencies except the os and struct builtin modules + """ + size = os.path.getsize(file_path) + + with open(file_path) as input: + height = -1 + width = -1 + data = input.read(25) + msg = " raised while trying to decode as JPEG." + + if (size >= 10) and data[:6] in ('GIF87a', 'GIF89a'): + # GIFs + w, h = struct.unpack("= 24) and data.startswith('\211PNG\r\n\032\n') + and (data[12:16] == 'IHDR')): + # PNGs + w, h = struct.unpack(">LL", data[16:24]) + width = int(w) + height = int(h) + elif (size >= 16) and data.startswith('\211PNG\r\n\032\n'): + # older PNGs + w, h = struct.unpack(">LL", data[8:16]) + width = int(w) + height = int(h) + elif (size >= 2) and data.startswith('\377\330'): + # JPEG + input.seek(0) + input.read(2) + b = input.read(1) + try: + while (b and ord(b) != 0xDA): + while (ord(b) != 0xFF): b = input.read(1) + while (ord(b) == 0xFF): b = input.read(1) + if (ord(b) >= 0xC0 and ord(b) <= 0xC3): + input.read(3) + h, w = struct.unpack(">HH", input.read(4)) + break + else: + input.read(int(struct.unpack(">H", input.read(2))[0])-2) + b = input.read(1) + width = int(w) + height = int(h) + except struct.error: + raise UnknownImageFormat("StructError" + msg) + except ValueError: + raise UnknownImageFormat("ValueError" + msg) + except Exception as e: + raise UnknownImageFormat(e.__class__.__name__ + msg) + elif size >= 2: + #see http://en.wikipedia.org/wiki/ICO_(file_format) + input.seek(0) + reserved = input.read(2) + if 0 != struct.unpack(" 1: + import warnings + warnings.warn("ICO File contains more than one image") + #http://msdn.microsoft.com/en-us/library/ms997538.aspx + w = input.read(1) + h = input.read(1) + width = ord(w) + height = ord(h) + else: + raise UnknownImageFormat(FILE_UNKNOWN) + + return width, height + diff --git a/modules/websubmit/lib/functions/Move_Photos_to_Storage.py b/modules/websubmit/lib/functions/Move_Photos_to_Storage.py index 1f0839cc3c..2edce8cefa 100644 --- a/modules/websubmit/lib/functions/Move_Photos_to_Storage.py +++ b/modules/websubmit/lib/functions/Move_Photos_to_Storage.py @@ -1,5 +1,5 @@ ## This file is part of Invenio. -## Copyright (C) 2010, 2011, 2012 CERN. +## Copyright (C) 2010, 2011, 2012, 2013, 2014 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as @@ -24,20 +24,15 @@ JQuery: - jquery.min.js - JQuery UI: - - jquery-ui.min.js - - UI "base" theme: - - jquery.ui.slider.css - - jquery.ui.core.css - - jquery.ui.theme.css - - images - - Uploadify 2.0.1 (JQuery plugin): - - jquery.uploadify.min.js - - sfwobject.js - - uploadify.css - - cancel.png - - uploadify.swf, uploadify.allglyphs.swf and uploadify.fla + Plupload 1.5.8 (jQuery plugin) + +Note +---- +Please download http://www.plupload.com/download +to your /js folder. The stracture should be + - /js/plupload/plupload.full.js + - /js/plupload/jquery.plupload.queue/jquery.plupload.queue.js +(or run $make install-plupload-plugin from the root folder of the Invenio sources) """ import os @@ -49,8 +44,15 @@ from invenio.bibdocfile import BibRecDocs, InvenioBibDocFileError from invenio.config import CFG_BINDIR, CFG_SITE_URL from invenio.dbquery import run_sql -from invenio.websubmit_icon_creator import create_icon, InvenioWebSubmitIconCreatorError +from invenio.websubmit_icon_creator import create_icon, \ + create_crop, \ + InvenioWebSubmitIconCreatorError, \ + InvenioWebSubmitCropCreatorError + +from invenio.bibdocfile import decompose_file from invenio.bibdocfile_config import CFG_BIBDOCFILE_DEFAULT_ICON_SUBFORMAT +from invenio.websubmit_config import CFG_WEBSUBMIT_CROP_PREVIEW_SIZE + def Move_Photos_to_Storage(parameters, curdir, form, user_info=None): """ @@ -124,10 +126,19 @@ def Move_Photos_to_Storage(parameters, curdir, form, user_info=None): for value in PHOTO_MANAGER_NEW \ if '/' in value]) - ## Create an instance of BibRecDocs for the current recid(sysno) + PHOTO_MANAGER_CROP = read_param_file(curdir, + 'PHOTO_MANAGER_CROPPING', + split_lines=True) + photo_manager_crop_dict = dict([value.split('/', 1) + for value in PHOTO_MANAGER_CROP + if '/' in value]) + + # Create an instance of BibRecDocs for the current recid(sysno) bibrecdocs = BibRecDocs(sysno) for photo_id in photo_manager_order_list: photo_description = read_param_file(curdir, 'PHOTO_MANAGER_DESCRIPTION_' + photo_id) + # Read also fr description + photo_description_fr = read_param_file(curdir, 'PHOTO_MANAGER_DESCRIPTION_' + photo_id+ '_fr') # We must take different actions depending if we deal with a # file that already exists, or if it is a new file if photo_id in photo_manager_new_dict.keys(): @@ -135,32 +146,72 @@ def Move_Photos_to_Storage(parameters, curdir, form, user_info=None): if photo_id not in photo_manager_delete_list: filename = photo_manager_new_dict[photo_id] filepath = os.path.join(curdir, 'files', str(user_info['uid']), - 'NewFile', filename) + 'file', filename) icon_filename = os.path.splitext(filename)[0] + ".gif" - fileiconpath = os.path.join(curdir, 'icons', str(user_info['uid']), - 'NewFile', icon_filename) + fileiconpath = os.path.join(curdir, 'icons', + str(user_info['uid']), 'file', + icon_filename) # Add the file if os.path.exists(filepath): _do_log(curdir, "Adding file %s" % filepath) - bibdoc = bibrecdocs.add_new_file(filepath, doctype="picture", never_fail=True) + bibdoc = bibrecdocs.add_new_file(filepath, + doctype="picture", + never_fail=True) + has_added_default_icon_subformat_p = False + # check if there is cropversion for this image + has_crop = photo_id in photo_manager_crop_dict.keys() + if has_crop: + # ok was the vars + dimensions = dict(x.split('=') + for x in + photo_manager_crop_dict[photo_id].split('&')) + # perfect let's now create the cropped version + # in order to pass it to the icon_sized bellow + # and have the same sizes for the cropped version + try: + # OK lets create the crop DAH! + (crop_path, crop_name) = create_crop( + filepath, + dimensions['w'].split('.')[0], + dimensions['h'].split('.')[0], + dimensions['x'].split('.')[0], + dimensions['y'].split('.')[0] + ) + _do_log(curdir, + "Crop file and name %s: %s" + % (crop_path, crop_name)) + crop_file_path = os.path.join(crop_path, crop_name) + # Add a new file format + # Get first the enxtension + (dummy, dummy, ext) = decompose_file(crop_file_path) + bibdoc.add_file_new_format(crop_file_path, + docformat='%s;crop' % (ext), + ) + except InvenioWebSubmitCropCreatorError, e: + _do_log(curdir, + "Cropped couldn't be created to %s: %s" + % (filepath, e)) + pass + has_added_default_icon_subformat_p = False for icon_size in icon_sizes: # Create icon if needed try: (icon_path, icon_name) = create_icon( - { 'input-file' : filepath, - 'icon-name' : icon_filename, - 'icon-file-format' : icon_format, - 'multipage-icon' : False, - 'multipage-icon-delay' : 100, - 'icon-scale' : icon_size, # Resize only if width > 300 - 'verbosity' : 0, - }) + {'input-file': filepath, + 'icon-name': icon_filename, + 'icon-file-format': icon_format, + 'multipage-icon': False, + 'multipage-icon-delay': 100, + 'icon-scale': icon_size, + 'verbosity': 0, + }) fileiconpath = os.path.join(icon_path, icon_name) except InvenioWebSubmitIconCreatorError, e: - _do_log(curdir, "Icon could not be created to %s: %s" % (filepath, e)) - pass + _do_log(curdir, + "Icon could not be created to %s: %s" + % (filepath, e)) if os.path.exists(fileiconpath): try: if not has_added_default_icon_subformat_p: @@ -179,20 +230,67 @@ def Move_Photos_to_Storage(parameters, curdir, form, user_info=None): for file_format in [bibdocfile.get_format() \ for bibdocfile in bibdoc.list_latest_files()]: bibdoc.set_comment(photo_description, file_format) + if photo_description_fr: + bibdoc.set_description(photo_description_fr, file_format) _do_log(curdir, "Added comment %s" % photo_description) else: # Existing file bibdocname = bibrecdocs.get_docname(int(photo_id)) + bibdoc = bibrecdocs.get_bibdoc(bibdocname) if photo_id in photo_manager_delete_list: # In principle we should not get here. but just in case... bibrecdocs.delete_bibdoc(bibdocname) _do_log(curdir, "Deleted %s" % bibdocname) else: - bibdoc = bibrecdocs.get_bibdoc(bibdocname) - for file_format in [bibdocfile.get_format() \ - for bibdocfile in bibdoc.list_latest_files()]: - bibdoc.set_comment(photo_description, file_format) - _do_log(curdir, "Added comment %s" % photo_description) + # Check if a new crop + has_crop = photo_id in photo_manager_crop_dict.keys() + _do_log(curdir, "I'm here on modification for photo_id %s" % photo_id) + if has_crop: + # ok was the vars + dimensions = dict(x.split('=') + for x in + photo_manager_crop_dict[photo_id].split('&')) + # perfect let's now create the cropped version + # in order to pass it to the icon_sized bellow + # and have the same sizes for the cropped version + try: + filepath = [file.get_full_path() for file in bibdoc.list_all_files() if file.get_subformat() == ''][0] + # OK lets create the crop DAH! + (crop_path, crop_name) = create_crop( + filepath, + dimensions['w'].split('.')[0], + dimensions['h'].split('.')[0], + dimensions['x'].split('.')[0], + dimensions['y'].split('.')[0] + ) + + crop_file_path = os.path.join(crop_path, crop_name) + + # Add a new file format + (dummy, dummy, ext) = decompose_file(crop_file_path) + + # Get first the enxtension + format_name = '%s;crop' %(ext) + + # Check if the format already exists + if bibdoc.format_already_exists_p(format_name): + match_crop_re = re.compile('crop') + bibdoc.delete_icon(match_crop_re) + # Add the crop + bibdoc.add_file_new_format(crop_file_path, docformat=format_name) + except InvenioWebSubmitCropCreatorError, e: + _do_log(curdir, + "Cropped couldn't be created to %s: %s" + % (filepath, e)) + pass + + # Update the descriptions + if bibdoc: + for file_format in [bibdocfile.get_format() \ + for bibdocfile in bibdoc.list_latest_files()]: + bibdoc.set_comment(photo_description, file_format) + bibdoc.set_description(photo_description_fr, file_format) + _do_log(curdir, "Added comment %s" % photo_description) # Now delete requeted files for photo_id in photo_manager_delete_list: @@ -212,9 +310,9 @@ def Move_Photos_to_Storage(parameters, curdir, form, user_info=None): # Delete the HB BibFormat cache in the DB, so that the fulltext # links do not point to possible dead files run_sql("DELETE LOW_PRIORITY from bibfmt WHERE format='HB' AND id_bibrec=%s", (sysno,)) - return "" + def read_param_file(curdir, param, split_lines=False): "Helper function to access files in submission dir" param_value = "" @@ -229,9 +327,9 @@ def read_param_file(curdir, param, split_lines=False): fd.close() except Exception, e: _do_log(curdir, 'Could not read %s: %s' % (param, e)) - pass return param_value + def _do_log(log_dir, msg): """ Log what we have done, in case something went wrong. @@ -241,9 +339,10 @@ def _do_log(log_dir, msg): """ log_file = os.path.join(log_dir, 'performed_actions.log') file_desc = open(log_file, "a+") - file_desc.write("%s --> %s\n" %(time.strftime("%Y-%m-%d %H:%M:%S"), msg)) + file_desc.write("%s --> %s\n" % (time.strftime("%Y-%m-%d %H:%M:%S"), msg)) file_desc.close() + def get_session_id(req, uid, user_info): """ Returns by all means the current session id of the user. @@ -251,7 +350,7 @@ def get_session_id(req, uid, user_info): Raises ValueError if cannot be found """ # Get the session id - ## This can be later simplified once user_info object contain 'sid' key + # This can be later simplified once user_info object contain 'sid' key session_id = None try: try: @@ -265,6 +364,7 @@ def get_session_id(req, uid, user_info): return session_id + def create_photos_manager_interface(sysno, session_id, uid, doctype, indir, curdir, access, can_delete_photos=True, @@ -278,6 +378,8 @@ def create_photos_manager_interface(sysno, session_id, uid, """ Creates and returns the HTML of the photos manager interface for submissions. + Some of the parameters have been deprecated, and are not used, but + were kept for maintaining backwards compatibility. @param sysno: current record id @param session_id: user session_id (as retrieved by get_session_id(...) ) @@ -286,16 +388,19 @@ def create_photos_manager_interface(sysno, session_id, uid, @param indir: submission "indir" @param curdir: submission "curdir" @param access: submission "access" - @param can_delete_photos: if users can delete photos - @param can_reorder_photos: if users can reorder photos + @param can_delete_photos (deprecated, not used): if users can delete photos + @param can_reorder_photos (deprecated, not used): if users can reorder photos @param can_upload_photos: if users can upload photos - @param editor_width: width (in pixels) of the editor - @param editor_height: height (in pixels) of the editor - @param initial_slider_value: initial value of the photo size slider - @param max_slider_value: max value of the photo size slider - @param min_slider_value: min value of the photo size slider + @param editor_width (deprecated, not used): width (in pixels) of the editor + @param editor_height (deprecated, not used): height (in pixels) of the editor + @param initial_slider_value (deprecated, not used): initial value of the photo size slider + @param max_slider_value (deprecated, not used): max value of the photo size slider + @param min_slider_value (deprecated, not used): min value of the photo size slider """ + out = '' + PHOTO_MANAGER_CROP = read_param_file(curdir, 'PHOTO_MANAGER_CROP', split_lines=True) + photo_manager_crop_dict = dict([value.split('/', 1) for value in PHOTO_MANAGER_CROP if '/' in value]) PHOTO_MANAGER_ICONS = read_param_file(curdir, 'PHOTO_MANAGER_ICONS', split_lines=True) photo_manager_icons_dict = dict([value.split('/', 1) for value in PHOTO_MANAGER_ICONS if '/' in value]) @@ -306,230 +411,622 @@ def create_photos_manager_interface(sysno, session_id, uid, PHOTO_MANAGER_NEW = read_param_file(curdir, 'PHOTO_MANAGER_NEW', split_lines=True) photo_manager_new_dict = dict([value.split('/', 1) for value in PHOTO_MANAGER_NEW if '/' in value]) photo_manager_descriptions_dict = {} - + photo_manager_descriptions_dict_fr = {} + photo_manager_photo_fullnames = {} + photo_manager_meta = {} # Compile a regular expression that can match the "default" icon, # and not larger version. CFG_BIBDOCFILE_ICON_SUBFORMAT_RE_DEFAULT = re.compile(CFG_BIBDOCFILE_DEFAULT_ICON_SUBFORMAT + '\Z') + crop_link = """ + %(crop_text)s + """ + # Load the existing photos from the DB if we are displaying # this interface for the first time, and if a record exists if sysno and not PHOTO_MANAGER_ORDER: bibarchive = BibRecDocs(sysno) for doc in bibarchive.list_bibdocs(): if doc.get_icon() is not None: - original_url = doc.list_latest_files()[0].get_url() doc_id = str(doc.get_id()) - icon_url = doc.get_icon(subformat_re=CFG_BIBDOCFILE_ICON_SUBFORMAT_RE_DEFAULT).get_url() # Get "default" icon + photo_manager_meta[doc_id] = {} + icon_url = doc.get_icon(subformat_re=CFG_BIBDOCFILE_ICON_SUBFORMAT_RE_DEFAULT).get_url() description = "" + description_fr = "" + + sizes = dict([(x.get_subformat() if x.get_subformat() != '' else 'master', + (x.get_url(), x.get_full_path())) for x in doc.list_all_files()]) + + # Check if image is cropped + is_cropped = True if 'crop' in sizes.keys() else False + photo_manager_meta[doc_id].update({'is_cropped': is_cropped}) + + # if the master format is missing the image cannot be cropped + image_can_be_cropped = True if 'master' in sizes.keys() and sizes['master'][1] else False + photo_manager_meta[doc_id].update({'can_cropped': image_can_be_cropped}) + + if image_can_be_cropped: + try: + image_preview_size = sizes[CFG_WEBSUBMIT_CROP_PREVIEW_SIZE][0] + except: + image_preview_size = sizes['master'][0] + + photo_manager_meta[doc_id].update({'preview_image': image_preview_size}) + photo_manager_meta[doc_id].update({'fullpath': sizes['master'][1]}) + for bibdoc_file in doc.list_latest_files(): - #format = bibdoc_file.get_format().lstrip('.').upper() - #url = bibdoc_file.get_url() - #photo_files.append((format, url)) + # format = bibdoc_file.get_format().lstrip('.').upper() + # url = bibdoc_file.get_url() + # photo_files.append((format, url)) if not description and bibdoc_file.get_comment(): description = escape(bibdoc_file.get_comment()) - name = bibarchive.get_docname(doc.id) + if not description_fr and bibdoc_file.get_description(): + description_fr = escape(bibdoc_file.get_description()) + photo_manager_descriptions_dict[doc_id] = description + photo_manager_descriptions_dict_fr[doc_id] = description_fr photo_manager_icons_dict[doc_id] = icon_url - photo_manager_order_list.append(doc_id) # FIXME: respect order + try: + photo_manager_photo_fullnames[doc_id] = bibdoc_file.fullname + except: + photo_manager_photo_fullnames[doc_id] = "" + photo_manager_order_list.append(doc_id) # Prepare the list of photos to display. photos_img = [] for doc_id in photo_manager_order_list: - if not photo_manager_icons_dict.has_key(doc_id): + + if doc_id not in photo_manager_icons_dict.keys(): continue + icon_url = photo_manager_icons_dict[doc_id] + fullname = photo_manager_photo_fullnames[doc_id] + if PHOTO_MANAGER_ORDER: # Get description from disk only if some changes have been done description = escape(read_param_file(curdir, 'PHOTO_MANAGER_DESCRIPTION_' + doc_id)) + description_fr = escape(read_param_file(curdir, 'PHOTO_MANAGER_DESCRIPTION_' + doc_id+'_fr')) else: description = escape(photo_manager_descriptions_dict[doc_id]) + description_fr = escape(photo_manager_descriptions_dict_fr[doc_id]) + + # Try to read the crop parameter + + link_to_crop = '' + if photo_manager_meta[doc_id]['can_cropped']: + link_to_crop = crop_link % {'crop_text': 'Cropped (change)' if photo_manager_meta[doc_id]['is_cropped'] else 'Crop', + 'icon_1440': photo_manager_meta[doc_id]['preview_image'], + 'fullpath': photo_manager_meta[doc_id]['fullpath'], + 'fullname': fullname, + 'doc_id': doc_id, + 'icon_url': icon_url} photos_img.append(''' -
  • -
    -
    - - -
    -
    - -
    +
    +
    + +
    + +
    +
    + %(crop_link)s + %(fullname)s + + +
    +
    -
  • ''' % \ - {'initial_slider_value': initial_slider_value, - 'doc_id': doc_id, - 'icon_url': icon_url, - 'description': description}) + ''' % {'fullname': fullname, + 'doc_id': doc_id, + 'CFG_SITE_URL': CFG_SITE_URL, + 'crop_link': link_to_crop, + 'icon_url': icon_url, + 'description_fr': description_fr, + 'description': description}) out += ''' - - - + + + + + + + + + + + + + + - -
      %(photos_img)s
    -
    -
    -
    - - - - - - - - - -
    - - +
    +
    +
    +
    +
    +

    Drop your images or click to select.

    +
    +
    %(photos_img)s
    +
    +
    - - + +
    ''' % {'CFG_SITE_URL': CFG_SITE_URL, - #'curdir': cgi.escape(quote(curdir, safe="")),#quote(curdir, safe=""), - 'uid': uid, 'access': quote(access, safe=""), 'doctype': quote(doctype, safe=""), 'indir': quote(indir, safe=""), @@ -538,16 +1035,11 @@ def create_photos_manager_interface(sysno, session_id, uid, 'PHOTO_MANAGER_ORDER': '\n'.join(photo_manager_order_list), 'PHOTO_MANAGER_DELETE': '\n'.join(photo_manager_delete_list), 'PHOTO_MANAGER_NEW': '\n'.join([key + '/' + value for key, value in photo_manager_new_dict.iteritems()]), - 'initial_slider_value': initial_slider_value, - 'max_slider_value': max_slider_value, - 'min_slider_value': min_slider_value, 'photos_img': '\n'.join(photos_img), 'hide_photo_viewer': (len(photos_img) == 0 and len(photo_manager_new_dict.keys()) == 0) and 'display:none;' or '', 'delete_hover_class': can_delete_photos and "#sortable li div.imgBlock:hover .hidden {display:inline;}" or '', - 'can_reorder_photos': can_reorder_photos and 'true' or 'false', 'can_upload_photos': can_upload_photos and 'true' or 'false', 'upload_display': not can_upload_photos and 'display: none' or '', - 'editor_width_style': editor_width and 'width:%spx;' % editor_width or '', - 'editor_height_style': editor_height and 'height:%spx;' % editor_height or ''} + } return out diff --git a/modules/websubmit/lib/websubmit_config.py b/modules/websubmit/lib/websubmit_config.py index 86038d91b3..4edc670bdf 100644 --- a/modules/websubmit/lib/websubmit_config.py +++ b/modules/websubmit/lib/websubmit_config.py @@ -19,7 +19,6 @@ __revision__ = "$Id$" -import re ## test: test = "FALSE" @@ -54,6 +53,12 @@ ## Prefix for video uploads, Garbage Collector CFG_WEBSUBMIT_TMP_VIDEO_PREFIX = "video_upload_" +# Which preview size to display for cropping +# `master` for the master version +# CFG_WEBSUBMIT_CROP_PREVIEW_SIZE = "master" +CFG_WEBSUBMIT_CROP_PREVIEW_SIZE = "icon-1440" + + class InvenioWebSubmitFunctionError(Exception): """This exception should only ever be raised by WebSubmit functions. It will be caught and handled by the WebSubmit core itself. @@ -188,6 +193,28 @@ def __str__(self): """ return str(self.value) +class InvenioWebSubmitCropCreatorError(Exception): + """This exception should be raised by websubmit_icon_creator when an + error is encoutered that prevents an icon from being created. + When caught, this exception should be used to stop processing with a + failure signal. + + Extends: Exception. + """ + def __init__(self, value): + """Set the internal "value" attribute to that of the passed "value" + parameter. + @param value: (string) - a string to write to the log. + """ + Exception.__init__(self) + self.value = value + def __str__(self): + """Return oneself as a string (actually, return the contents of + self.value). + @return: (string) + """ + return str(self.value) + class InvenioWebSubmitFileMetadataRuntimeError(Exception): """This exception should be raised by websubmit_file_metadata plugins when an error is encoutered that prevents a extracting/updating a file. diff --git a/modules/websubmit/lib/websubmit_icon_creator.py b/modules/websubmit/lib/websubmit_icon_creator.py index 12d00fd8a2..9e29caa559 100644 --- a/modules/websubmit/lib/websubmit_icon_creator.py +++ b/modules/websubmit/lib/websubmit_icon_creator.py @@ -49,7 +49,8 @@ CFG_PATH_PDFTK, \ CFG_PATH_CONVERT from invenio.shellutils import escape_shell_arg -from invenio.websubmit_config import InvenioWebSubmitIconCreatorError +from invenio.websubmit_config import InvenioWebSubmitIconCreatorError, \ + InvenioWebSubmitCropCreatorError CFG_ALLOWED_FILE_EXTENSIONS = ["pdf", "gif", "jpg", \ "jpeg", "ps", "png", "bmp", \ @@ -495,7 +496,106 @@ def create_icon(options): ## icon file to the caller: return (path_workingdir, icon_name) +## ***** Functions Specific to crop images: ***** + +def make_crop(path_workingdir, \ + filename, \ + filetype, \ + width, \ + height, \ + pos_x, \ + pos_y): + """ Makes the crop """ + # Create the crop_name + crop_name = "crop-%s" % filename + if len(crop_name.split(".")) > 1: + ## The icon file name has an extension - strip it and add the icon + ## file type extension: + raw_name = crop_name[:crop_name.rfind(".")] + if raw_name != "": + crop_name = "%s.%s" % (raw_name, filetype) + else: + ## It would appear that the file had no extension and that its + ## name started with a period. Just use the original name with + ## the icon file type's suffix: + crop_name = "%s.%s" % (crop_name, filetype) + + # Make the command + cmd = "%(convert)s %(file-path)s -crop %(width)sx%(height)s+%(pos_x)s+%(pos_y)s %(crop-path)s 2>/dev/null" % \ + { + 'convert' : CFG_PATH_CONVERT, + 'width' : width, + 'height' : height, + 'pos_x' : pos_x, + 'pos_y' : pos_y, + 'file-path' : escape_shell_arg("%s/%s" \ + % (path_workingdir, \ + filename)), + 'crop-path' : escape_shell_arg("%s/%s" \ + % (path_workingdir, \ + crop_name)), + } + errcode_create_crop = os.system(cmd) + + # Check if the crop was successful + if errcode_create_crop or \ + not os.access("%s/%s" %(path_workingdir, crop_name), os.F_OK): + msg = "Error: Unable to create and crop for file [%s/%s] (error " \ + "code [%s].)" \ + %(path_workingdir, filename, errcode_create_crop) + raise InvenioWebSubmitCropCreatorError(msg) + + # The crop was successfully created. Return the name + return crop_name + +def create_crop(input_file, width, height, pos_x=0, pos_y=0): + """ Creates the crop """ + + # Check width + if width in (None, 0, ""): + msg = "Error width must be greater than 0" + raise InvenioWebSubmitCropCreatorError(msg) + # Check height + if height in (None, 0, ""): + msg = "Error height must be greater than 0" + raise InvenioWebSubmitCropCreatorError(msg) + # Check filename + if input_file in (None, ""): + msg = "Error input_file couldn't be empty" + raise InvenioWebSubmitCropCreatorError(msg) + + # Lets find the extension + tmp_file_extension = input_file.split('.')[-1].split(';')[0] + if tmp_file_extension.lower() not in CFG_ALLOWED_FILE_EXTENSIONS: + # Sorry we not support this extension raise an error + msg = "Error: icons can be only be created from %s files, " \ + "not [%s]." % (str(CFG_ALLOWED_FILE_EXTENSIONS), \ + tmp_file_extension.lower()) + raise InvenioWebSubmitCropCreatorError(msg) + else: + filetype = tmp_file_extension.lower() + # Brilliant just create a working directory and copy the input-file + # there + path_workingdir = create_working_directory() + try: + base_source_file = \ + copy_file_to_directory(input_file, path_workingdir) + except IOError, err: + msg = "Crop creation failed: unable to copy the source image file " \ + "to the working directory. Got this error: [%s]" % str(err) + raise InvenioWebSubmitCropCreatorError(msg) + + # Make the crop and get the crop-name + crop_name = make_crop(path_workingdir, \ + base_source_file, \ + filetype, \ + width, \ + height, \ + pos_x, \ + pos_y) + + return (path_workingdir, crop_name) ## ***** Functions Specific to CLI calling of the program: ***** diff --git a/modules/websubmit/lib/websubmit_webinterface.py b/modules/websubmit/lib/websubmit_webinterface.py index 01614b752a..e9974e7fdb 100644 --- a/modules/websubmit/lib/websubmit_webinterface.py +++ b/modules/websubmit/lib/websubmit_webinterface.py @@ -1,5 +1,5 @@ ## This file is part of Invenio. -## Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 CERN. +## Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as @@ -51,7 +51,10 @@ decompose_file, propose_next_docname from invenio.errorlib import register_exception from invenio.htmlutils import is_html_text_editor_installed -from invenio.websubmit_icon_creator import create_icon, InvenioWebSubmitIconCreatorError +from invenio.websubmit_icon_creator import create_icon, \ + create_crop, \ + InvenioWebSubmitIconCreatorError + from invenio.ckeditor_invenio_connector import process_CKEditor_upload, send_response import invenio.template websubmit_templates = invenio.template.load('websubmit') @@ -63,10 +66,140 @@ from invenio.websubmit_engine import home, action, interface, endaction, makeCataloguesTable + class WebInterfaceSubmitPages(WebInterfaceDirectory): - _exports = ['summary', 'sub', 'direct', '', 'attachfile', 'uploadfile', \ - 'getuploadedfile', 'upload_video', ('continue', 'continue_')] + _exports = ['summary', 'sub', 'direct', '', 'attachfile', 'uploadfile', + 'getuploadedfile', 'upload_video', ('continue', 'continue_'), + 'crop_image'] + + def crop_image(self, req, form): + """ + Creates the cropped image API + ============================= + """ + # First of all wash the urls + argd = wash_urlargd(form, { + 'doctype': (str, ''), + 'access': (str, ''), + 'indir': (str, ''), + 'session_id': (str, ''), + 'action': (str, ''), + 'key': (str, 'file'), + 'filename': (str, None), + 'width': (str, None), + 'height': (str, None), + 'pos_x': (str, None), + 'pos_y': (str, None) + }) + # Wash the user id from request + uid = getUid(req) + # Init a dictionary that holds the file directories + # aka files, icons, crop etc. + directories = {} + # Common prefix for current directory + common_dir_prefix = os.path.join(CFG_WEBSUBMIT_STORAGEDIR, + argd['indir'], + argd['doctype'], + argd['access']) + # Common suffix for current directory + common_dir_suffix = os.path.join(str(uid), argd['key']) + # Asbolute path for icons directory + directories['icons'] = os.path.join(common_dir_prefix, + 'icons', + common_dir_suffix) + # Asbolute path for files directory + directories['files'] = os.path.join(common_dir_prefix, + 'files', + common_dir_suffix) + # Asbolute path for crop directory + directories['crop'] = os.path.join(common_dir_prefix, + 'crop', + common_dir_suffix) + + # Check if crop folder exists + if not os.path.exists(directories['crop']): + try: + os.makedirs(directories['crop']) + except OSError, e: + if e.errno != errno.EEXIST: + register_exception(req=req, alert_admin=True) + raise apache.SERVER_RETURN(apache.HTTP_FORBIDDEN) + + def create_the_cropped_file(): + """ + It creates the cropped file + """ + (path, name) = create_crop(os.path.join(directories['files'], + argd['filename']), + argd['width'].strip(), + argd['height'].strip(), + argd['pos_x'].strip(), + argd['pos_y'].strip()) + return (path, name) + + def create_the_ready_to_crop_version(): + """ + It creates a 1440 version of the original image + """ + # Finally make the cropped version + (path, name) = create_icon({ + 'input-file': os.path.join(directories['files'], argd['filename']), + 'icon-name': "resized-%s" % argd['filename'], + 'icon-file-format': 'gif', + 'multipage-icon': False, + 'multipage-icon-delay': 100, + 'icon-scale': "1440>", + 'verbosity': 0, + }) + return (path, name) + + def make_security_checks(): + """ + It checks if the user has permission to submit + """ + return True + + # Ok let's first check if the user could submit + if not make_security_checks(): + # Raise 401 + raise apache.SERVER_RETURN(apache.HTTP_UNAUTHORIZED) + else: + # else is clean lets continue + # which action? + if argd['action'] == 'create_ready_to_crop': + # Check if the required field `filename` and `absPath` are there + # If not raise 400 + if argd['filename'] is None: + raise apache.SERVER_RETURN(apache.HTTP_BAD_REQUEST) + + # Make the cropped version + (resized_path, resized_name) = create_the_ready_to_crop_version() + # Move the cropped file to submission directory + # Move the crop-icon to submit dir + os.rename(os.path.join(resized_path, resized_name), + os.path.join(directories['icons'], resized_name)) + # return the absPath of the file + return resized_name + + elif argd['action'] == 'create_the_cropped_version': + + if any(argd.get(key) in (None, '') for key in ('filename', 'width', + 'height', 'pos_x', 'pos_y')): + raise apache.SERVER_RETURN(apache.HTTP_BAD_REQUEST) + + # Do the crop + (cropped_path, cropped_name) = create_the_cropped_file() + # Move the cropped file to submission directory + # Move the crop-icon to submit dir + os.rename(os.path.join(cropped_path, cropped_name), + os.path.join(directories['crop'], cropped_name)) + # return the absPath of the file + return cropped_name + + else: + # Raise 406 + raise apache.SERVER_RETURN(apache.HTTP_NOT_ACCEPTABLE) def uploadfile(self, req, form): """ @@ -91,6 +224,7 @@ def uploadfile(self, req, form): 'indir': (str, ''), 'session_id': (str, ''), 'rename': (str, ''), + 'replace': (str, '') }) curdir = None @@ -105,29 +239,14 @@ def uploadfile(self, req, form): argd['access']) user_info = collect_user_info(req) - if form.has_key("session_id"): - # Are we uploading using Flash, which does not transmit - # cookie? The expect to receive session_id as a form - # parameter. First check that IP addresses do not - # mismatch. A ValueError will be raises if there is - # something wrong - session = get_session(req=req, sid=argd['session_id']) - try: - session = get_session(req=req, sid=argd['session_id']) - except ValueError, e: - raise apache.SERVER_RETURN(apache.HTTP_BAD_REQUEST) + uid = user_info['uid'] - # Retrieve user information. We cannot rely on the session here. - res = run_sql("SELECT uid FROM session WHERE session_key=%s", (argd['session_id'],)) - if len(res): - uid = res[0][0] - user_info = collect_user_info(uid) - try: - act_fd = file(os.path.join(curdir, 'act')) - action = act_fd.read() - act_fd.close() - except: - action = "" + try: + act_fd = file(os.path.join(curdir, 'act')) + action = act_fd.read() + act_fd.close() + except: + action = "" # Is user authorized to perform this action? (auth_code, auth_message) = acc_authorize_action(uid, "submit", @@ -174,11 +293,13 @@ def uploadfile(self, req, form): if filename != "": # Check that file does not already exist n = 1 - while os.path.exists(os.path.join(dir_to_open, filename)): - #dirname, basename, extension = decompose_file(new_destination_path) - basedir, name, extension = decompose_file(filename) - new_name = propose_next_docname(name) - filename = new_name + extension + if not argd.get('replace'): + while os.path.exists(os.path.join(dir_to_open, filename)): + #dirname, basename, extension = decompose_file(new_destination_path) + basedir, name, extension = decompose_file(filename) + new_name = propose_next_docname(name) + filename = new_name + extension + # This may be dangerous if the file size is bigger than the available memory fp = open(os.path.join(dir_to_open, filename), "w") fp.write(formfields.file.read()) @@ -218,8 +339,14 @@ def uploadfile(self, req, form): raise apache.SERVER_RETURN(apache.HTTP_FORBIDDEN) os.rename(os.path.join(icon_path, icon_name), os.path.join(icons_dir, icon_name)) - added_files[key] = {'name': filename, - 'iconName': icon_name} + + + # It returns the names and the full path of the image + added_files[key] = { + 'name' : filename, + 'iconName' : icon_name, + 'absPath' : os.path.join(dir_to_open, filename) + } except InvenioWebSubmitIconCreatorError, e: # We could not create the icon added_files[key] = {'name': filename} @@ -475,39 +602,48 @@ def getuploadedfile(self, req, form): ./curdir/icons/uid directory, so that we are sure we stream files only to the user who uploaded them. """ - argd = wash_urlargd(form, {'indir': (str, None), - 'doctype': (str, None), - 'access': (str, None), - 'icon': (int, 0), - 'key': (str, None), - 'filename': (str, None), - 'nowait': (int, 0)}) + argd = wash_urlargd(form, { 'indir': (str, None), + 'doctype': (str, None), + 'access': (str, None), + 'icon': (int, 0), + 'type': (str, ''), + 'key': (str, None), + 'filename': (str, None), + 'onlysize': (str, ''), + 'nowait': (int, 0), + 'filepath': (str, '') + }) if None in argd.values(): raise apache.SERVER_RETURN(apache.HTTP_BAD_REQUEST) uid = getUid(req) - if argd['icon']: - file_path = os.path.join(CFG_WEBSUBMIT_STORAGEDIR, - argd['indir'], - argd['doctype'], - argd['access'], - 'icons', - str(uid), - argd['key'], - argd['filename'] - ) + # support legacy `icon` + if argd['icon'] or argd['type'] == 'icon': + response_type = 'icons' else: + response_type = argd['type'] if argd['type'] not in (None, '') else 'files' + if not argd['filepath']: file_path = os.path.join(CFG_WEBSUBMIT_STORAGEDIR, argd['indir'], argd['doctype'], argd['access'], - 'files', + response_type, str(uid), argd['key'], argd['filename'] ) + else: + file_path = argd['filepath'] + + if argd['onlysize'] != '': + from invenio.get_image_size import get_image_size + (x, y) = get_image_size(file_path) + return json.dumps({ + 'x':x, + 'y':y + }) abs_file_path = os.path.abspath(file_path) if abs_file_path.startswith(CFG_WEBSUBMIT_STORAGEDIR): @@ -699,7 +835,7 @@ def continue_(self, req, form): def direct(self, req, form): """Directly redirected to an initialized submission.""" args = wash_urlargd(form, {'sub': (str, ''), - 'access' : (str, '')}) + 'access': (str, '')}) sub = args['sub'] access = args['access'] @@ -708,7 +844,8 @@ def direct(self, req, form): _ = gettext_set_language(ln) uid = getUid(req) - if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: + + if CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "direct", navmenuid='submit') diff --git a/modules/websubmit/web/Makefile.am b/modules/websubmit/web/Makefile.am index d86a97a4d1..9f5dc5e9a0 100644 --- a/modules/websubmit/web/Makefile.am +++ b/modules/websubmit/web/Makefile.am @@ -24,6 +24,14 @@ doc_DATA = approve.py \ yourapprovals.py \ yoursubmissions.py +webdir = $(localstatedir)/www/img + +web_DATA = websubmit.css + +jsdir = $(localstatedir)/www/js + +js_DATA = crop.js + testdir = $(libdir)/webtest/invenio test_DATA = test.pdf \ @@ -37,6 +45,6 @@ test_DATA = test.pdf \ icon-test.gif \ test.tar.gz -EXTRA_DIST = $(doc_DATA) $(test_DATA) +EXTRA_DIST = $(doc_DATA) $(test_DATA) $(web_DATA) $(js_DATA) CLEANFILES = *~ *.tmp *.pyc diff --git a/modules/websubmit/web/crop.js b/modules/websubmit/web/crop.js new file mode 100644 index 0000000000..32f0e07c39 --- /dev/null +++ b/modules/websubmit/web/crop.js @@ -0,0 +1,134 @@ +jQuery(function($){ + var jcrop_api + , current; + + $('body').on('click', '[rel=crop]', function(){ + + var $this = $(this); + + current = { + original : $this.data('original'), + thumb : $this.data('thumb'), + width : $this.data('width'), + height : $this.data('height'), + parent : $this, + crop : ($this.data('crop') !== undefined) ? $this.data('crop') : null, + isCropped: ($this.data('cropped') == 1) ? true : false, + id : $this.data('id'), + name : $this.data('name') + }; + + // Fire up event crop.click + $('body').trigger('crop.click', [$this, current]); + + $.magnificPopup.open({ + modal: true, + key: 'crop-popup', + midClick: true, + type: 'inline', + closeBtnInside: true, + items:{ + src: '
    '+ + ''+ + ''+ + '
    ', + type: 'inline' + }, + callbacks: { + open: function(){ + // What to do when it opens + $('body').trigger('crop.open', [this, current]); + }, + afterClose: function(){ + // What to do when it closes + $('body').trigger('crop.close', [this, current]); + jcropDestroy(); + } + } + }); + }); + + $('body').on('click', '[rel=save]', saveCrop); + $('body').on('click', '[rel=close]', closeCrop); + + $('body').on('overlay.ready', function(event, current, response, dimensions){ + $('',{ + id : 'target', + src: response + }).load(function(){ + $('#target').replaceWith($(this)); + $('[rel=save]').show(); + setTimeout(function(){ + // Calculate the real dimensions ratio + jcropInit(current.width, current.height, current, dimensions); + }, 0) + }) + }); + function jcropCoords(c){ + current.crop = c; + } + function jcropInit(x, y, current, dimensions){ + $('body').trigger('crop.beforeInit'); + // Check if has allready init + var animate = (!current.isCropped || current.crop.x === undefined) + ? [100, 100, 400, 300]: + [current.crop.x, + current.crop.y, + current.crop.x2, + current.crop.y2]; + + x = (x !== undefined) ? x : $('#target').width(); + y = (y !== undefined) ? y : $('#target').height(); + dimensions = JSON.parse(dimensions); + // Init Crop + $('#target').Jcrop({ + onSelect: jcropCoords, + aspectRatio: 4/3, + bgColor: '#ffffff', + bgOpacity: 0.5, + minSize: [ 80, 80 ], + trueSize: [parseInt(dimensions.x), parseInt(dimensions.y)], + allowSelect: false + },function(){ + $('body').trigger('crop.init', [this, current]); + jcrop_api = this; + jcrop_api.focus(); + jcrop_api.animateTo(animate); + }); + } + function jcropDestroy(){ + // Destroy Crop + try{ + jcrop_api.destroy(); + current = null; + return false; + }catch(e){ + // Just make the expeption silent + return false; + } + } + function saveCrop(){ + $('body').trigger('crop.save', [this, current]); + $('[rel=close]').hide(); + $(this).click(function () { return false; }); + $(this).text('Saving ...') + current.parent.data('crop', current.crop); + current.parent.data('cropped', 1); + + setTimeout(function(){ + $.magnificPopup.close(); + }, 500) + + $(this).prop('disabled', false); + } + function closeCrop(){ + $.magnificPopup.close(); + } + function removeCrop(){ + //current.parent.data('cropped', 0); + //current.parent.parent().find('.cropped').html(); + } +}); diff --git a/modules/websubmit/web/websubmit.css b/modules/websubmit/web/websubmit.css new file mode 100644 index 0000000000..874983a523 --- /dev/null +++ b/modules/websubmit/web/websubmit.css @@ -0,0 +1,281 @@ +/* + * This file is part of Invenio. + * Copyright (C) 2014 CERN. + * + * Invenio is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Invenio is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Invenio; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +.thumbnail{ + position: relative; + border: none; + float: left; + margin: 0 5px 10px 5px; + width: 282px; + box-shadow: none; + min-height: 270px; + border-radius: 0; + padding: 0; + background-color: #e5e5e5; + border: 1px solid #ccc; +} +.thumbnail textarea{ + width: 263px!important; + border-radius: 0; + margin: 0 auto; + display: inherit; + padding: 5px; + overflow: auto; + font-size: 12px; + border: 1px solid #ccc!important; +} +.thumbnail a{ + text-align: center; + padding: 5px 0; + font-size: 13px; + display: block; +} +.progress img{ + position: absolute; + width: 220px!important; + height: 16px!important; + top: 0!important; + bottom: 0; + left: 0; + right: 0; + border-bottom: none!important; + margin: 20px auto; +} +.preview-form{ + margin: 0px 0; +} +.checkbox{ + margin: 0; + font-size: 12px; +} +.thumbnail-wrapper img{ + max-height: 180px; + max-width: 282px; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; +} +.thumbnail-wrapper { + background: #222; + position: relative; + box-shadow: inset 0px 1px 1px #000; + margin-bottom: 5px; + width: 282px; + height: 180px; +} +#drop-zone-label{ + padding: 50px 20px; + margin: 20px 8px; + cursor: pointer; + border: 3px dashed #ccc; +} +#drop-zone-label:hover { + border: 3px dashed #ddd; +} +#drop-zone-label.dragover{ + border: 3px dashed #555; +} +#drop-zone-label.dragover h2{ + color: #555; +} +#drop-zone-label.dragleave{ + border: 3px dashed #ccc; +} +#drop-zone-label.dragdrop{ + border: 3px dashed green; +} +#drop-zone-label.dragdrop h2{ + color: green; +} +#drop-zone-label:hover h2, #drop-zone-label:hover small{ + color: #666; +} +#drop-zone-label h2, #drop-zone-label small{ + margin: 0; + color: #999; + font-size: 20px; + text-align: center; + line-height: normal; +} +a.remove_image{ + color: #f96464; + font-size: 30px; + position: absolute; + right: 5px; + font-weight: normal; + text-decoration: none; + display: none; + top: 2px; + z-index: 9999; +} +a.remove_image:hover { + color: #bc1313; + text-decoration: none!important; +} +.thumbnail:hover a.remove_image{ + display: inline; + text-decoration: none!important; +} +.thumbnail:hover a.remove_image img{ + width: 20px; + height: 20px; +} +.submission-error{ + position: relative; + border: none; + float: left; + margin-right: 5px; + margin-left: 5px; + margin-bottom: 10px; + width: 275px; + box-shadow: none; + border-radius: 0; + padding: 5px; + color: #fff; + font-size:10px; + background-color: #e74c3c; +} +.uploadedImages{ + max-width:890px; + margin: 0 auto; +} +.filename{ + position: absolute; + top: 0; + text-align: left; + left: 0px; + background: rgba(0,0,0,0.3); + width:272px; + padding: 3px 5px; + color: #fff; + font-size: 12px; +} +/* Error Messages */ +.uploader-alert-message { + display: block; + color: red; +} +/* Cropping */ +div.cropImgWrapper { + z-index: 999999999999999!important; + margin: 0 80px!important; + position: relative; + box-shadow: none!important; + border: none!important; +} + +#make-image{ + position: relative; + width:400px; + height: 180px; + padding: 20px 0; + overflow: hidden; + margin-bottom: 10px; +} +.crop-actions{ + margin-top: 10px; + padding-top: 15px; + border-top: 1px solid #f6f6f6; + text-align: right; +} +.crop-actions a{ + border: 1px solid; + text-align: center; + padding: 5px 10px; + border-radius: 3px; + margin-left: 5px; +} +.crop-actions .crop-save{ + background: #6fb64e; + border-color: #62ac3b; + color: #fff; +} +.crop-actions .crop-save:hover{ + background: #47a447; + border-color: #62ac3b; + color: #fff; +} +.crop-actions .crop-save.disabled{ + background: #47a447; + border-color: #62ac3b; + color: #fff; + cursor: not-allowed; +} +.crop-actions .crop-cancel{ + background: #f8f8f8; + color: #333; + border-color: #ccc; +} +.crop-actions .crop-cancel:hover{ + background: #ebebeb; + color: #333; + border-color: #ccc; +} +.crop-actions a:hover{ + text-decoration: none; +} +.crop-ctr{ + text-align: center; +} +.crop-ctr a{ + margin-right: 10px; + font-size: 12px; +} +.crop-loading-message{ + display: block; + text-align: center; +} +.crop-tool-links{ + text-align: right; +} +.crop-tool-links a{ + margin: 0 5px; +} +.crop-tool-links a[rel=save]{ + color: #fff; + background-color: #7d9ffa; +} +.crop-tool-links a[rel=save]:hover{ + text-decoration: none; + background-color: #2860f7; +} +.language_control { + text-align: left; + padding: 0px 0px 10px 0; + width: 263px!important; + border-radius: 0; + margin: 2px auto; +} +.language_control a{ + display: inline; + line-height: normal!important; + padding: 5px 10px; +} +.language_control a:hover{ + text-decoration: none; +} +.language_control a.active_lang{ + border-bottom: 1px solid #ccc; + border-left: 1px solid #ccc; + background: #fff; + border-right: 1px solid #ccc; +}