Got react image manager working

This commit is contained in:
Dan Brown 2015-08-12 23:42:42 +01:00
parent 8d72883dcb
commit 83c653fc32
10 changed files with 212 additions and 261 deletions

3
.gitignore vendored
View File

@ -6,6 +6,7 @@ Homestead.yaml
.idea .idea
/public/plugins /public/plugins
/public/css /public/css
/public/js/all* /public/js
/public/uploads
/public/bower /public/bower
/storage/images /storage/images

View File

@ -71,7 +71,7 @@ class ImageController extends Controller
*/ */
public function getAll($page = 0) public function getAll($page = 0)
{ {
$pageSize = 13; $pageSize = 25;
$images = DB::table('images')->orderBy('created_at', 'desc') $images = DB::table('images')->orderBy('created_at', 'desc')
->skip($page*$pageSize)->take($pageSize)->get(); ->skip($page*$pageSize)->take($pageSize)->get();
foreach($images as $image) { foreach($images as $image) {
@ -95,9 +95,9 @@ class ImageController extends Controller
public function getThumbnail($image, $width = 220, $height = 220) public function getThumbnail($image, $width = 220, $height = 220)
{ {
$explodedPath = explode('/', $image->url); $explodedPath = explode('/', $image->url);
array_splice($explodedPath, 3, 0, ['thumbs-' . $width . '-' . $height]); array_splice($explodedPath, 4, 0, ['thumbs-' . $width . '-' . $height]);
$thumbPath = implode('/', $explodedPath); $thumbPath = implode('/', $explodedPath);
$thumbFilePath = storage_path() . $thumbPath; $thumbFilePath = public_path() . $thumbPath;
// Return the thumbnail url path if already exists // Return the thumbnail url path if already exists
if(file_exists($thumbFilePath)) { if(file_exists($thumbFilePath)) {
@ -105,7 +105,7 @@ class ImageController extends Controller
} }
// Otherwise create the thumbnail // Otherwise create the thumbnail
$thumb = ImageTool::make(storage_path() . $image->url); $thumb = ImageTool::make(public_path() . $image->url);
$thumb->fit($width, $height); $thumb->fit($width, $height);
// Create thumbnail folder if it does not exist // Create thumbnail folder if it does not exist
@ -127,17 +127,18 @@ class ImageController extends Controller
{ {
$imageUpload = $request->file('file'); $imageUpload = $request->file('file');
$name = str_replace(' ', '-', $imageUpload->getClientOriginalName()); $name = str_replace(' ', '-', $imageUpload->getClientOriginalName());
$imagePath = '/images/' . Date('Y-m-M') . '/'; $storageName = substr(sha1(time()), 0, 10) . '-' . $name;
$storagePath = storage_path(). $imagePath; $imagePath = '/uploads/images/'.Date('Y-m-M').'/';
$fullPath = $storagePath . $name; $storagePath = public_path(). $imagePath;
$fullPath = $storagePath . $storageName;
while(file_exists($fullPath)) { while(file_exists($fullPath)) {
$name = substr(sha1(rand()), 0, 3) . $name; $storageName = substr(sha1(rand()), 0, 3) . $storageName;
$fullPath = $storagePath . $name; $fullPath = $storagePath . $storageName;
} }
$imageUpload->move($storagePath, $name); $imageUpload->move($storagePath, $storageName);
// Create and save image object // Create and save image object
$this->image->name = $name; $this->image->name = $name;
$this->image->url = $imagePath . $name; $this->image->url = $imagePath . $storageName;
$this->image->created_by = Auth::user()->id; $this->image->created_by = Auth::user()->id;
$this->image->updated_by = Auth::user()->id; $this->image->updated_by = Auth::user()->id;
$this->image->save(); $this->image->save();

View File

@ -13,5 +13,5 @@ var elixir = require('laravel-elixir');
elixir(function(mix) { elixir(function(mix) {
mix.sass('styles.scss'); mix.sass('styles.scss');
mix.babel('image-manager.js'); mix.babel('image-manager.js', 'public/js/image-manager.js');
}); });

View File

@ -1,117 +0,0 @@
// Dropzone config
Dropzone.options.imageUploadDropzone = {
uploadMultiple: false,
previewsContainer: '.image-manager-display .uploads',
init: function() {
this.on('success', function(event, image) {
$('.image-manager-display .uploads').empty();
var newImage = $('<img />').attr('data-image-id', image.id);
newImage.attr('title', image.name).attr('src', image.thumbnail);
newImage.data('imageData', image);
$('.image-manager-display .uploads').after(newImage);
});
}
};
(function() {
var isInit = false;
var elem;
var overlay;
var display;
var imageIndexUrl = '/images/all';
var pageIndex = 0;
var hasMore = true;
var isGettingImages = true;
var ImageManager = {};
var action = false;
ImageManager.show = function(selector, callback) {
if(isInit) {
showWindow();
} else {
this.init(selector)
showWindow();
}
action = (typeof callback !== 'undefined') ? callback : false;
};
ImageManager.init = function(selector) {
elem = $(selector);
overlay = elem.closest('.overlay');
display = elem.find('.image-manager-display').first();
var uploads = display.find('.uploads');
var images = display.find('images');
var loadMore = display.find('.load-more');
// Get recent images and show
$.getJSON(imageIndexUrl, showImages);
function showImages(data) {
var images = data.images;
hasMore = data.hasMore;
pageIndex++;
isGettingImages = false;
for(var i = 0; i < images.length; i++) {
var image = images[i];
var newImage = $('<img />').attr('data-image-id', image.id);
newImage.attr('title', image.name).attr('src', image.thumbnail);
loadMore.before(newImage);
newImage.data('imageData', image);
}
if(hasMore) loadMore.show();
}
loadMore.click(function() {
loadMore.hide();
if(isGettingImages === false) {
isGettingImages = true;
$.getJSON(imageIndexUrl + '/' + pageIndex, showImages);
}
});
// Image grabbing on scroll
display.on('scroll', function() {
var displayBottom = display.scrollTop() + display.height();
var elemTop = loadMore.offset().top;
if(elemTop < displayBottom && hasMore && isGettingImages === false) {
isGettingImages = true;
loadMore.hide();
$.getJSON(imageIndexUrl + '/' + pageIndex, showImages);
}
});
elem.on('dblclick', '.image-manager-display img', function() {
var imageElem = $(this);
var imageData = imageElem.data('imageData');
closeWindow();
if(action) {
action(imageData);
}
});
elem.find('button[data-action="close"]').click(function() {
closeWindow();
});
// Set up dropzone
elem.find('.image-manager-dropzone').first().dropzone({
uploadMultiple: false
});
isInit = true;
};
function showWindow() {
overlay.closest('body').css('overflow', 'hidden');
overlay.show();
}
function closeWindow() {
overlay.hide();
overlay.closest('body').css('overflow', 'auto');
}
window.ImageManager = ImageManager;
})();

View File

@ -1,63 +1,140 @@
class ImageList extends React.Component { (function() {
constructor(props) {
super(props);
this.state = {
images: [],
hasMore: false,
page: 0
};
}
componentDidMount() { class ImageManager extends React.Component {
$.getJSON('/images/all', data => {
this.setState({ constructor(props) {
images: data.images, super(props);
hasMore: data.hasMore this.state = {
images: [],
hasMore: false,
page: 0
};
}
show(callback) {
$(React.findDOMNode(this)).show();
this.callback = callback;
}
hide() {
$(React.findDOMNode(this)).hide();
}
selectImage(image) {
if(this.callback) {
this.callback(image);
}
this.hide();
}
componentDidMount() {
var _this = this;
// Set initial images
$.getJSON('/images/all', data => {
this.setState({
images: data.images,
hasMore: data.hasMore
});
}); });
}); // Create dropzone
} this.dropZone = new Dropzone(React.findDOMNode(this.refs.dropZone), {
url: '/upload/image',
loadMore() { init: function() {
this.state.page++; var dz = this;
$.getJSON('/images/all/' + this.state.page, data => { this.on("sending", function(file, xhr, data) {
this.setState({ data.append("_token", document.querySelector('meta[name=token]').getAttribute('content'));
images: this.state.images.concat(data.images), });
hasMore: data.hasMore this.on("success", function(file, data) {
_this.state.images.unshift(data);
_this.setState({
images: _this.state.images
});
//$(file.previewElement).fadeOut(400, function() {
// dz.removeFile(file);
//})
});
}
}); });
}); }
}
render() { loadMore() {
var images = this.state.images.map(function(image) { this.state.page++;
$.getJSON('/images/all/' + this.state.page, data => {
this.setState({
images: this.state.images.concat(data.images),
hasMore: data.hasMore
});
});
}
render() {
var loadMore = this.loadMore.bind(this);
var selectImage = this.selectImage.bind(this);
return ( return (
<div key={image.id}> <div className="overlay">
<img src={image.thumbnail}/> <div id="image-manager">
<div className="image-manager-content">
<div className="dropzone-container" ref="dropZone">
<div className="dz-message">Drop files or click here to upload</div>
</div>
<ImageList data={this.state.images} loadMore={loadMore} selectImage={selectImage} hasMore={this.state.hasMore}/>
</div>
<div className="image-manager-sidebar">
<h2>Images</h2>
</div>
</div>
</div> </div>
); );
}); }
return (
<div className="image-list"> }
{images} window.ImageManager = new ImageManager();
<div className="load-more" onClick={this.loadMore}>Load More</div>
</div> class ImageList extends React.Component {
render() {
var selectImage = this.props.selectImage;
var images = this.props.data.map(function(image) {
return (
<Image key={image.id} image={image} selectImage={selectImage} />
);
});
return (
<div className="image-manager-list clearfix">
{images}
{ this.props.hasMore ? <div className="load-more" onClick={this.props.loadMore}>Load More</div> : null }
</div>
);
}
}
class Image extends React.Component {
setImage() {
this.props.selectImage(this.props.image);
}
render() {
var setImage = this.setImage.bind(this);
return (
<div>
<img onDoubleClick={setImage} src={this.props.image.thumbnail}/>
</div>
);
}
}
if(document.getElementById('image-manager-container')) {
window.ImageManager = React.render(
<ImageManager />,
document.getElementById('image-manager-container')
); );
} }
} })();
class ImageManager extends React.Component {
render() {
return (
<div id="image-manager">
<ImageList/>
</div>
);
}
}
React.render(
<ImageManager />,
document.getElementById('container')
);

View File

@ -9,46 +9,29 @@
border-radius: 4px; border-radius: 4px;
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.3); box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.3);
overflow: hidden; overflow: hidden;
.image-list img { .image-manager-list img {
border-radius: 0; border-radius: 0;
float: left; float: left;
margin: 1px; margin: 1px;
cursor: pointer; cursor: pointer;
} }
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 999;
display: flex;
} }
#image-manager .dropzone { #image-manager .dropzone-container {
display: table; height: 100px;
position: absolute; position: relative;
top: 10px;
left: 300px;
width: 480px;
height: 60px;
border: 4px dashed $primary;
text-align: center;
z-index: 900;
.dz-message {
display: table-cell;
vertical-align: middle;
color: $primary;
font-size: 1.2em;
}
* {
pointer-events: none;
}
} }
.image-manager-display-wrap {
height: 100%; #container {
padding-top: 87px; height: 90vh;
position: absolute;
top: 0;width: 100%;
}
.image-manager-display {
height: 100%;
width: 100%;
text-align: left;
overflow-y: scroll;
} }
#image-manager .load-more { #image-manager .load-more {
width: 150px; width: 150px;
height: 150px; height: 150px;
@ -62,32 +45,54 @@
font-size: 20px; font-size: 20px;
cursor: pointer; cursor: pointer;
} }
.image-manager-title {
font-size: 2em; .image-manager-sidebar {
text-align: left; width: 300px;
margin: 0 $-m; height: 100%;
padding: $-xl $-m; margin-left: 1px;
color: #666; padding: 0 $-l;
border-bottom: 1px solid #DDD; border-left: 1px solid #DDD;
} }
.image-manager-dropzone { .image-manager-list {
background-color: lighten($primary, 40%); overflow-y: scroll;
height: 25%; flex: 1;
text-align: center;
font-size: 2em;
line-height: 2em;
padding-top: $-xl*1.2;
color: #666;
border-top: 2px solid $primary;
} }
.image-manager-content {
display: flex;
flex-direction: column;
height: 100%;
flex: 1;
}
// Dropzone // Dropzone
/* /*
* The MIT License * The MIT License
* Copyright (c) 2012 Matias Meno <m@tias.me> * Copyright (c) 2012 Matias Meno <m@tias.me>
*/ */
.dz-message {
font-size: 1.6em;
font-style: italic;
color: #aaa;
text-align: center;
line-height: 90px;
cursor: pointer;
transition: all ease-in-out 120ms;
position: absolute;
top: 0;
left: 50%;
max-width: 400px;
width: 400px;
margin-left: -200px;
}
.dz-drag-hover .dz-message {
background-color: rgb(16, 126, 210);
color: #EEE;
}
@keyframes passing-through { @keyframes passing-through {
0% { 0% {
opacity: 0; opacity: 0;
@ -128,30 +133,13 @@
.dropzone, .dropzone * { .dropzone, .dropzone * {
box-sizing: border-box; } box-sizing: border-box; }
.dropzone {
background: white;
padding: 20px 20px; }
.dropzone.dz-clickable {
cursor: pointer; }
.dropzone.dz-clickable * {
cursor: default; }
.dropzone.dz-clickable .dz-message, .dropzone.dz-clickable .dz-message * {
cursor: pointer; }
.dropzone.dz-started .dz-message {
display: none; }
.dropzone.dz-drag-hover {
border-style: solid; }
.dropzone.dz-drag-hover .dz-message {
opacity: 0.5; }
.dropzone .dz-message {
text-align: center;
margin: 2em 0; }
.dz-preview { .dz-preview {
position: relative; position: relative;
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
margin: 16px; margin: 12px;
min-height: 100px; } min-height: 80px; }
.dz-preview:hover { .dz-preview:hover {
z-index: 1000; } z-index: 1000; }
.dz-preview:hover .dz-details { .dz-preview:hover .dz-details {
@ -186,16 +174,16 @@
top: 0; top: 0;
left: 0; left: 0;
opacity: 0; opacity: 0;
font-size: 13px; font-size: 10px;
min-width: 100%; min-width: 100%;
max-width: 100%; max-width: 100%;
padding: 2em 1em; padding: 6px 3px;
text-align: center; text-align: center;
color: rgba(0, 0, 0, 0.9); color: rgba(0, 0, 0, 0.9);
line-height: 150%; } line-height: 150%; }
.dz-preview .dz-details .dz-size { .dz-preview .dz-details .dz-size {
margin-bottom: 1em; margin-bottom: 0.5em;
font-size: 16px; } font-size: 12px; }
.dz-preview .dz-details .dz-filename { .dz-preview .dz-details .dz-filename {
white-space: nowrap; } white-space: nowrap; }
.dz-preview .dz-details .dz-filename:hover span { .dz-preview .dz-details .dz-filename:hover span {
@ -221,8 +209,8 @@
.dz-preview .dz-image { .dz-preview .dz-image {
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
width: 120px; width: 80px;
height: 120px; height: 80px;
position: relative; position: relative;
display: block; display: block;
z-index: 10; } z-index: 10; }

View File

@ -182,7 +182,7 @@ ul.menu {
.overlay { .overlay {
background-color: rgba(0, 0, 0, 0.2); background-color: rgba(0, 0, 0, 0.2);
position: fixed; position: fixed;
display: block; display: none;
z-index: 95536; z-index: 95536;
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -355,7 +355,8 @@ body.dragging, body.dragging * {
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: -1; z-index: -1;
.overlay { &:after{
content: '';
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -363,6 +364,7 @@ body.dragging, body.dragging * {
height: 100%; height: 100%;
z-index: -1; z-index: -1;
background-color: rgba(0,0,0,0.7); background-color: rgba(0,0,0,0.7);
display: block;
} }
} }

View File

@ -3,6 +3,7 @@
<head> <head>
<title>BookStack</title> <title>BookStack</title>
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="token" content="{{ csrf_token() }}">
<link rel="stylesheet" href="/css/app.css"> <link rel="stylesheet" href="/css/app.css">
<link href='//fonts.googleapis.com/css?family=Roboto:400,400italic,500,500italic,700,700italic,300italic,100,300' rel='stylesheet' type='text/css'> <link href='//fonts.googleapis.com/css?family=Roboto:400,400italic,500,500italic,700,700italic,300italic,100,300' rel='stylesheet' type='text/css'>
{{--<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">--}} {{--<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">--}}
@ -10,6 +11,7 @@
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="/bower/bootstrap/dist/js/bootstrap.js"></script> <script src="/bower/bootstrap/dist/js/bootstrap.js"></script>
<script src="/bower/jquery-sortable/source/js/jquery-sortable.js"></script> <script src="/bower/jquery-sortable/source/js/jquery-sortable.js"></script>
<script src="/bower/dropzone/dist/min/dropzone.min.js"></script>
<script src="https://fb.me/react-0.13.3.js"></script> <script src="https://fb.me/react-0.13.3.js"></script>
<script> <script>
$.fn.smoothScrollTo = function() { $.fn.smoothScrollTo = function() {
@ -63,7 +65,5 @@
</section> </section>
@yield('bottom') @yield('bottom')
<script src="/js/all.js"></script>
</body> </body>
</html> </html>

View File

@ -2,8 +2,6 @@
@section('head') @section('head')
<script src="/bower/tinymce-dist/tinymce.jquery.min.js"></script> <script src="/bower/tinymce-dist/tinymce.jquery.min.js"></script>
<script src="/bower/dropzone/dist/min/dropzone.min.js"></script>
<script src="/js/image-manager.js"></script>
@stop @stop
@section('content') @section('content')
@ -16,5 +14,6 @@
@stop @stop
@section('bottom') @section('bottom')
@include('pages/image-manager') <div id="image-manager-container"></div>
<script src="/js/image-manager.js"></script>
@stop @stop

View File

@ -41,7 +41,7 @@
toolbar: "undo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image link | fontsizeselect fullscreen", toolbar: "undo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image link | fontsizeselect fullscreen",
content_style: "body {padding-left: 15px !important; padding-right: 15px !important; margin:0!important; margin-left:auto!important;margin-right:auto!important;}", content_style: "body {padding-left: 15px !important; padding-right: 15px !important; margin:0!important; margin-left:auto!important;margin-right:auto!important;}",
file_browser_callback: function(field_name, url, type, win) { file_browser_callback: function(field_name, url, type, win) {
ImageManager.show('#image-manager', function(image) { ImageManager.show(function(image) {
win.document.getElementById(field_name).value = image.url; win.document.getElementById(field_name).value = image.url;
if ("createEvent" in document) { if ("createEvent" in document) {
var evt = document.createEvent("HTMLEvents"); var evt = document.createEvent("HTMLEvents");