Dan Brown

Updated image controller styling and added preview option

The notification system was also updated so it can be used from JavaScript events such as image manager uploads.

Closes #25
1 "use strict"; 1 "use strict";
2 2
3 -module.exports = function (ngApp) { 3 +module.exports = function (ngApp, events) {
4 4
5 ngApp.controller('ImageManagerController', ['$scope', '$attrs', '$http', '$timeout', 'imageManagerService', 5 ngApp.controller('ImageManagerController', ['$scope', '$attrs', '$http', '$timeout', 'imageManagerService',
6 function ($scope, $attrs, $http, $timeout, imageManagerService) { 6 function ($scope, $attrs, $http, $timeout, imageManagerService) {
...@@ -17,21 +17,40 @@ module.exports = function (ngApp) { ...@@ -17,21 +17,40 @@ module.exports = function (ngApp) {
17 var dataLoaded = false; 17 var dataLoaded = false;
18 var callback = false; 18 var callback = false;
19 19
20 + /**
21 + * Simple returns the appropriate upload url depending on the image type set.
22 + * @returns {string}
23 + */
20 $scope.getUploadUrl = function () { 24 $scope.getUploadUrl = function () {
21 return '/images/' + $scope.imageType + '/upload'; 25 return '/images/' + $scope.imageType + '/upload';
22 }; 26 };
23 27
28 + /**
29 + * Runs on image upload, Adds an image to local list of images
30 + * and shows a success message to the user.
31 + * @param file
32 + * @param data
33 + */
24 $scope.uploadSuccess = function (file, data) { 34 $scope.uploadSuccess = function (file, data) {
25 $scope.$apply(() => { 35 $scope.$apply(() => {
26 $scope.images.unshift(data); 36 $scope.images.unshift(data);
27 }); 37 });
38 + events.emit('success', 'Image uploaded');
28 }; 39 };
29 40
41 + /**
42 + * Runs the callback and hides the image manager.
43 + * @param returnData
44 + */
30 function callbackAndHide(returnData) { 45 function callbackAndHide(returnData) {
31 if (callback) callback(returnData); 46 if (callback) callback(returnData);
32 $scope.showing = false; 47 $scope.showing = false;
33 } 48 }
34 49
50 + /**
51 + * Image select action. Checks if a double-click was fired.
52 + * @param image
53 + */
35 $scope.imageSelect = function (image) { 54 $scope.imageSelect = function (image) {
36 var dblClickTime = 300; 55 var dblClickTime = 300;
37 var currentTime = Date.now(); 56 var currentTime = Date.now();
...@@ -48,10 +67,19 @@ module.exports = function (ngApp) { ...@@ -48,10 +67,19 @@ module.exports = function (ngApp) {
48 previousClickTime = currentTime; 67 previousClickTime = currentTime;
49 }; 68 };
50 69
70 + /**
71 + * Action that runs when the 'Select image' button is clicked.
72 + * Runs the callback and hides the image manager.
73 + */
51 $scope.selectButtonClick = function () { 74 $scope.selectButtonClick = function () {
52 callbackAndHide($scope.selectedImage); 75 callbackAndHide($scope.selectedImage);
53 }; 76 };
54 77
78 + /**
79 + * Show the image manager.
80 + * Takes a callback to execute later on.
81 + * @param doneCallback
82 + */
55 function show(doneCallback) { 83 function show(doneCallback) {
56 callback = doneCallback; 84 callback = doneCallback;
57 $scope.showing = true; 85 $scope.showing = true;
...@@ -62,6 +90,8 @@ module.exports = function (ngApp) { ...@@ -62,6 +90,8 @@ module.exports = function (ngApp) {
62 } 90 }
63 } 91 }
64 92
93 + // Connects up the image manger so it can be used externally
94 + // such as from TinyMCE.
65 imageManagerService.show = show; 95 imageManagerService.show = show;
66 imageManagerService.showExternal = function (doneCallback) { 96 imageManagerService.showExternal = function (doneCallback) {
67 $scope.$apply(() => { 97 $scope.$apply(() => {
...@@ -70,10 +100,16 @@ module.exports = function (ngApp) { ...@@ -70,10 +100,16 @@ module.exports = function (ngApp) {
70 }; 100 };
71 window.ImageManager = imageManagerService; 101 window.ImageManager = imageManagerService;
72 102
103 + /**
104 + * Hide the image manager
105 + */
73 $scope.hide = function () { 106 $scope.hide = function () {
74 $scope.showing = false; 107 $scope.showing = false;
75 }; 108 };
76 109
110 + /**
111 + * Fetch the list image data from the server.
112 + */
77 function fetchData() { 113 function fetchData() {
78 var url = '/images/' + $scope.imageType + '/all/' + page; 114 var url = '/images/' + $scope.imageType + '/all/' + page;
79 $http.get(url).then((response) => { 115 $http.get(url).then((response) => {
...@@ -82,28 +118,33 @@ module.exports = function (ngApp) { ...@@ -82,28 +118,33 @@ module.exports = function (ngApp) {
82 page++; 118 page++;
83 }); 119 });
84 } 120 }
121 + $scope.fetchData = fetchData;
85 122
123 + /**
124 + * Save the details of an image.
125 + * @param event
126 + */
86 $scope.saveImageDetails = function (event) { 127 $scope.saveImageDetails = function (event) {
87 event.preventDefault(); 128 event.preventDefault();
88 var url = '/images/update/' + $scope.selectedImage.id; 129 var url = '/images/update/' + $scope.selectedImage.id;
89 $http.put(url, this.selectedImage).then((response) => { 130 $http.put(url, this.selectedImage).then((response) => {
90 - $scope.imageUpdateSuccess = true; 131 + events.emit('success', 'Image details updated');
91 - $timeout(() => {
92 - $scope.imageUpdateSuccess = false;
93 - }, 3000);
94 }, (response) => { 132 }, (response) => {
95 var errors = response.data; 133 var errors = response.data;
96 var message = ''; 134 var message = '';
97 Object.keys(errors).forEach((key) => { 135 Object.keys(errors).forEach((key) => {
98 message += errors[key].join('\n'); 136 message += errors[key].join('\n');
99 }); 137 });
100 - $scope.imageUpdateFailure = message; 138 + events.emit('error', message);
101 - $timeout(() => {
102 - $scope.imageUpdateFailure = false;
103 - }, 5000);
104 }); 139 });
105 }; 140 };
106 141
142 + /**
143 + * Delete an image from system and notify of success.
144 + * Checks if it should force delete when an image
145 + * has dependant pages.
146 + * @param event
147 + */
107 $scope.deleteImage = function (event) { 148 $scope.deleteImage = function (event) {
108 event.preventDefault(); 149 event.preventDefault();
109 var force = $scope.dependantPages !== false; 150 var force = $scope.dependantPages !== false;
...@@ -112,10 +153,7 @@ module.exports = function (ngApp) { ...@@ -112,10 +153,7 @@ module.exports = function (ngApp) {
112 $http.delete(url).then((response) => { 153 $http.delete(url).then((response) => {
113 $scope.images.splice($scope.images.indexOf($scope.selectedImage), 1); 154 $scope.images.splice($scope.images.indexOf($scope.selectedImage), 1);
114 $scope.selectedImage = false; 155 $scope.selectedImage = false;
115 - $scope.imageDeleteSuccess = true; 156 + events.emit('success', 'Image successfully deleted');
116 - $timeout(() => {
117 - $scope.imageDeleteSuccess = false;
118 - }, 3000);
119 }, (response) => { 157 }, (response) => {
120 // Pages failure 158 // Pages failure
121 if (response.status === 400) { 159 if (response.status === 400) {
...@@ -124,6 +162,15 @@ module.exports = function (ngApp) { ...@@ -124,6 +162,15 @@ module.exports = function (ngApp) {
124 }); 162 });
125 }; 163 };
126 164
165 + /**
166 + * Simple date creator used to properly format dates.
167 + * @param stringDate
168 + * @returns {Date}
169 + */
170 + $scope.getDate = function(stringDate) {
171 + return new Date(stringDate);
172 + };
173 +
127 }]); 174 }]);
128 175
129 176
......
...@@ -5,7 +5,7 @@ var toggleSwitchTemplate = require('./components/toggle-switch.html'); ...@@ -5,7 +5,7 @@ var toggleSwitchTemplate = require('./components/toggle-switch.html');
5 var imagePickerTemplate = require('./components/image-picker.html'); 5 var imagePickerTemplate = require('./components/image-picker.html');
6 var dropZoneTemplate = require('./components/drop-zone.html'); 6 var dropZoneTemplate = require('./components/drop-zone.html');
7 7
8 -module.exports = function (ngApp) { 8 +module.exports = function (ngApp, events) {
9 9
10 /** 10 /**
11 * Toggle Switches 11 * Toggle Switches
......
1 - 1 +"use strict";
2 2
3 // AngularJS - Create application and load components 3 // AngularJS - Create application and load components
4 var angular = require('angular'); 4 var angular = require('angular');
...@@ -7,9 +7,31 @@ var ngAnimate = require('angular-animate'); ...@@ -7,9 +7,31 @@ var ngAnimate = require('angular-animate');
7 var ngSanitize = require('angular-sanitize'); 7 var ngSanitize = require('angular-sanitize');
8 8
9 var ngApp = angular.module('bookStack', ['ngResource', 'ngAnimate', 'ngSanitize']); 9 var ngApp = angular.module('bookStack', ['ngResource', 'ngAnimate', 'ngSanitize']);
10 -var services = require('./services')(ngApp); 10 +
11 -var directives = require('./directives')(ngApp); 11 +
12 -var controllers = require('./controllers')(ngApp); 12 +// Global Event System
13 +var Events = {
14 + listeners: {},
15 + emit: function (eventName, eventData) {
16 + if (typeof this.listeners[eventName] === 'undefined') return this;
17 + var eventsToStart = this.listeners[eventName];
18 + for (let i = 0; i < eventsToStart.length; i++) {
19 + var event = eventsToStart[i];
20 + event(eventData);
21 + }
22 + return this;
23 + },
24 + listen: function (eventName, callback) {
25 + if (typeof this.listeners[eventName] === 'undefined') this.listeners[eventName] = [];
26 + this.listeners[eventName].push(callback);
27 + return this;
28 + }
29 +};
30 +window.Events = Events;
31 +
32 +var services = require('./services')(ngApp, Events);
33 +var directives = require('./directives')(ngApp, Events);
34 +var controllers = require('./controllers')(ngApp, Events);
13 35
14 //Global jQuery Config & Extensions 36 //Global jQuery Config & Extensions
15 37
...@@ -32,8 +54,25 @@ $.expr[":"].contains = $.expr.createPseudo(function (arg) { ...@@ -32,8 +54,25 @@ $.expr[":"].contains = $.expr.createPseudo(function (arg) {
32 // Global jQuery Elements 54 // Global jQuery Elements
33 $(function () { 55 $(function () {
34 56
57 +
58 + var notifications = $('.notification');
59 + var successNotification = notifications.filter('.pos');
60 + var errorNotification = notifications.filter('.neg');
61 + // Notification Events
62 + window.Events.listen('success', function (text) {
63 + successNotification.hide();
64 + successNotification.find('span').text(text);
65 + setTimeout(() => {
66 + successNotification.show();
67 + }, 1);
68 + });
69 + window.Events.listen('error', function (text) {
70 + errorNotification.find('span').text(text);
71 + errorNotification.show();
72 + });
73 +
35 // Notification hiding 74 // Notification hiding
36 - $('.notification').click(function () { 75 + notifications.click(function () {
37 $(this).fadeOut(100); 76 $(this).fadeOut(100);
38 }); 77 });
39 78
......
...@@ -8,7 +8,6 @@ module.exports = { ...@@ -8,7 +8,6 @@ module.exports = {
8 statusbar: false, 8 statusbar: false,
9 menubar: false, 9 menubar: false,
10 paste_data_images: false, 10 paste_data_images: false,
11 - //height: 700,
12 extended_valid_elements: 'pre[*]', 11 extended_valid_elements: 'pre[*]',
13 automatic_uploads: false, 12 automatic_uploads: false,
14 valid_children: "-div[p|pre|h1|h2|h3|h4|h5|h6|blockquote]", 13 valid_children: "-div[p|pre|h1|h2|h3|h4|h5|h6|blockquote]",
...@@ -31,7 +30,7 @@ module.exports = { ...@@ -31,7 +30,7 @@ module.exports = {
31 alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-right'}, 30 alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-right'},
32 }, 31 },
33 file_browser_callback: function (field_name, url, type, win) { 32 file_browser_callback: function (field_name, url, type, win) {
34 - ImageManager.show(function (image) { 33 + window.ImageManager.showExternal(function (image) {
35 win.document.getElementById(field_name).value = image.url; 34 win.document.getElementById(field_name).value = image.url;
36 if ("createEvent" in document) { 35 if ("createEvent" in document) {
37 var evt = document.createEvent("HTMLEvents"); 36 var evt = document.createEvent("HTMLEvents");
...@@ -40,6 +39,10 @@ module.exports = { ...@@ -40,6 +39,10 @@ module.exports = {
40 } else { 39 } else {
41 win.document.getElementById(field_name).fireEvent("onchange"); 40 win.document.getElementById(field_name).fireEvent("onchange");
42 } 41 }
42 + var html = '<a href="' + image.url + '" target="_blank">';
43 + html += '<img src="' + image.thumbs.display + '" alt="' + image.name + '">';
44 + html += '</a>';
45 + win.tinyMCE.activeEditor.execCommand('mceInsertContent', false, html);
43 }); 46 });
44 }, 47 },
45 paste_preprocess: function (plugin, args) { 48 paste_preprocess: function (plugin, args) {
......
1 "use strict"; 1 "use strict";
2 2
3 -module.exports = function(ngApp) { 3 +module.exports = function(ngApp, events) {
4 4
5 ngApp.factory('imageManagerService', function() { 5 ngApp.factory('imageManagerService', function() {
6 return { 6 return {
......
...@@ -21,7 +21,6 @@ ...@@ -21,7 +21,6 @@
21 border-radius: 4px; 21 border-radius: 4px;
22 box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.3); 22 box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.3);
23 overflow: hidden; 23 overflow: hidden;
24 - max-width: 1340px;
25 position: fixed; 24 position: fixed;
26 top: 0; 25 top: 0;
27 bottom: 0; 26 bottom: 0;
...@@ -44,18 +43,49 @@ ...@@ -44,18 +43,49 @@
44 right: 0; 43 right: 0;
45 } 44 }
46 45
47 -.image-manager-list img { 46 +.image-manager-list .image {
48 display: block; 47 display: block;
48 + position: relative;
49 border-radius: 0; 49 border-radius: 0;
50 float: left; 50 float: left;
51 margin: 0; 51 margin: 0;
52 cursor: pointer; 52 cursor: pointer;
53 width: (100%/6); 53 width: (100%/6);
54 height: auto; 54 height: auto;
55 - border: 1px solid #FFF; 55 + border: 1px solid #DDD;
56 + box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
56 transition: all cubic-bezier(.4, 0, 1, 1) 160ms; 57 transition: all cubic-bezier(.4, 0, 1, 1) 160ms;
58 + overflow: hidden;
57 &.selected { 59 &.selected {
58 transform: scale3d(0.92, 0.92, 0.92); 60 transform: scale3d(0.92, 0.92, 0.92);
61 + border: 1px solid #444;
62 + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2);
63 + }
64 + img {
65 + width: 100%;
66 + max-width: 100%;
67 + display: block;
68 + }
69 + .image-meta {
70 + position: absolute;
71 + width: 100%;
72 + bottom: 0;
73 + left: 0;
74 + color: #EEE;
75 + background-color: rgba(0, 0, 0, 0.4);
76 + font-size: 10px;
77 + padding: 3px 4px;
78 + span {
79 + display: block;
80 + }
81 + }
82 + @include smaller-than($xl) {
83 + width: (100%/4);
84 + }
85 + @include smaller-than($m) {
86 + .image-meta {
87 + display: none;
88 + }
59 } 89 }
60 } 90 }
61 91
......
...@@ -5,11 +5,14 @@ ...@@ -5,11 +5,14 @@
5 <div class="image-manager-content"> 5 <div class="image-manager-content">
6 <div class="image-manager-list"> 6 <div class="image-manager-list">
7 <div ng-repeat="image in images"> 7 <div ng-repeat="image in images">
8 - <img class="anim fadeIn" 8 + <div class="image anim fadeIn" ng-style="{animationDelay: ($index > 26) ? '160ms' : ($index * 25) + 'ms'}"
9 - ng-class="{selected: (image==selectedImage)}" 9 + ng-class="{selected: (image==selectedImage)}" ng-click="imageSelect(image)">
10 - ng-src="@{{image.thumbs.gallery}}" ng-attr-alt="@{{image.title}}" ng-attr-title="@{{image.name}}" 10 + <img ng-src="@{{image.thumbs.gallery}}" ng-attr-alt="@{{image.title}}" ng-attr-title="@{{image.name}}">
11 - ng-click="imageSelect(image)" 11 + <div class="image-meta">
12 - ng-style="{animationDelay: ($index > 26) ? '160ms' : ($index * 25) + 'ms'}"> 12 + <span class="name" ng-bind="image.name"></span>
13 + <span class="date">Uploaded @{{ getDate(image.created_at) | date:'mediumDate' }}</span>
14 + </div>
15 + </div>
13 </div> 16 </div>
14 <div class="load-more" ng-show="hasMore" ng-click="fetchData()">Load More</div> 17 <div class="load-more" ng-show="hasMore" ng-click="fetchData()">Load More</div>
15 </div> 18 </div>
...@@ -19,18 +22,20 @@ ...@@ -19,18 +22,20 @@
19 22
20 <div class="image-manager-sidebar"> 23 <div class="image-manager-sidebar">
21 <h2>Images</h2> 24 <h2>Images</h2>
22 - <hr class="even">
23 <drop-zone upload-url="@{{getUploadUrl()}}" event-success="uploadSuccess"></drop-zone> 25 <drop-zone upload-url="@{{getUploadUrl()}}" event-success="uploadSuccess"></drop-zone>
24 <div class="image-manager-details anim fadeIn" ng-show="selectedImage"> 26 <div class="image-manager-details anim fadeIn" ng-show="selectedImage">
25 27
26 <hr class="even"> 28 <hr class="even">
27 29
28 <form ng-submit="saveImageDetails($event)"> 30 <form ng-submit="saveImageDetails($event)">
31 + <div>
32 + <a ng-href="@{{selectedImage.url}}" target="_blank" style="display: block;">
33 + <img ng-src="@{{selectedImage.thumbs.gallery}}" ng-attr-alt="@{{selectedImage.title}}" ng-attr-title="@{{selectedImage.name}}">
34 + </a>
35 + </div>
29 <div class="form-group"> 36 <div class="form-group">
30 <label for="name">Image Name</label> 37 <label for="name">Image Name</label>
31 <input type="text" id="name" name="name" ng-model="selectedImage.name"> 38 <input type="text" id="name" name="name" ng-model="selectedImage.name">
32 - <p class="text-pos text-small" ng-show="imageUpdateSuccess"><i class="fa fa-check"></i> Image name updated</p>
33 - <p class="text-neg text-small" ng-show="imageUpdateFailure"><i class="fa fa-times"></i> <span ng-bind="imageUpdateFailure"></span></p>
34 </div> 39 </div>
35 </form> 40 </form>
36 41
...@@ -53,8 +58,6 @@ ...@@ -53,8 +58,6 @@
53 </form> 58 </form>
54 </div> 59 </div>
55 60
56 - <p class="text-pos" ng-show="imageDeleteSuccess"><i class="fa fa-check"></i> Image deleted</p>
57 -
58 <div class="image-manager-bottom"> 61 <div class="image-manager-bottom">
59 <button class="button pos anim fadeIn" ng-show="selectedImage" ng-click="selectButtonClick()"> 62 <button class="button pos anim fadeIn" ng-show="selectedImage" ng-click="selectButtonClick()">
60 <i class="zmdi zmdi-square-right"></i>Select Image 63 <i class="zmdi zmdi-square-right"></i>Select Image
......
1 -@if(Session::has('success'))
2 - <div class="notification anim pos">
3 - <i class="zmdi zmdi-mood"></i> <span>{{ Session::get('success') }}</span>
4 - </div>
5 -@endif
6 1
7 -@if(Session::has('error'))
8 - <div class="notification anim neg stopped">
9 - <i class="zmdi zmdi-alert-circle"></i> <span>{{ Session::get('error') }}</span>
10 - </div>
11 -@endif
...\ No newline at end of file ...\ No newline at end of file
2 +<div class="notification anim pos" @if(!Session::has('success')) style="display:none;" @endif>
3 + <i class="zmdi zmdi-check-circle"></i> <span>{{ Session::get('success') }}</span>
4 +</div>
5 +
6 +<div class="notification anim neg stopped" @if(!Session::has('error')) style="display:none;" @endif>
7 + <i class="zmdi zmdi-alert-circle"></i> <span>{{ Session::get('error') }}</span>
8 +</div>
......