Created
March 25, 2016 22:17
-
-
Save grobertson/6e3b60f75e49cdc6fed1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% 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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment