Browse Source

nicer photo uploading experience

pull/82/head
Aaron Parecki 8 years ago
parent
commit
202a7876ec
No known key found for this signature in database GPG Key ID: 276C2817346D6056
  1. 2
      lib/helpers.php
  2. 23
      public/css/style.css
  3. 4
      views/layout.php
  4. 308
      views/new-post.php

2
lib/helpers.php

@ -363,6 +363,7 @@ function validate_photo(&$file) {
// Only does anything if the exif library is loaded, otherwise is a noop.
function correct_photo_rotation($filename) {
if(class_exists('IMagick')) {
try {
$image = new IMagick($filename);
$orientation = $image->getImageOrientation();
switch($orientation) {
@ -378,6 +379,7 @@ function correct_photo_rotation($filename) {
}
$image->setImageOrientation(IMagick::ORIENTATION_TOPLEFT);
$image->writeImage($filename);
} catch(Exception $e){}
}
}

23
public/css/style.css

@ -203,6 +203,29 @@ body {
}
/**
* nicer file upload
*/
.btn-file {
position: relative;
overflow: hidden;
}
.btn-file input[type=file] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
font-size: 100px;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
outline: none;
background: white;
cursor: inherit;
display: block;
}
.glyphicon-spin {
-webkit-animation: spin 1000ms infinite linear;

4
views/layout.php

@ -22,10 +22,14 @@
<script src="/js/jquery-1.7.1.min.js"></script>
<script src="/libs/localforage.js"></script>
<script src="/bootstrap/js/bootstrap.min.js"></script>
<script src="/libs/tokenfield/bootstrap-tokenfield.min.js"></script>
<link rel="stylesheet" href="/libs/tokenfield/bootstrap-tokenfield.min.css">
<link rel="stylesheet" href="/libs/tokenfield/tokenfield-typeahead.min.css">
<script src="/libs/awesomplete/awesomplete.min.js"></script>
<link rel="stylesheet" href="/libs/awesomplete/awesomplete.css">
<link rel="stylesheet" href="/css/style.css">
<link rel="apple-touch-icon" sizes="57x57" href="/images/quill-icon-57.png">

308
views/new-post.php

@ -41,22 +41,11 @@
<input type="text" id="note_slug" value="" class="form-control">
</div>
<a href="javascript:expandPhotoSection();" id="expand-photo-section"><i class="glyphicon glyphicon-camera" style="color: #aaa; font-size: 36px;"></i></a>
<div class="form-group hidden" id="photo-section">
<label for="note_photo">Photo</label>
<input type="file" name="note_photo" id="note_photo" accept="image/*">
<a href="javascript:switchToManualPhotoURL();" id="note_manual_photo">enter photo url</a>
<a href="javascript:addPhotoURL();" class="hidden" id="add_photo">add photo</a>
<br>
<div id="photo_preview_container" class="hidden">
<img src="" id="photo_preview" style="max-width: 300px; max-height: 300px;">
<div>
<button type="button" class="btn btn-danger btn-sm" id="remove_photo"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Remove image</button>
</div>
</div>
<div class="form-group hidden" id="photo-previews">
</div>
<a href="javascript:addNewPhoto();" id="expand-photo-section"><i class="glyphicon glyphicon-camera" style="color: #aaa; font-size: 36px;"></i></a>
<div class="form-group" style="margin-top: 1em;">
<label for="note_syndicate-to">Syndicate <a href="javascript:reload_syndications()">(refresh list)</a></label>
<div id="syndication-container">
@ -118,6 +107,62 @@
</div>
</div>
<!-- Add Photo -->
<div class="modal fade" id="photo-modal" tabindex="-1" role="dialog" aria-labelledby="photo-modal-title" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="photo-modal-title">Add Photo</h4>
</div>
<div class="modal-body">
<div id="modal_photo_preview" class="hidden">
<img style="width:100%;">
</div>
<label id="note_photo_button" class="btn btn-default btn-file" style="margin-bottom: 1em;">
Choose File <input type="file" name="note_photo" id="note_photo" accept="image/*">
</label>
<input type="url" id="note_photo_url" class="form-control" placeholder="Paste image URL">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary save-btn">Add</button>
</div>
</div>
</div>
</div>
<!-- Edit Photo -->
<div class="modal fade" id="edit-photo-modal" tabindex="-1" role="dialog" aria-labelledby="edit-photo-modal-title" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="edit-photo-modal-title">Edit Photo</h4>
</div>
<div class="modal-body">
<div id="modal_edit_photo_preview" style="margin-bottom: 4px;">
<img style="width:100%;">
</div>
<input type="text" id="note_photo_alt" class="form-control hidden" placeholder="Image alt text">
<input type="hidden" id="modal_edit_photo_index">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger remove-btn" data-dismiss="modal">Remove</button>
<!-- <button type="button" class="btn btn-primary save-btn">Save</button> -->
</div>
</div>
</div>
</div>
<style type="text/css">
#reply {
@ -130,6 +175,34 @@
font-weight: bold;
}
#modal_photo_preview img, #modal_edit_photo_preview img {
width: 100%;
border-radius: 4px;
margin-bottom: 4px;
}
#photo-previews span {
width: 24%;
height: 180px;
margin-right: 1px;
position: relative;
overflow: hidden;
display: inline-block;
}
#photo-previews img {
position: absolute;
left: 50%;
top: 50%;
height: 100%;
width: auto;
-webkit-transform: translate(-50%,-50%);
-ms-transform: translate(-50%,-50%);
transform: translate(-50%,-50%);
cursor: pointer;
}
.pcheck206 { color: #6ba15c; } /* tweet fits within the limit even after adding RT @username */
.pcheck207 { color: #c4b404; } /* danger zone, tweet will overflow when RT @username is added */
.pcheck200,.pcheck208 { color: #59cb3a; } /* exactly fits 140 chars, both with or without RT */
@ -176,7 +249,7 @@ function saveNoteState() {
inReplyTo: $("#note_in_reply_to").val(),
category: $("#note_category").val(),
slug: $("#note_slug").val(),
photo: $("#note_photo_url").val()
photos: photos
};
state.syndications = [];
$("#syndication-container button.btn-info").each(function(i,btn){
@ -193,8 +266,9 @@ function restoreNoteState() {
$("#note_in_reply_to").val(note.inReplyTo);
$("#note_category").val(note.category);
$("#note_slug").val(note.slug);
if(note.photo) {
replacePhotoWithPhotoURL(note.photo);
if(note.photos) {
photos = note.photos;
refreshPhotoPreviews();
}
if(note.inReplyTo) {
expandReplySection();
@ -212,33 +286,6 @@ function restoreNoteState() {
});
}
function replacePhotoWithPhotoURL(url) {
$("#note_photo").after('<input type="url" name="note_photo_url[]" value="" class="note_photo_url form-control">');
$(".note_photo_url").val(url);
$("#note_photo").remove();
$("#photo_preview").attr("src", url);
$("#photo_preview_container").removeClass("hidden");
$("#note_manual_photo").addClass("hidden");
}
function switchToManualPhotoURL() {
$("#note_photo").after('<input type="url" name="note_photo_url[]" value="" class="note_photo_url form-control">');
$("#note_photo").remove();
$("#note_photo_url").change(function(){
$("#photo_preview").attr("src", $(this).val());
$("#photo_preview_container").removeClass("hidden");
});
$("#note_manual_photo").addClass("hidden");
$("#add_photo").removeClass("hidden");
}
function addPhotoURL() {
$(".note_photo_url:last").after('<input type="url" name="note_photo_url[]" value="" class="note_photo_url form-control" style="margin-top:2px;">');
if($(".note_photo_url").length == 4) {
$("#add_photo").remove();
}
}
function expandReplySection() {
$("#expand-reply").click();
$("#note_in_reply_to").change();
@ -251,37 +298,125 @@ function activateTokenField() {
});
}
function expandPhotoSection() {
$("#photo-section").removeClass("hidden");
$("#expand-photo-section").addClass("hidden");
var hasMediaEndpoint = <?= $this->media_endpoint ? 'true' : 'false' ?>;
var photos = [];
function addNewPhoto() {
// Reset modal
$("#note_photo").val("");
$("#note_photo_url").val("");
$("#modal_photo_preview").addClass("hidden");
$("#note_photo_button").removeClass("hidden");
$("#note_photo_url").removeClass("hidden");
// Show the modal
$("#photo-modal").modal();
}
$(function(){
$("#note_photo").on("change", function(e){
var userHasSetCategory = false;
// If the user has a media endpoint, upload the photo to it right now
if(hasMediaEndpoint) {
var formData = new FormData();
formData.append("null","null");
formData.append("photo", e.target.files[0]);
var request = new XMLHttpRequest();
request.open("POST", "/micropub/media");
request.onreadystatechange = function() {
if(request.readyState == XMLHttpRequest.DONE) {
try {
var response = JSON.parse(request.responseText);
if(response.location) {
$("#modal_photo_preview img").attr("src", response.location);
$("#note_photo_url").removeClass("hidden").val(response.location);
$("#note_photo_button").addClass("hidden");
} else {
console.log("Endpoint did not return a location header", response);
}
} catch(e) {
console.log(e);
}
}
}
request.send(formData);
} else {
$("#modal_photo_preview img").attr("src", URL.createObjectURL(e.target.files[0]));
}
var hasMediaEndpoint = <?= $this->media_endpoint ? 'true' : 'false' ?>;
$("#modal_photo_preview").removeClass("hidden");
$("#note_photo_button").addClass("hidden");
$("#note_photo_url").addClass("hidden");
});
$("#note_content, #note_category, #note_in_reply_to, #note_slug, #note_photo_url").on('keyup change', function(e){
$("#note_photo_url").on("change", function(){
$("#modal_photo_preview img").attr("src", $(this).val());
$("#modal_photo_preview").removeClass("hidden");
$("#note_photo_button").addClass("hidden");
});
$("#photo-modal .save-btn").click(function(){
if($("#note_photo_url").val()) {
photos.push({
url: $("#note_photo_url").val(),
external: true
});
} else {
photos.push({
url: URL.createObjectURL(document.getElementById("note_photo").files[0]),
file: document.getElementById("note_photo").files[0],
external: false
});
}
$("#photo-modal").modal('hide');
refreshPhotoPreviews();
saveNoteState();
});
$("#note_content").on('keyup', function(){
var scrollHeight = document.getElementById("note_content").scrollHeight;
var currentHeight = parseInt($("#note_content").css("height"));
if(Math.abs(scrollHeight - currentHeight) > 20) {
$("#note_content").css("height", (scrollHeight+30)+"px");
$("#edit-photo-modal .save-btn").click(function(){
});
$("#edit-photo-modal .remove-btn").click(function(){
var new_photos = [];
for(i=0; i<photos.length; i++) {
if(i != $("#modal_edit_photo_index").val()) {
new_photos.push(photos[i]);
}
}
photos = new_photos;
refreshPhotoPreviews();
saveNoteState();
});
$("#expand-reply").click(function(){
$('.reply-section').removeClass('hidden');
$(this).addClass('hidden');
return false;
});
function refreshPhotoPreviews() {
$("#photo-previews").html("");
for(i=0; i<photos.length; i++) {
console.log(photos[i]);
$("#photo-previews").append('<span><img src="'+photos[i].url+'"></span>');
}
if(photos.length == 0) {
$("#photo-previews").addClass("hidden");
} else {
$("#photo-previews").removeClass("hidden");
}
$("#photo-previews img").unbind("click").bind("click", function(){
console.log("Photo was tapped: "+$(this).attr("src"));
$("#modal_edit_photo_preview img").attr("src", $(this).attr("src"));
var index = false;
for(i=0; i<photos.length; i++) {
if(photos[i].url == $(this).attr("src")) {
index = i;
}
}
$("#modal_edit_photo_index").val(index);
$("#edit-photo-modal").modal();
});
}
// Preview the photo when one is chosen
$("#photo_preview_container").addClass("hidden");
/*
$("#note_photo").on("change", function(e){
// If the user has a media endpoint, upload the photo to it right now
if(hasMediaEndpoint) {
@ -314,14 +449,34 @@ $(function(){
$("#photo_preview_container").removeClass("hidden");
}
});
$("#remove_photo").on("click", function(){
$("#note_photo").val("");
$(".note_photo_url").val("");
$("#photo_preview").attr("src", "" );
$("#photo_preview_container").addClass("hidden");
*/
$(function(){
var userHasSetCategory = false;
$("#note_content, #note_category, #note_in_reply_to, #note_slug").on('keyup change', function(e){
saveNoteState();
});
$("#note_content").on('keyup', function(){
var scrollHeight = document.getElementById("note_content").scrollHeight;
var currentHeight = parseInt($("#note_content").css("height"));
if(Math.abs(scrollHeight - currentHeight) > 20) {
$("#note_content").css("height", (scrollHeight+30)+"px");
}
});
$("#expand-reply").click(function(){
$('.reply-section').removeClass('hidden');
$(this).addClass('hidden');
return false;
});
// Preview the photo when one is chosen
$("#photo_preview_container").addClass("hidden");
$("#note_content").on('change keyup', function(e){
var text = $("#note_content").val();
var tweet_length = tw_text_proxy(text).length;
@ -443,15 +598,20 @@ $(function(){
formData.append("<?= $this->user->micropub_slug_field ?>", v);
}
// Add either the photo as a file, or the photo URL depending on whether the user has a media endpoint
if(document.getElementById("note_photo") && document.getElementById("note_photo").files[0]) {
formData.append("photo", document.getElementById("note_photo").files[0]);
} else if($(".note_photo_url").val()) {
$(".note_photo_url").each(function(){
if($(this).val()) {
formData.append("photo[]", $(this).val());
if(photos.length == 1) {
if(photos[0].external) {
formData.append("photo", photos[0].url);
} else {
formData.append("photo", photos[0].file);
}
} else {
for(i=0; i<photos.length; i++) {
if(photos[i].external) {
formData.append("photo[]", photos[i].url);
} else {
formData.append("photo[]", photos[i].file);
}
}
});
}
// Need to append a placeholder field because if the file size max is hit, $_POST will

Loading…
Cancel
Save