Skip to content

Instantly share code, notes, and snippets.

@grobertson
Created March 25, 2016 22:17

Revisions

  1. grobertson created this gist Mar 25, 2016.
    350 changes: 350 additions & 0 deletions selection.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,350 @@
    {% load static from staticfiles %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="Crop Imported Image">
    <title>Cropper</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.2/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropper/2.2.5/cropper.min.css">
    <style>
    .validation_error {
    border: 2px inset #f88;
    }
    </style>
    <script>
    {% autoescape off %}
    sourceData = {{ image.source_image_attribution }}
    {% endautoescape %}
    </script>
    </head>
    <body>

    <div class="container">
    <h1 class="page-header">Preview</h1>
    <div class="row">
    <div class="col-sm-8">
    <div style="max-height: 600px;">
    <canvas id="canvas"></canvas>
    <img id="image" class="img-responsive" src="/grabit/proxyimage?id={{ image.id }}" image_id="{{ image.id }}" aspect_ratio="2:1" alt="Image to crop.">
    </div>
    </div>
    <div class="col-sm-4">
    <div class="row">
    <h3 class="page-header">Settings</h3>
    </div>
    <div class="row">
    <div class="col-sm-4">
    <div>
    <p>Short Title</p>
    <p>Alt Text</p>
    <p>Credit</p>
    <p>Attribution Link</p>
    </div>
    </div>
    <div class="col-sm-8">
    <div>
    <p><input name="title" id="image_title" value="{{ image.title }}" style="width: 100%;" placeholder="Short title..."></p>
    <p><input name="description" id="image_description" value="{{ image.image_description }}" style="width: 100%;" placeholder="Clear SEO description..."></p>
    <p><input name="credit" id="image_credit" value="{{ image.image_credit }}" style="width: 100%;" placeholder="Photo via ..."></p>
    <p><input name="attribution" id="image_attribution_link" value="{{ image.image_attribution_link }}" style="width: 100%;" placeholder="http://imgur.com/hJydC..."></p>
    <imput name="cropX1" id="cropX1" value="{{ points.top_left_x }}" type="hidden">
    <imput name="cropY1" id="cropY1" value="{{ points.top_left_y }}" type="hidden">
    <imput name="cropX2" id="cropX2" value="{{ points.bottom_right_x }}" type="hidden">
    <imput name="cropY2" id="cropY2" value="{{ points.bottom_right_y }}" type="hidden">
    <imput name="naturalX" id="naturalX" value="{{ image.original_image_width }}" type="hidden">
    <imput name="naturalY" id="naturalY" value="{{ image.original_image_height }}" type="hidden">
    <imput name="source_image_attribution" id="source_image_attribution" value="" type="hidden">
    </div>
    </div>
    <div class="row">
    <div class="col-sm-12">
    <h3 class="page-header">License</h3>
    <div>
    <p>
    <select name="license" id="image_license">
    <option value="CC-BY" selected>CC-BY</option>
    <option value="CC-BY-ND">CC-BY-ND</option>
    <option value="CC-BY-SA">CC-BY-SA</option>
    <option value="CC-CC0">CC0 [Public Domain]</option>
    <option value="Licensed">Licensed</option>
    </select>
    </p>
    </div>
    </div>
    </div>
    <div class="row">
    <div class="col-sm-12">
    <h3 class="page-header">Quality</h3>
    <div>
    <p>
    <select name="quality" id="quality">
    <option value="50">Low</option>
    <option value="60">Medium</option>
    <option value="70" selected>High</option>
    <option value="80">Very High</option>
    <option value="90">Fine Detail</option>
    <option value="100">Line Art</option>
    </select>
    </p>
    </div>
    </div>
    </div>
    <div class="row">
    <div class="col-sm-12">
    <h3 class="page-header">Remix Attribution</h3>
    <div>
    <p>
    <em>Sources for remixed artwork</em>
    </p>
    <p id="source_image_attribution_fields">

    </p>
    <p><a href="javascript:addRemixSource();">Click to add</a></p>
    </div>
    </div>
    </div>
    <div class="row">
    <div class="col-sm-8">
    </div>
    <div class="col-sm-4">
    <input type="submit" name="submit" id="submit">
    </div>
    </div>
    </div>
    </div>

    <!-- Scripts -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/js/bootstrap.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/cropper/2.2.1/cropper.min.js"></script>
    <script>
    var $canvas = $('#canvas');
    var $imageDom = $('#image');
    var image = $imageDom[0];
    var X1 = $('#cropX1');
    var Y1 = $('#cropY1');
    var X2 = $('#cropX2');
    var Y2 = $('#cropY2');
    var originalX1 = $('#cropX1').attr('value'); // Attr will get the original value
    var originalY1 = $('#cropY1').attr('value');
    var originalX2 = $('#cropX2').attr('value');
    var originalY2 = $('#cropY2').attr('value');
    var imageTitle = $("#image_title");
    var imageDescription = $("#image_description");
    var imageCredit = $("#image_credit");
    var imageAttributionLink = $("#image_attribution_link");
    var imageLicense = $("#image_license");
    var imageQuality = $("#quality");
    var imageSourceAttribution = document.getElementById("source_image_attribution");
    var imageSourceAttributionFields = document.getElementById("source_image_attribution_fields");
    // imageSourceAttribution is stored as a json object containing an array of tuples: "{ sources: [['Artist 1', 'http://link.one/destination'], ['Artist 2', 'http://link.two/destination']] }"

    function start() {
    var width = $(this).width();
    var height = $(this).height();
    var canvas = $canvas[0];
    canvas.width = width;
    canvas.height = height;
    canvas.getContext('2d').drawImage(
    this,
    0, 0, this.naturalWidth, this.naturalHeight,
    0, 0, width, height
    );

    $canvas.cropper({
    aspectRatio: 2 / 1,
    autoCrop: false,
    strict: true,
    guides: true,
    movable: false,
    scalable: false,
    zoomable: false,
    viewMode: 1,
    crop: function(e) {
    X1.val(upConvert(e.x));
    Y1.val(upConvert(e.y));
    X2.val(upConvert(e.x + e.width));
    Y2.val(upConvert(e.y + e.height));
    console.log("X1: " + X1.val() + " Y1: " + Y1.val() + " X2: " + X2.val() + " Y2: " + Y2.val() + " crop width: " + e.width + " crop height: " + e.height);
    },
    built: function(){
    $imageDom.hide();
    var saved_crop = restoreCrop();
    if(saved_crop){
    $(this).cropper('crop'); // FGR: Must be called before setData. Ask me how I know.
    $(this).cropper('setData', saved_crop);
    $(this).cropper('crop'); // FGR: Call again after setting dimensions to set X1,Y1/X2,Y2
    }
    }
    });
    }

    if (image.complete) {
    start.call(image);
    } else {
    $imageDom.on('load', start);
    }

    unserializeRemixSources();
    $('#submit').on('click', save)

    function save(){
    if(validateForm()){
    serializeRemixSources();
    var form_data = {
    imgCropX1: Math.round(X1.val()),
    imgCropY1: Math.round(Y1.val()),
    imgCropX2: Math.round(X2.val()),
    imgCropY2: Math.round(Y2.val()),
    image_id: $imageDom.attr('image_id'),
    aspect_ratio: $imageDom.attr('aspect_ratio'),
    title: $('#image_title').val(),
    image_description: $('#image_description').val(),
    image_credit: $('#image_credit').val(),
    image_attribution_link: $('#image_attribution_link').val(),
    image_license: $('#image_license').val(),
    quality: $('#quality').val(),
    source_image_attribution: serializeRemixSources()
    };
    var url = '/grabit/saveimage';
    // Post the form (resulting in a save). Callback loads the admin object detail.
    $.post(url, form_data, function(data){
    var next_url = '/admin/grabit/croppedimage/' + form_data.image_id + '/';
    window.location = next_url;
    });
    }else{
    return false;
    }
    }

    function restoreCrop(){
    try{
    var data = {
    x: downConvert(originalX1),
    y: downConvert(originalY1),
    width: downConvert(originalX2),
    height: downConvert(originalY2),
    rotate: 0
    }
    return data;
    }catch(e){
    return false;
    }
    }

    function getRatio(){
    // Returns the result of the natural width of the image divided by the visible width of the canvas.
    return roundToPrecision(image.naturalWidth / canvas.width, 5);
    }

    function upConvert(val){
    // returns the value multiplied by the effective ratio
    return val * getRatio() || 1;
    }

    function downConvert(val){
    // returns the value divided by the effective ratio
    return val / getRatio() || 1;
    }

    function roundToPrecision(value, exp) {
    // Round to a specific number of decimal places, not to integer
    // http://stackoverflow.com/a/21323330
    if (typeof exp === 'undefined' || +exp === 0)
    return Math.round(value);
    value = +value;
    exp = +exp;
    if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0))
    return NaN;
    // Shift
    value = value.toString().split('e');
    value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)));
    // Shift back
    value = value.toString().split('e');
    return +(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp));
    }

    function validateForm(){
    return validateField(imageTitle) && validateField(imageDescription) && validateField(imageCredit) &&validateField(imageAttributionLink) && validateURL(imageAttributionLink);
    }

    function validateField(el){
    if(Validator.isEmptyString(el.val(), false)){
    el.addClass("validation_error");
    return false;
    }else{
    el.removeClass("validation_error");
    return true;
    }
    }

    function validateURL(el){
    if(Validator.isURL(el.val())){
    el.removeClass("validation_error");
    return true;
    }else{
    el.addClass("validation_error");
    return false;
    }
    }

    function addRemixSource(name, url){
    var eSourceName = document.createElement("input");
    eSourceName.name = "remixSourceName[]";
    eSourceName.placeholder = "Artist...";
    eSourceName.style = "width: 100%;";
    eSourceName.value = name || '';
    var eSourceLink = document.createElement("input");
    eSourceLink.name = "remixSourceLink[]";
    eSourceLink.placeholder = "http://bluthcorp.com/...";
    eSourceLink.style = "width: 100%;";
    eSourceLink.value = url || '';
    var para = document.createElement("p");
    para.appendChild(eSourceName);
    para.appendChild(eSourceLink);
    imageSourceAttributionFields.appendChild(para);
    }

    function serializeRemixSources(){
    var names = document.getElementsByName("remixSourceName[]");
    var links = document.getElementsByName("remixSourceLink[]");
    var serialized = [];
    for (var i = 0, len = names.length; i < len; i++) {
    if (names[i].value != '' && links[i].value != ''){
    serialized.push([names[i].value, links[i].value]);
    }
    }
    sourceData.sources = serialized;
    return JSON.stringify(sourceData);
    }

    function unserializeRemixSources(){
    var sources = sourceData.sources;
    for (var i = 0, len = sources.length; i < len; i++) {
    addRemixSource(sources[i][0], sources[i][1]);
    }
    }

    var Validator = (function(){

    return {
    /* string validation */
    isEmptyString : function(string, countWhitespace){
    if(!countWhitespace){
    string = string.replace(/\s+/g, '');
    }
    return string.length == 0;
    },
    isURL : function(string){
    return /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/i.test(string);
    }

    };

    })();
    </script>
    </body>
    </html>