#9, #8: Photos can now be amended again through the "show album" page. Added Tether for Bootstrap v4 tooltip support.
This commit is contained in:
parent
3c2d50f373
commit
ce1c82b275
@ -10,6 +10,12 @@ use Illuminate\Support\Facades\Auth;
|
|||||||
class DbHelper
|
class DbHelper
|
||||||
{
|
{
|
||||||
public static function getAlbumsForCurrentUser()
|
public static function getAlbumsForCurrentUser()
|
||||||
|
{
|
||||||
|
return self::getAlbumsForCurrentUser_NonPaged()
|
||||||
|
->paginate(UserConfig::get('items_per_page'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getAlbumsForCurrentUser_NonPaged()
|
||||||
{
|
{
|
||||||
$albumsQuery = Album::query();
|
$albumsQuery = Album::query();
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
@ -57,8 +63,7 @@ class DbHelper
|
|||||||
return $albumsQuery->select('albums.*')
|
return $albumsQuery->select('albums.*')
|
||||||
->distinct()
|
->distinct()
|
||||||
->orderBy('name')
|
->orderBy('name')
|
||||||
->withCount('photos')
|
->withCount('photos');
|
||||||
->paginate(UserConfig::get('items_per_page'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getAlbumByAliasForCurrentUser($urlAlias)
|
public static function getAlbumByAliasForCurrentUser($urlAlias)
|
||||||
|
@ -59,7 +59,8 @@ class GlobalConfiguration
|
|||||||
|
|
||||||
private function addAlbumsToView()
|
private function addAlbumsToView()
|
||||||
{
|
{
|
||||||
$albums = DbHelper::getAlbumsForCurrentUser();
|
//$albums = DbHelper::getAlbumsForCurrentUser_NonPaged()->get();
|
||||||
|
$albums = Album::all();
|
||||||
View::share('albums', $albums);
|
View::share('albums', $albums);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
17
resources/assets/css/admin.css
vendored
17
resources/assets/css/admin.css
vendored
@ -5,4 +5,21 @@
|
|||||||
.card-header.card-danger {
|
.card-header.card-danger {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo .loading {
|
||||||
|
background-color: #ffffff;
|
||||||
|
display: none;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
opacity: 0.8;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo .loading img {
|
||||||
|
margin-top: 40px;
|
||||||
}
|
}
|
21
resources/assets/css/tether-theme-basic.css
vendored
Normal file
21
resources/assets/css/tether-theme-basic.css
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
.tether-element, .tether-element:after, .tether-element:before, .tether-element *, .tether-element *:after, .tether-element *:before {
|
||||||
|
box-sizing: border-box; }
|
||||||
|
|
||||||
|
.tether-element {
|
||||||
|
position: absolute;
|
||||||
|
display: none; }
|
||||||
|
.tether-element.tether-open {
|
||||||
|
display: block; }
|
||||||
|
|
||||||
|
.tether-element.tether-theme-basic {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%; }
|
||||||
|
.tether-element.tether-theme-basic .tether-content {
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
font-family: inherit;
|
||||||
|
background: #fff;
|
||||||
|
color: inherit;
|
||||||
|
padding: 1em;
|
||||||
|
font-size: 1.1em;
|
||||||
|
line-height: 1.5em; }
|
8
resources/assets/css/tether.css
vendored
Normal file
8
resources/assets/css/tether.css
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.tether-element, .tether-element:after, .tether-element:before, .tether-element *, .tether-element *:after, .tether-element *:before {
|
||||||
|
box-sizing: border-box; }
|
||||||
|
|
||||||
|
.tether-element {
|
||||||
|
position: absolute;
|
||||||
|
display: none; }
|
||||||
|
.tether-element.tether-open {
|
||||||
|
display: block; }
|
985
resources/assets/js/002-bootbox.js
Normal file
985
resources/assets/js/002-bootbox.js
Normal file
@ -0,0 +1,985 @@
|
|||||||
|
/**
|
||||||
|
* bootbox.js [v4.4.0]
|
||||||
|
*
|
||||||
|
* http://bootboxjs.com/license.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
// @see https://github.com/makeusabrew/bootbox/issues/180
|
||||||
|
// @see https://github.com/makeusabrew/bootbox/issues/186
|
||||||
|
(function (root, factory) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
if (typeof define === "function" && define.amd) {
|
||||||
|
// AMD. Register as an anonymous module.
|
||||||
|
define(["resources/assets/js/001-jquery"], factory);
|
||||||
|
} else if (typeof exports === "object") {
|
||||||
|
// Node. Does not work with strict CommonJS, but
|
||||||
|
// only CommonJS-like environments that support module.exports,
|
||||||
|
// like Node.
|
||||||
|
module.exports = factory(require("resources/assets/js/001-jquery"));
|
||||||
|
} else {
|
||||||
|
// Browser globals (root is window)
|
||||||
|
root.bootbox = factory(root.jQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
}(this, function init($, undefined) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// the base DOM structure needed to create a modal
|
||||||
|
var templates = {
|
||||||
|
dialog:
|
||||||
|
"<div class='bootbox modal' tabindex='-1' role='dialog'>" +
|
||||||
|
"<div class='modal-dialog'>" +
|
||||||
|
"<div class='modal-content'>" +
|
||||||
|
"<div class='modal-body'><div class='bootbox-body'></div></div>" +
|
||||||
|
"</div>" +
|
||||||
|
"</div>" +
|
||||||
|
"</div>",
|
||||||
|
header:
|
||||||
|
"<div class='modal-header'>" +
|
||||||
|
"<h4 class='modal-title'></h4>" +
|
||||||
|
"</div>",
|
||||||
|
footer:
|
||||||
|
"<div class='modal-footer'></div>",
|
||||||
|
closeButton:
|
||||||
|
"<button type='button' class='bootbox-close-button close' data-dismiss='modal' aria-hidden='true'>×</button>",
|
||||||
|
form:
|
||||||
|
"<form class='bootbox-form'></form>",
|
||||||
|
inputs: {
|
||||||
|
text:
|
||||||
|
"<input class='bootbox-input bootbox-input-text form-control' autocomplete=off type=text />",
|
||||||
|
textarea:
|
||||||
|
"<textarea class='bootbox-input bootbox-input-textarea form-control'></textarea>",
|
||||||
|
email:
|
||||||
|
"<input class='bootbox-input bootbox-input-email form-control' autocomplete='off' type='email' />",
|
||||||
|
select:
|
||||||
|
"<select class='bootbox-input bootbox-input-select form-control'></select>",
|
||||||
|
checkbox:
|
||||||
|
"<div class='checkbox'><label><input class='bootbox-input bootbox-input-checkbox' type='checkbox' /></label></div>",
|
||||||
|
date:
|
||||||
|
"<input class='bootbox-input bootbox-input-date form-control' autocomplete=off type='date' />",
|
||||||
|
time:
|
||||||
|
"<input class='bootbox-input bootbox-input-time form-control' autocomplete=off type='time' />",
|
||||||
|
number:
|
||||||
|
"<input class='bootbox-input bootbox-input-number form-control' autocomplete=off type='number' />",
|
||||||
|
password:
|
||||||
|
"<input class='bootbox-input bootbox-input-password form-control' autocomplete='off' type='password' />"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var defaults = {
|
||||||
|
// default language
|
||||||
|
locale: "en",
|
||||||
|
// show backdrop or not. Default to static so user has to interact with dialog
|
||||||
|
backdrop: "static",
|
||||||
|
// animate the modal in/out
|
||||||
|
animate: true,
|
||||||
|
// additional class string applied to the top level dialog
|
||||||
|
className: null,
|
||||||
|
// whether or not to include a close button
|
||||||
|
closeButton: true,
|
||||||
|
// show the dialog immediately by default
|
||||||
|
show: true,
|
||||||
|
// dialog container
|
||||||
|
container: "body"
|
||||||
|
};
|
||||||
|
|
||||||
|
// our public object; augmented after our private API
|
||||||
|
var exports = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _t(key) {
|
||||||
|
var locale = locales[defaults.locale];
|
||||||
|
return locale ? locale[key] : locales.en[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
function processCallback(e, dialog, callback) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// by default we assume a callback will get rid of the dialog,
|
||||||
|
// although it is given the opportunity to override this
|
||||||
|
|
||||||
|
// so, if the callback can be invoked and it *explicitly returns false*
|
||||||
|
// then we'll set a flag to keep the dialog active...
|
||||||
|
var preserveDialog = $.isFunction(callback) && callback.call(dialog, e) === false;
|
||||||
|
|
||||||
|
// ... otherwise we'll bin it
|
||||||
|
if (!preserveDialog) {
|
||||||
|
dialog.modal("hide");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKeyLength(obj) {
|
||||||
|
// @TODO defer to Object.keys(x).length if available?
|
||||||
|
var k, t = 0;
|
||||||
|
for (k in obj) {
|
||||||
|
t ++;
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
function each(collection, iterator) {
|
||||||
|
var index = 0;
|
||||||
|
$.each(collection, function(key, value) {
|
||||||
|
iterator(key, value, index++);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitize(options) {
|
||||||
|
var buttons;
|
||||||
|
var total;
|
||||||
|
|
||||||
|
if (typeof options !== "object") {
|
||||||
|
throw new Error("Please supply an object of options");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.message) {
|
||||||
|
throw new Error("Please specify a message");
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure any supplied options take precedence over defaults
|
||||||
|
options = $.extend({}, defaults, options);
|
||||||
|
|
||||||
|
if (!options.buttons) {
|
||||||
|
options.buttons = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons = options.buttons;
|
||||||
|
|
||||||
|
total = getKeyLength(buttons);
|
||||||
|
|
||||||
|
each(buttons, function(key, button, index) {
|
||||||
|
|
||||||
|
if ($.isFunction(button)) {
|
||||||
|
// short form, assume value is our callback. Since button
|
||||||
|
// isn't an object it isn't a reference either so re-assign it
|
||||||
|
button = buttons[key] = {
|
||||||
|
callback: button
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// before any further checks make sure by now button is the correct type
|
||||||
|
if ($.type(button) !== "object") {
|
||||||
|
throw new Error("button with key " + key + " must be an object");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!button.label) {
|
||||||
|
// the lack of an explicit label means we'll assume the key is good enough
|
||||||
|
button.label = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!button.className) {
|
||||||
|
if (total <= 2 && index === total-1) {
|
||||||
|
// always add a primary to the main option in a two-button dialog
|
||||||
|
button.className = "btn-primary";
|
||||||
|
} else {
|
||||||
|
button.className = "btn-secondary";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* map a flexible set of arguments into a single returned object
|
||||||
|
* if args.length is already one just return it, otherwise
|
||||||
|
* use the properties argument to map the unnamed args to
|
||||||
|
* object properties
|
||||||
|
* so in the latter case:
|
||||||
|
* mapArguments(["foo", $.noop], ["message", "callback"])
|
||||||
|
* -> { message: "foo", callback: $.noop }
|
||||||
|
*/
|
||||||
|
function mapArguments(args, properties) {
|
||||||
|
var argn = args.length;
|
||||||
|
var options = {};
|
||||||
|
|
||||||
|
if (argn < 1 || argn > 2) {
|
||||||
|
throw new Error("Invalid argument length");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argn === 2 || typeof args[0] === "string") {
|
||||||
|
options[properties[0]] = args[0];
|
||||||
|
options[properties[1]] = args[1];
|
||||||
|
} else {
|
||||||
|
options = args[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* merge a set of default dialog options with user supplied arguments
|
||||||
|
*/
|
||||||
|
function mergeArguments(defaults, args, properties) {
|
||||||
|
return $.extend(
|
||||||
|
// deep merge
|
||||||
|
true,
|
||||||
|
// ensure the target is an empty, unreferenced object
|
||||||
|
{},
|
||||||
|
// the base options object for this type of dialog (often just buttons)
|
||||||
|
defaults,
|
||||||
|
// args could be an object or array; if it's an array properties will
|
||||||
|
// map it to a proper options object
|
||||||
|
mapArguments(
|
||||||
|
args,
|
||||||
|
properties
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this entry-level method makes heavy use of composition to take a simple
|
||||||
|
* range of inputs and return valid options suitable for passing to bootbox.dialog
|
||||||
|
*/
|
||||||
|
function mergeDialogOptions(className, labels, properties, args) {
|
||||||
|
// build up a base set of dialog properties
|
||||||
|
var baseOptions = {
|
||||||
|
className: "bootbox-" + className,
|
||||||
|
buttons: createLabels.apply(null, labels)
|
||||||
|
};
|
||||||
|
|
||||||
|
// ensure the buttons properties generated, *after* merging
|
||||||
|
// with user args are still valid against the supplied labels
|
||||||
|
return validateButtons(
|
||||||
|
// merge the generated base properties with user supplied arguments
|
||||||
|
mergeArguments(
|
||||||
|
baseOptions,
|
||||||
|
args,
|
||||||
|
// if args.length > 1, properties specify how each arg maps to an object key
|
||||||
|
properties
|
||||||
|
),
|
||||||
|
labels
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* from a given list of arguments return a suitable object of button labels
|
||||||
|
* all this does is normalise the given labels and translate them where possible
|
||||||
|
* e.g. "ok", "confirm" -> { ok: "OK, cancel: "Annuleren" }
|
||||||
|
*/
|
||||||
|
function createLabels() {
|
||||||
|
var buttons = {};
|
||||||
|
|
||||||
|
for (var i = 0, j = arguments.length; i < j; i++) {
|
||||||
|
var argument = arguments[i];
|
||||||
|
var key = argument.toLowerCase();
|
||||||
|
var value = argument.toUpperCase();
|
||||||
|
|
||||||
|
buttons[key] = {
|
||||||
|
label: _t(value)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateButtons(options, buttons) {
|
||||||
|
var allowedButtons = {};
|
||||||
|
each(buttons, function(key, value) {
|
||||||
|
allowedButtons[value] = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
each(options.buttons, function(key) {
|
||||||
|
if (allowedButtons[key] === undefined) {
|
||||||
|
throw new Error("button key " + key + " is not allowed (options are " + buttons.join("\n") + ")");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.alert = function() {
|
||||||
|
var options;
|
||||||
|
|
||||||
|
options = mergeDialogOptions("alert", ["ok"], ["message", "callback"], arguments);
|
||||||
|
|
||||||
|
if (options.callback && !$.isFunction(options.callback)) {
|
||||||
|
throw new Error("alert requires callback property to be a function when provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* overrides
|
||||||
|
*/
|
||||||
|
options.buttons.ok.callback = options.onEscape = function() {
|
||||||
|
if ($.isFunction(options.callback)) {
|
||||||
|
return options.callback.call(this);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return exports.dialog(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.confirm = function() {
|
||||||
|
var options;
|
||||||
|
|
||||||
|
options = mergeDialogOptions("confirm", ["cancel", "confirm"], ["message", "callback"], arguments);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* overrides; undo anything the user tried to set they shouldn't have
|
||||||
|
*/
|
||||||
|
options.buttons.cancel.callback = options.onEscape = function() {
|
||||||
|
return options.callback.call(this, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
options.buttons.confirm.callback = function() {
|
||||||
|
return options.callback.call(this, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// confirm specific validation
|
||||||
|
if (!$.isFunction(options.callback)) {
|
||||||
|
throw new Error("confirm requires a callback");
|
||||||
|
}
|
||||||
|
|
||||||
|
return exports.dialog(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.prompt = function() {
|
||||||
|
var options;
|
||||||
|
var defaults;
|
||||||
|
var dialog;
|
||||||
|
var form;
|
||||||
|
var input;
|
||||||
|
var shouldShow;
|
||||||
|
var inputOptions;
|
||||||
|
|
||||||
|
// we have to create our form first otherwise
|
||||||
|
// its value is undefined when gearing up our options
|
||||||
|
// @TODO this could be solved by allowing message to
|
||||||
|
// be a function instead...
|
||||||
|
form = $(templates.form);
|
||||||
|
|
||||||
|
// prompt defaults are more complex than others in that
|
||||||
|
// users can override more defaults
|
||||||
|
// @TODO I don't like that prompt has to do a lot of heavy
|
||||||
|
// lifting which mergeDialogOptions can *almost* support already
|
||||||
|
// just because of 'value' and 'inputType' - can we refactor?
|
||||||
|
defaults = {
|
||||||
|
className: "bootbox-prompt",
|
||||||
|
buttons: createLabels("cancel", "confirm"),
|
||||||
|
value: "",
|
||||||
|
inputType: "text"
|
||||||
|
};
|
||||||
|
|
||||||
|
options = validateButtons(
|
||||||
|
mergeArguments(defaults, arguments, ["title", "callback"]),
|
||||||
|
["cancel", "confirm"]
|
||||||
|
);
|
||||||
|
|
||||||
|
// capture the user's show value; we always set this to false before
|
||||||
|
// spawning the dialog to give us a chance to attach some handlers to
|
||||||
|
// it, but we need to make sure we respect a preference not to show it
|
||||||
|
shouldShow = (options.show === undefined) ? true : options.show;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* overrides; undo anything the user tried to set they shouldn't have
|
||||||
|
*/
|
||||||
|
options.message = form;
|
||||||
|
|
||||||
|
options.buttons.cancel.callback = options.onEscape = function() {
|
||||||
|
return options.callback.call(this, null);
|
||||||
|
};
|
||||||
|
|
||||||
|
options.buttons.confirm.callback = function() {
|
||||||
|
var value;
|
||||||
|
|
||||||
|
switch (options.inputType) {
|
||||||
|
case "text":
|
||||||
|
case "textarea":
|
||||||
|
case "email":
|
||||||
|
case "select":
|
||||||
|
case "date":
|
||||||
|
case "time":
|
||||||
|
case "number":
|
||||||
|
case "password":
|
||||||
|
value = input.val();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "checkbox":
|
||||||
|
var checkedItems = input.find("input:checked");
|
||||||
|
|
||||||
|
// we assume that checkboxes are always multiple,
|
||||||
|
// hence we default to an empty array
|
||||||
|
value = [];
|
||||||
|
|
||||||
|
each(checkedItems, function(_, item) {
|
||||||
|
value.push($(item).val());
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options.callback.call(this, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
options.show = false;
|
||||||
|
|
||||||
|
// prompt specific validation
|
||||||
|
if (!options.title) {
|
||||||
|
throw new Error("prompt requires a title");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$.isFunction(options.callback)) {
|
||||||
|
throw new Error("prompt requires a callback");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!templates.inputs[options.inputType]) {
|
||||||
|
throw new Error("invalid prompt type");
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the input based on the supplied type
|
||||||
|
input = $(templates.inputs[options.inputType]);
|
||||||
|
|
||||||
|
switch (options.inputType) {
|
||||||
|
case "text":
|
||||||
|
case "textarea":
|
||||||
|
case "email":
|
||||||
|
case "date":
|
||||||
|
case "time":
|
||||||
|
case "number":
|
||||||
|
case "password":
|
||||||
|
input.val(options.value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "select":
|
||||||
|
var groups = {};
|
||||||
|
inputOptions = options.inputOptions || [];
|
||||||
|
|
||||||
|
if (!$.isArray(inputOptions)) {
|
||||||
|
throw new Error("Please pass an array of input options");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inputOptions.length) {
|
||||||
|
throw new Error("prompt with select requires options");
|
||||||
|
}
|
||||||
|
|
||||||
|
each(inputOptions, function(_, option) {
|
||||||
|
|
||||||
|
// assume the element to attach to is the input...
|
||||||
|
var elem = input;
|
||||||
|
|
||||||
|
if (option.value === undefined || option.text === undefined) {
|
||||||
|
throw new Error("given options in wrong format");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... but override that element if this option sits in a group
|
||||||
|
|
||||||
|
if (option.group) {
|
||||||
|
// initialise group if necessary
|
||||||
|
if (!groups[option.group]) {
|
||||||
|
groups[option.group] = $("<optgroup/>").attr("label", option.group);
|
||||||
|
}
|
||||||
|
|
||||||
|
elem = groups[option.group];
|
||||||
|
}
|
||||||
|
|
||||||
|
elem.append("<option value='" + option.value + "'>" + option.text + "</option>");
|
||||||
|
});
|
||||||
|
|
||||||
|
each(groups, function(_, group) {
|
||||||
|
input.append(group);
|
||||||
|
});
|
||||||
|
|
||||||
|
// safe to set a select's value as per a normal input
|
||||||
|
input.val(options.value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "checkbox":
|
||||||
|
var values = $.isArray(options.value) ? options.value : [options.value];
|
||||||
|
inputOptions = options.inputOptions || [];
|
||||||
|
|
||||||
|
if (!inputOptions.length) {
|
||||||
|
throw new Error("prompt with checkbox requires options");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inputOptions[0].value || !inputOptions[0].text) {
|
||||||
|
throw new Error("given options in wrong format");
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkboxes have to nest within a containing element, so
|
||||||
|
// they break the rules a bit and we end up re-assigning
|
||||||
|
// our 'input' element to this container instead
|
||||||
|
input = $("<div/>");
|
||||||
|
|
||||||
|
each(inputOptions, function(_, option) {
|
||||||
|
var checkbox = $(templates.inputs[options.inputType]);
|
||||||
|
|
||||||
|
checkbox.find("input").attr("value", option.value);
|
||||||
|
checkbox.find("label").append(option.text);
|
||||||
|
|
||||||
|
// we've ensured values is an array so we can always iterate over it
|
||||||
|
each(values, function(_, value) {
|
||||||
|
if (value === option.value) {
|
||||||
|
checkbox.find("input").prop("checked", true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
input.append(checkbox);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO provide an attributes option instead
|
||||||
|
// and simply map that as keys: vals
|
||||||
|
if (options.placeholder) {
|
||||||
|
input.attr("placeholder", options.placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.pattern) {
|
||||||
|
input.attr("pattern", options.pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.maxlength) {
|
||||||
|
input.attr("maxlength", options.maxlength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now place it in our form
|
||||||
|
form.append(input);
|
||||||
|
|
||||||
|
form.on("submit", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
// Fix for SammyJS (or similar JS routing library) hijacking the form post.
|
||||||
|
e.stopPropagation();
|
||||||
|
// @TODO can we actually click *the* button object instead?
|
||||||
|
// e.g. buttons.confirm.click() or similar
|
||||||
|
dialog.find(".btn-primary").click();
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog = exports.dialog(options);
|
||||||
|
|
||||||
|
// clear the existing handler focusing the submit button...
|
||||||
|
dialog.off("shown.bs.modal");
|
||||||
|
|
||||||
|
// ...and replace it with one focusing our input, if possible
|
||||||
|
dialog.on("shown.bs.modal", function() {
|
||||||
|
// need the closure here since input isn't
|
||||||
|
// an object otherwise
|
||||||
|
input.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (shouldShow === true) {
|
||||||
|
dialog.modal("show");
|
||||||
|
}
|
||||||
|
|
||||||
|
return dialog;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.dialog = function(options) {
|
||||||
|
options = sanitize(options);
|
||||||
|
|
||||||
|
var dialog = $(templates.dialog);
|
||||||
|
var innerDialog = dialog.find(".modal-dialog");
|
||||||
|
var body = dialog.find(".modal-body");
|
||||||
|
var buttons = options.buttons;
|
||||||
|
var buttonStr = "";
|
||||||
|
var callbacks = {
|
||||||
|
onEscape: options.onEscape
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($.fn.modal === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
"$.fn.modal is not defined; please double check you have included " +
|
||||||
|
"the Bootstrap JavaScript library. See http://getbootstrap.com/javascript/ " +
|
||||||
|
"for more details."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
each(buttons, function(key, button) {
|
||||||
|
|
||||||
|
// @TODO I don't like this string appending to itself; bit dirty. Needs reworking
|
||||||
|
// can we just build up button elements instead? slower but neater. Then button
|
||||||
|
// can just become a template too
|
||||||
|
buttonStr += "<button data-bb-handler='" + key + "' type='button' class='btn " + button.className + "'>" + button.label + "</button>";
|
||||||
|
callbacks[key] = button.callback;
|
||||||
|
});
|
||||||
|
|
||||||
|
body.find(".bootbox-body").html(options.message);
|
||||||
|
|
||||||
|
if (options.animate === true) {
|
||||||
|
dialog.addClass("fade");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.className) {
|
||||||
|
dialog.addClass(options.className);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.size === "large") {
|
||||||
|
innerDialog.addClass("modal-lg");
|
||||||
|
} else if (options.size === "small") {
|
||||||
|
innerDialog.addClass("modal-sm");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.title) {
|
||||||
|
body.before(templates.header);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.closeButton) {
|
||||||
|
var closeButton = $(templates.closeButton);
|
||||||
|
|
||||||
|
if (options.title) {
|
||||||
|
dialog.find(".modal-header").prepend(closeButton);
|
||||||
|
} else {
|
||||||
|
closeButton.css("margin-top", "-10px").prependTo(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.title) {
|
||||||
|
dialog.find(".modal-title").html(options.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttonStr.length) {
|
||||||
|
body.after(templates.footer);
|
||||||
|
dialog.find(".modal-footer").html(buttonStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap event listeners; used handle extra
|
||||||
|
* setup & teardown required after the underlying
|
||||||
|
* modal has performed certain actions
|
||||||
|
*/
|
||||||
|
|
||||||
|
dialog.on("hidden.bs.modal", function(e) {
|
||||||
|
// ensure we don't accidentally intercept hidden events triggered
|
||||||
|
// by children of the current dialog. We shouldn't anymore now BS
|
||||||
|
// namespaces its events; but still worth doing
|
||||||
|
if (e.target === this) {
|
||||||
|
dialog.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
dialog.on("show.bs.modal", function() {
|
||||||
|
// sadly this doesn't work; show is called *just* before
|
||||||
|
// the backdrop is added so we'd need a setTimeout hack or
|
||||||
|
// otherwise... leaving in as would be nice
|
||||||
|
if (options.backdrop) {
|
||||||
|
dialog.next(".modal-backdrop").addClass("bootbox-backdrop");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
dialog.on("shown.bs.modal", function() {
|
||||||
|
dialog.find(".btn-primary:first").focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootbox event listeners; experimental and may not last
|
||||||
|
* just an attempt to decouple some behaviours from their
|
||||||
|
* respective triggers
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (options.backdrop !== "static") {
|
||||||
|
// A boolean true/false according to the Bootstrap docs
|
||||||
|
// should show a dialog the user can dismiss by clicking on
|
||||||
|
// the background.
|
||||||
|
// We always only ever pass static/false to the actual
|
||||||
|
// $.modal function because with `true` we can't trap
|
||||||
|
// this event (the .modal-backdrop swallows it)
|
||||||
|
// However, we still want to sort of respect true
|
||||||
|
// and invoke the escape mechanism instead
|
||||||
|
dialog.on("click.dismiss.bs.modal", function(e) {
|
||||||
|
// @NOTE: the target varies in >= 3.3.x releases since the modal backdrop
|
||||||
|
// moved *inside* the outer dialog rather than *alongside* it
|
||||||
|
if (dialog.children(".modal-backdrop").length) {
|
||||||
|
e.currentTarget = dialog.children(".modal-backdrop").get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.target !== e.currentTarget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.trigger("escape.close.bb");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.on("escape.close.bb", function(e) {
|
||||||
|
if (callbacks.onEscape) {
|
||||||
|
processCallback(e, dialog, callbacks.onEscape);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard jQuery event listeners; used to handle user
|
||||||
|
* interaction with our dialog
|
||||||
|
*/
|
||||||
|
|
||||||
|
dialog.on("click", ".modal-footer button", function(e) {
|
||||||
|
var callbackKey = $(this).data("bb-handler");
|
||||||
|
|
||||||
|
processCallback(e, dialog, callbacks[callbackKey]);
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.on("click", ".bootbox-close-button", function(e) {
|
||||||
|
// onEscape might be falsy but that's fine; the fact is
|
||||||
|
// if the user has managed to click the close button we
|
||||||
|
// have to close the dialog, callback or not
|
||||||
|
processCallback(e, dialog, callbacks.onEscape);
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.on("keyup", function(e) {
|
||||||
|
if (e.which === 27) {
|
||||||
|
dialog.trigger("escape.close.bb");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// the remainder of this method simply deals with adding our
|
||||||
|
// dialogent to the DOM, augmenting it with Bootstrap's modal
|
||||||
|
// functionality and then giving the resulting object back
|
||||||
|
// to our caller
|
||||||
|
|
||||||
|
$(options.container).append(dialog);
|
||||||
|
|
||||||
|
dialog.modal({
|
||||||
|
backdrop: options.backdrop ? "static": false,
|
||||||
|
keyboard: false,
|
||||||
|
show: false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.show) {
|
||||||
|
dialog.modal("show");
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO should we return the raw element here or should
|
||||||
|
// we wrap it in an object on which we can expose some neater
|
||||||
|
// methods, e.g. var d = bootbox.alert(); d.hide(); instead
|
||||||
|
// of d.modal("hide");
|
||||||
|
|
||||||
|
/*
|
||||||
|
function BBDialog(elem) {
|
||||||
|
this.elem = elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
BBDialog.prototype = {
|
||||||
|
hide: function() {
|
||||||
|
return this.elem.modal("hide");
|
||||||
|
},
|
||||||
|
show: function() {
|
||||||
|
return this.elem.modal("show");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
return dialog;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.setDefaults = function() {
|
||||||
|
var values = {};
|
||||||
|
|
||||||
|
if (arguments.length === 2) {
|
||||||
|
// allow passing of single key/value...
|
||||||
|
values[arguments[0]] = arguments[1];
|
||||||
|
} else {
|
||||||
|
// ... and as an object too
|
||||||
|
values = arguments[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$.extend(defaults, values);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.hideAll = function() {
|
||||||
|
$(".bootbox").modal("hide");
|
||||||
|
|
||||||
|
return exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* standard locales. Please add more according to ISO 639-1 standard. Multiple language variants are
|
||||||
|
* unlikely to be required. If this gets too large it can be split out into separate JS files.
|
||||||
|
*/
|
||||||
|
var locales = {
|
||||||
|
bg_BG : {
|
||||||
|
OK : "Ок",
|
||||||
|
CANCEL : "Отказ",
|
||||||
|
CONFIRM : "Потвърждавам"
|
||||||
|
},
|
||||||
|
br : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Cancelar",
|
||||||
|
CONFIRM : "Sim"
|
||||||
|
},
|
||||||
|
cs : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Zrušit",
|
||||||
|
CONFIRM : "Potvrdit"
|
||||||
|
},
|
||||||
|
da : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Annuller",
|
||||||
|
CONFIRM : "Accepter"
|
||||||
|
},
|
||||||
|
de : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Abbrechen",
|
||||||
|
CONFIRM : "Akzeptieren"
|
||||||
|
},
|
||||||
|
el : {
|
||||||
|
OK : "Εντάξει",
|
||||||
|
CANCEL : "Ακύρωση",
|
||||||
|
CONFIRM : "Επιβεβαίωση"
|
||||||
|
},
|
||||||
|
en : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Cancel",
|
||||||
|
CONFIRM : "OK"
|
||||||
|
},
|
||||||
|
es : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Cancelar",
|
||||||
|
CONFIRM : "Aceptar"
|
||||||
|
},
|
||||||
|
et : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Katkesta",
|
||||||
|
CONFIRM : "OK"
|
||||||
|
},
|
||||||
|
fa : {
|
||||||
|
OK : "قبول",
|
||||||
|
CANCEL : "لغو",
|
||||||
|
CONFIRM : "تایید"
|
||||||
|
},
|
||||||
|
fi : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Peruuta",
|
||||||
|
CONFIRM : "OK"
|
||||||
|
},
|
||||||
|
fr : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Annuler",
|
||||||
|
CONFIRM : "D'accord"
|
||||||
|
},
|
||||||
|
he : {
|
||||||
|
OK : "אישור",
|
||||||
|
CANCEL : "ביטול",
|
||||||
|
CONFIRM : "אישור"
|
||||||
|
},
|
||||||
|
hu : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Mégsem",
|
||||||
|
CONFIRM : "Megerősít"
|
||||||
|
},
|
||||||
|
hr : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Odustani",
|
||||||
|
CONFIRM : "Potvrdi"
|
||||||
|
},
|
||||||
|
id : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Batal",
|
||||||
|
CONFIRM : "OK"
|
||||||
|
},
|
||||||
|
it : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Annulla",
|
||||||
|
CONFIRM : "Conferma"
|
||||||
|
},
|
||||||
|
ja : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "キャンセル",
|
||||||
|
CONFIRM : "確認"
|
||||||
|
},
|
||||||
|
lt : {
|
||||||
|
OK : "Gerai",
|
||||||
|
CANCEL : "Atšaukti",
|
||||||
|
CONFIRM : "Patvirtinti"
|
||||||
|
},
|
||||||
|
lv : {
|
||||||
|
OK : "Labi",
|
||||||
|
CANCEL : "Atcelt",
|
||||||
|
CONFIRM : "Apstiprināt"
|
||||||
|
},
|
||||||
|
nl : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Annuleren",
|
||||||
|
CONFIRM : "Accepteren"
|
||||||
|
},
|
||||||
|
no : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Avbryt",
|
||||||
|
CONFIRM : "OK"
|
||||||
|
},
|
||||||
|
pl : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Anuluj",
|
||||||
|
CONFIRM : "Potwierdź"
|
||||||
|
},
|
||||||
|
pt : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Cancelar",
|
||||||
|
CONFIRM : "Confirmar"
|
||||||
|
},
|
||||||
|
ru : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Отмена",
|
||||||
|
CONFIRM : "Применить"
|
||||||
|
},
|
||||||
|
sq : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Anulo",
|
||||||
|
CONFIRM : "Prano"
|
||||||
|
},
|
||||||
|
sv : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "Avbryt",
|
||||||
|
CONFIRM : "OK"
|
||||||
|
},
|
||||||
|
th : {
|
||||||
|
OK : "ตกลง",
|
||||||
|
CANCEL : "ยกเลิก",
|
||||||
|
CONFIRM : "ยืนยัน"
|
||||||
|
},
|
||||||
|
tr : {
|
||||||
|
OK : "Tamam",
|
||||||
|
CANCEL : "İptal",
|
||||||
|
CONFIRM : "Onayla"
|
||||||
|
},
|
||||||
|
zh_CN : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "取消",
|
||||||
|
CONFIRM : "确认"
|
||||||
|
},
|
||||||
|
zh_TW : {
|
||||||
|
OK : "OK",
|
||||||
|
CANCEL : "取消",
|
||||||
|
CONFIRM : "確認"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.addLocale = function(name, values) {
|
||||||
|
$.each(["OK", "CANCEL", "CONFIRM"], function(_, v) {
|
||||||
|
if (!values[v]) {
|
||||||
|
throw new Error("Please supply a translation for '" + v + "'");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
locales[name] = {
|
||||||
|
OK: values.OK,
|
||||||
|
CANCEL: values.CANCEL,
|
||||||
|
CONFIRM: values.CONFIRM
|
||||||
|
};
|
||||||
|
|
||||||
|
return exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.removeLocale = function(name) {
|
||||||
|
delete locales[name];
|
||||||
|
|
||||||
|
return exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.setLocale = function(name) {
|
||||||
|
return exports.setDefaults("locale", name);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.init = function(_$) {
|
||||||
|
return init(_$ || $);
|
||||||
|
};
|
||||||
|
|
||||||
|
return exports;
|
||||||
|
}));
|
1792
resources/assets/js/004-tether.js
Normal file
1792
resources/assets/js/004-tether.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -29,11 +29,12 @@ function AnalyseAlbumViewModel() {
|
|||||||
return this.numberTotal > 0 && (this.numberSuccessful + this.numberFailed >= this.numberTotal);
|
return this.numberTotal > 0 && (this.numberSuccessful + this.numberFailed >= this.numberTotal);
|
||||||
},
|
},
|
||||||
latestCompletedImages: function() {
|
latestCompletedImages: function() {
|
||||||
return this.imagesRecentlyCompleted.slice(
|
var startIndex = this.imagesRecentlyCompleted.length - 3 < 0
|
||||||
(this.imagesRecentlyCompleted.length - 3 < 0
|
? 0
|
||||||
? 0
|
: this.imagesRecentlyCompleted.length - 3;
|
||||||
: this.imagesRecentlyCompleted.length - 3)
|
var endIndex = startIndex + 3;
|
||||||
, 3);
|
|
||||||
|
return this.imagesRecentlyCompleted.slice(startIndex, endIndex);
|
||||||
},
|
},
|
||||||
numberTotal: function () {
|
numberTotal: function () {
|
||||||
return this.imagesToAnalyse.length;
|
return this.imagesToAnalyse.length;
|
||||||
@ -125,6 +126,340 @@ function AnalyseImageViewModel(image_info) {
|
|||||||
this.url = image_info.url;
|
this.url = image_info.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This model is used by admin/show_album.blade.php to handle photo changes.
|
||||||
|
* @param album_id ID of the album the photos are in
|
||||||
|
* @param language Array containing language strings
|
||||||
|
* @param urls Array containing URLs
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function EditPhotosViewModel(album_id, language, urls) {
|
||||||
|
this.el = '#photos-tab';
|
||||||
|
|
||||||
|
this.data = {
|
||||||
|
albums: [],
|
||||||
|
bulkModifyMethod: '',
|
||||||
|
isSubmitting: false,
|
||||||
|
photoIDs: [],
|
||||||
|
photoIDsAvailable: [],
|
||||||
|
selectAllInAlbum: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// When a photo is un-selected, remove the "select all in album" flag as the user has overridden the selection
|
||||||
|
/*self.photoIDs.subscribe(function (changes)
|
||||||
|
{
|
||||||
|
if (changes[0].status !== 'deleted')
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.selectAllInAlbum(0);
|
||||||
|
}, null, 'arrayChange');*/
|
||||||
|
|
||||||
|
this.methods = {
|
||||||
|
// Called when the Apply button on the "bulk apply selected actions" form is clicked
|
||||||
|
bulkModifySelected: function (e) {
|
||||||
|
if (this.isSubmitting) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bulk_form = $(e.target).closest('form');
|
||||||
|
|
||||||
|
if (this.bulkModifyMethod === 'change_album') {
|
||||||
|
// Prompt for the new album to move to
|
||||||
|
this.promptForNewAlbum(function (dialog) {
|
||||||
|
var album_id = $('select', dialog).val();
|
||||||
|
$('input[name="new-album-id"]', bulk_form).val(album_id);
|
||||||
|
|
||||||
|
self.isSubmitting = true;
|
||||||
|
$('button[name="bulk-apply"]', bulk_form).click();
|
||||||
|
_bt_showLoadingModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (this.bulkModifyMethod === 'delete') {
|
||||||
|
// Prompt for a confirmation - are you sure?!
|
||||||
|
bootbox.dialog({
|
||||||
|
message: language.delete_bulk_confirm_message,
|
||||||
|
title: language.delete_bulk_confirm_title,
|
||||||
|
buttons: {
|
||||||
|
cancel: {
|
||||||
|
label: language.action_cancel,
|
||||||
|
className: "btn-secondary"
|
||||||
|
},
|
||||||
|
confirm: {
|
||||||
|
label: language.action_delete,
|
||||||
|
className: "btn-danger",
|
||||||
|
callback: function () {
|
||||||
|
self.isSubmitting = true;
|
||||||
|
$('button[name="bulk-apply"]', bulk_form).click();
|
||||||
|
//_bt_showLoadingModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All other methods submit the form as normal
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
changeAlbum: function (e) {
|
||||||
|
this.selectPhotoSingle(e.target);
|
||||||
|
|
||||||
|
var photo_id = this.photoIDs[0];
|
||||||
|
this.photoIDs = [];
|
||||||
|
|
||||||
|
this.promptForNewAlbum(function (dialog) {
|
||||||
|
var album_id = $('select', dialog).val();
|
||||||
|
$.post(urls.move_photo.replace(/\/0$/, '/' + photo_id), {'new_album_id': album_id}, function () {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
//_bt_showLoadingModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
deletePhoto: function (e) {
|
||||||
|
var self = this;
|
||||||
|
this.selectPhotoSingle(e.target);
|
||||||
|
|
||||||
|
var photo_id = self.photoIDs[0];
|
||||||
|
this.photoIDs = [];
|
||||||
|
|
||||||
|
bootbox.dialog({
|
||||||
|
message: language.delete_confirm_message,
|
||||||
|
title: language.delete_confirm_title,
|
||||||
|
buttons: {
|
||||||
|
cancel: {
|
||||||
|
label: language.action_cancel,
|
||||||
|
className: "btn-secondary"
|
||||||
|
},
|
||||||
|
confirm: {
|
||||||
|
label: language.action_delete,
|
||||||
|
className: "btn-danger",
|
||||||
|
callback: function () {
|
||||||
|
var url = urls.delete_photo;
|
||||||
|
url = url.replace(/\/0$/, '/' + photo_id);
|
||||||
|
|
||||||
|
$('.loading', parent).show();
|
||||||
|
|
||||||
|
$.post(url, {'_method': 'DELETE'}, function (data) {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
flip: function (horizontal, vertical, parent) {
|
||||||
|
var url = urls.flip_photo;
|
||||||
|
url = url.replace('/0/', '/' + this.photoIDs[0] + '/');
|
||||||
|
|
||||||
|
if (horizontal) {
|
||||||
|
url = url.replace(/\/-1\//, '/1/');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
url = url.replace(/\/-1\//, '/0/');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vertical) {
|
||||||
|
url = url.replace(/\/-2$/, '/1');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
url = url.replace(/\/-2$/, '/0');
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.loading', parent).show();
|
||||||
|
$.post(url, function () {
|
||||||
|
var image = $('img.photo-thumbnail', parent);
|
||||||
|
var originalUrl = image.data('original-src');
|
||||||
|
image.attr('src', originalUrl + "&_=" + new Date().getTime());
|
||||||
|
|
||||||
|
$('.loading', parent).hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.photoIDs = [];
|
||||||
|
},
|
||||||
|
flipBoth: function (e) {
|
||||||
|
this.selectPhotoSingle(e.target);
|
||||||
|
this.flip(true, true, $(e.target).closest('.photo'));
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
flipHorizontal: function (e) {
|
||||||
|
this.selectPhotoSingle(e.target);
|
||||||
|
this.flip(true, false, $(e.target).closest('.photo'));
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
flipVertical: function (e) {
|
||||||
|
this.selectPhotoSingle(e.target);
|
||||||
|
this.flip(false, true, $(e.target).closest('.photo'));
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isPhotoSelected: function(photoID) {
|
||||||
|
if (this.photoIDs.indexOf(photoID) > -1)
|
||||||
|
{
|
||||||
|
return 'checked';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
promptForNewAlbum: function (callback_on_selected) {
|
||||||
|
var albums = this.albums;
|
||||||
|
var select = $('<select/>')
|
||||||
|
.attr('name', 'album_id')
|
||||||
|
.addClass('form-control');
|
||||||
|
|
||||||
|
for (var i = 0; i < albums.length; i++) {
|
||||||
|
var option = $('<option/>')
|
||||||
|
.attr('value', albums[i].id)
|
||||||
|
.html(albums[i].name)
|
||||||
|
.appendTo(select);
|
||||||
|
|
||||||
|
// Pre-select the current album
|
||||||
|
if (album_id === albums[i].id) {
|
||||||
|
option.attr('selected', 'selected');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bootbox.dialog({
|
||||||
|
message: $('<p/>').html(language.change_album_message).prop('outerHTML') + select.prop('outerHTML'),
|
||||||
|
title: language.change_album_title,
|
||||||
|
buttons: {
|
||||||
|
cancel: {
|
||||||
|
label: language.action_cancel,
|
||||||
|
className: 'btn-secondary'
|
||||||
|
},
|
||||||
|
confirm: {
|
||||||
|
label: language.action_continue,
|
||||||
|
className: 'btn-success',
|
||||||
|
callback: function () {
|
||||||
|
callback_on_selected(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
regenerateThumbnails: function (e) {
|
||||||
|
this.selectPhotoSingle(e.target);
|
||||||
|
var parent = $(e.target).closest('.photo');
|
||||||
|
|
||||||
|
var url = urls.regenerate_thumbnails;
|
||||||
|
url = url.replace(/\/0$/, '/' + this.photoIDs[0]);
|
||||||
|
|
||||||
|
$('.loading', parent).show();
|
||||||
|
$.post(url, function () {
|
||||||
|
var image = $('img.photo-thumbnail', parent);
|
||||||
|
var originalUrl = image.data('original-src');
|
||||||
|
image.attr('src', originalUrl + "&_=" + new Date().getTime());
|
||||||
|
|
||||||
|
$('.loading', parent).hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.photoIDs = [];
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
rotate: function (angle, parent) {
|
||||||
|
var url = urls.rotate_photo;
|
||||||
|
url = url.replace('/0/', '/' + this.photoIDs[0] + '/');
|
||||||
|
url = url.replace(/\/1$/, '/' + angle);
|
||||||
|
|
||||||
|
$('.loading', parent).show();
|
||||||
|
$.post(url, function () {
|
||||||
|
var image = $('img.photo-thumbnail', parent);
|
||||||
|
var originalUrl = image.data('original-src');
|
||||||
|
image.attr('src', originalUrl + "&_=" + new Date().getTime());
|
||||||
|
|
||||||
|
$('.loading', parent).hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.photoIDs = [];
|
||||||
|
},
|
||||||
|
rotateLeft: function (e) {
|
||||||
|
this.selectPhotoSingle(e.target);
|
||||||
|
this.rotate(90, $(e.target).closest('.photo'));
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
rotateRight: function(e)
|
||||||
|
{
|
||||||
|
this.selectPhotoSingle(e.target);
|
||||||
|
this.rotate(270, $(e.target).closest('.photo'));
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
selectAll: function() {
|
||||||
|
var self = this;
|
||||||
|
bootbox.dialog({
|
||||||
|
title: language.select_all_choice_title,
|
||||||
|
message: language.select_all_choice_message,
|
||||||
|
buttons: {
|
||||||
|
select_all: {
|
||||||
|
label: language.select_all_choice_all_action,
|
||||||
|
className: 'btn-secondary',
|
||||||
|
callback: function()
|
||||||
|
{
|
||||||
|
self.selectAllInAlbum = 1;
|
||||||
|
for (i = 0; i < self.photoIDsAvailable.length; i++)
|
||||||
|
{
|
||||||
|
self.photoIDs.push(self.photoIDsAvailable[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
select_visible: {
|
||||||
|
label: language.select_all_choice_visible_action,
|
||||||
|
className: 'btn-primary',
|
||||||
|
callback: function()
|
||||||
|
{
|
||||||
|
self.selectAllInAlbum = 0;
|
||||||
|
for (i = 0; i < self.photoIDsAvailable.length; i++)
|
||||||
|
{
|
||||||
|
self.photoIDs.push(self.photoIDsAvailable[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
selectNone: function() {
|
||||||
|
this.photoIDs = [];
|
||||||
|
this.selectAllInAlbum = 0;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
selectPhotoSingle: function (link_item) {
|
||||||
|
// Get the photo ID from the clicked link
|
||||||
|
var parent = $(link_item).closest('.photo');
|
||||||
|
var photo_id = $(parent).data('photo-id');
|
||||||
|
|
||||||
|
// Save the photo ID
|
||||||
|
this.photoIDs = [];
|
||||||
|
this.photoIDs.push(photo_id);
|
||||||
|
|
||||||
|
// Hide the dropdown
|
||||||
|
$(link_item).dropdown('toggle');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This model is used by admin/show_album.blade.php to handle photo uploads.
|
* This model is used by admin/show_album.blade.php to handle photo uploads.
|
||||||
* @param album_id ID of the album the photos are being uploaded to
|
* @param album_id ID of the album the photos are being uploaded to
|
||||||
|
@ -28,53 +28,7 @@
|
|||||||
|
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
{{-- Photos --}}
|
{{-- Photos --}}
|
||||||
<div role="tabpanel" class="tab-pane{{ $active_tab == 'photos' ? ' active' : '' }}" id="photos-tab">
|
@include(Theme::viewName('partials.album_photos_tab'))
|
||||||
@if (count($photos) == 0)
|
|
||||||
<div class="text-center" style="margin-top: 30px;">
|
|
||||||
<h4 class="text-danger"><b>@lang('admin.album_no_photos_p1')</b></h4>
|
|
||||||
<p>@lang('admin.album_no_photos_p2')</p>
|
|
||||||
<p style="margin-top: 30px;"><button id="upload-button" class="btn btn-lg btn-success"><i class="fa fa-fw fa-upload"></i> @lang('admin.album_no_photos_button')</button></p>
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<form action="{{ route('photos.updateBulk', [$album->id]) }}" method="POST">
|
|
||||||
{{ csrf_field() }}
|
|
||||||
{{ method_field('PUT') }}
|
|
||||||
|
|
||||||
@foreach ($photos as $photo)
|
|
||||||
@include (Theme::viewName('partials.single_photo_admin'))
|
|
||||||
@endforeach
|
|
||||||
|
|
||||||
<div class="pull-left" style="margin-bottom: 15px;">
|
|
||||||
<p style="margin-bottom: 15px;">
|
|
||||||
<button data-bind="click: selectAll" type="button" class="btn btn-default">@lang('admin.select_all_action')</button>
|
|
||||||
<button data-bind="click: selectNone" type="button" class="btn btn-default">@lang('admin.select_none_action')</button>
|
|
||||||
</p>
|
|
||||||
<input data-bind="value: selectAllInAlbum" type="hidden" name="select-all-album"/>
|
|
||||||
|
|
||||||
<div data-bind="visible: selectAllInAlbum" class="alert alert-warning">
|
|
||||||
<p>@lang('admin.select_all_album_active')</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p><label class="control-label" for="bulk-action">@lang('forms.bulk_edit_photos_label')</label></p>
|
|
||||||
<input type="hidden" name="new-album-id" value="{{ $album->id }}"/>
|
|
||||||
<select name="bulk-action" data-bind="value: bulkModifyMethod, enable: photoIDs().length > 0">
|
|
||||||
<option>@lang('forms.bulk_edit_photos_placeholder')</option>
|
|
||||||
@foreach ($bulk_actions as $key => $value)
|
|
||||||
<option value="{{ $key }}"{{ $key == old('bulk-action') ? ' selected="selected"' : '' }}>{{ $value }}</option>
|
|
||||||
@endforeach
|
|
||||||
</select>
|
|
||||||
<button type="submit" class="btn btn-sm btn-primary" name="bulk-apply" value="clicked" data-bind="click: bulkModifySelected, enable: photoIDs().length > 0">@lang('forms.apply_action')</button>
|
|
||||||
</div>
|
|
||||||
<div class="text-right">
|
|
||||||
<button type="submit" class="btn btn-success">@lang('forms.save_action')</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="text-center">
|
|
||||||
{{ $photos->links() }}
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Upload --}}
|
{{-- Upload --}}
|
||||||
@include(Theme::viewName('partials.album_upload_tab'))
|
@include(Theme::viewName('partials.album_upload_tab'))
|
||||||
@ -275,19 +229,19 @@
|
|||||||
urls.rotate_photo = '{{ route('photos.rotate', ['id' => 0, 'angle' => 1]) }}';
|
urls.rotate_photo = '{{ route('photos.rotate', ['id' => 0, 'angle' => 1]) }}';
|
||||||
|
|
||||||
var viewModel = new UploadPhotosViewModel('{{ $album->id }}', '{{ $queue_token }}', language, urls);
|
var viewModel = new UploadPhotosViewModel('{{ $album->id }}', '{{ $queue_token }}', language, urls);
|
||||||
/*var editViewModel = new EditPhotosViewModel('{{ $album->id }}', language, urls);
|
var editViewModel = new EditPhotosViewModel('{{ $album->id }}', language, urls);
|
||||||
|
|
||||||
@foreach ($photos as $photo)
|
@foreach ($photos as $photo)
|
||||||
editViewModel.photoIDsAvailable.push('{{ $photo->id }}');
|
editViewModel.data.photoIDsAvailable.push({{ $photo->id }});
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
||||||
// Populate the list of albums in the view model
|
// Populate the list of albums in the view model
|
||||||
@foreach ($albums as $album)
|
@foreach ($albums as $album)
|
||||||
editViewModel.albums.push({
|
editViewModel.data.albums.push({
|
||||||
'id': '{{ $album->id }}',
|
'id': '{{ $album->id }}',
|
||||||
'name': '{!! addslashes($album->name) !!}'
|
'name': '{!! addslashes($album->name) !!}'
|
||||||
});
|
});
|
||||||
@endforeach*/
|
@endforeach
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('#upload-button').click(function() {
|
$('#upload-button').click(function() {
|
||||||
@ -358,7 +312,8 @@
|
|||||||
});*/
|
});*/
|
||||||
|
|
||||||
// Bind the view models to the relevant tab
|
// Bind the view models to the relevant tab
|
||||||
//ko.applyBindings(editViewModel, document.getElementById('photos-tab'));
|
var appEdit = new Vue(editViewModel);
|
||||||
|
|
||||||
var appUpload = new Vue(viewModel);
|
var appUpload = new Vue(viewModel);
|
||||||
appUpload.$watch('isUploadInProgress', function(value) {
|
appUpload.$watch('isUploadInProgress', function(value) {
|
||||||
if (value)
|
if (value)
|
||||||
@ -370,15 +325,6 @@
|
|||||||
$('#upload-progress-modal').modal('hide');
|
$('#upload-progress-modal').modal('hide');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
{{-- Whatever I try, I could not get VueJS to adjust the progress bars automatically --}}
|
|
||||||
/*appUpload.$watch('failedPercentage', function(value) {
|
|
||||||
alert('failed: ' + value);
|
|
||||||
$('.progress-bar.bg-danger', '#upload-progress-modal').width(value);
|
|
||||||
});
|
|
||||||
appUpload.$watch('successfulPercentage', function(value) {
|
|
||||||
alert('success: ' + value);
|
|
||||||
$('.progress-bar.bg-success', '#upload-progress-modal').width(value);
|
|
||||||
});*/
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@endpush
|
@endpush
|
@ -0,0 +1,47 @@
|
|||||||
|
<div role="tabpanel" class="tab-pane{{ $active_tab == 'photos' ? ' active' : '' }}" id="photos-tab">
|
||||||
|
@if (count($photos) == 0)
|
||||||
|
<div class="text-center" style="margin-top: 30px;">
|
||||||
|
<h4 class="text-danger"><b>@lang('admin.album_no_photos_p1')</b></h4>
|
||||||
|
<p>@lang('admin.album_no_photos_p2')</p>
|
||||||
|
<p style="margin-top: 30px;"><button id="upload-button" class="btn btn-lg btn-success"><i class="fa fa-fw fa-upload"></i> @lang('admin.album_no_photos_button')</button></p>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<form action="{{ route('photos.updateBulk', [$album->id]) }}" method="POST">
|
||||||
|
{{ csrf_field() }}
|
||||||
|
{{ method_field('PUT') }}
|
||||||
|
|
||||||
|
@foreach ($photos as $photo)
|
||||||
|
@include (Theme::viewName('partials.single_photo_admin'))
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
<div class="pull-left" style="margin-bottom: 15px;">
|
||||||
|
<p style="margin-bottom: 15px;">
|
||||||
|
<button v-on:click="selectAll" type="button" class="btn btn-secondary">@lang('admin.select_all_action')</button>
|
||||||
|
<button v-on:click="selectNone" type="button" class="btn btn-secondary">@lang('admin.select_none_action')</button>
|
||||||
|
</p>
|
||||||
|
<input v-model="selectAllInAlbum" type="hidden" name="select-all-album"/>
|
||||||
|
|
||||||
|
<div v-if="selectAllInAlbum" class="alert alert-warning">
|
||||||
|
@lang('admin.select_all_album_active')
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p><label class="control-label" for="bulk-action">@lang('forms.bulk_edit_photos_label')</label></p>
|
||||||
|
<input type="hidden" name="new-album-id" value="{{ $album->id }}"/>
|
||||||
|
<select name="bulk-action" data-bind="value: bulkModifyMethod, enable: photoIDs().length > 0">
|
||||||
|
<option>@lang('forms.bulk_edit_photos_placeholder')</option>
|
||||||
|
@foreach ($bulk_actions as $key => $value)
|
||||||
|
<option value="{{ $key }}"{{ $key == old('bulk-action') ? ' selected="selected"' : '' }}>{{ $value }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
<button type="submit" class="btn btn-sm btn-primary" name="bulk-apply" value="clicked" data-bind="click: bulkModifySelected, enable: photoIDs().length > 0">@lang('forms.apply_action')</button>
|
||||||
|
</div>
|
||||||
|
<div class="text-right">
|
||||||
|
<button type="submit" class="btn btn-success">@lang('forms.save_action')</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
{{ $photos->links() }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
@ -63,7 +63,7 @@
|
|||||||
@endif
|
@endif
|
||||||
|
|
||||||
{{-- Single-file upload progress modal --}}
|
{{-- Single-file upload progress modal --}}
|
||||||
<div class="modal" id="upload-progress-modal">
|
<div class="modal" id="upload-progress-modal" data-backdrop="static" data-keyboard="false">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
|
@ -9,36 +9,38 @@
|
|||||||
</a><br/>
|
</a><br/>
|
||||||
|
|
||||||
{{-- Photo editing tasks - these are hooked up using Javascript in admin/show_album --}}
|
{{-- Photo editing tasks - these are hooked up using Javascript in admin/show_album --}}
|
||||||
<div class="btn-toolbar" role="toolbar" style="margin-top: 5px;">
|
<div class="text-center mt-1">
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
<i class="fa fa-fw fa-pencil"></i> <span class="caret"></span>
|
<button type="button" class="btn btn-secondary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
</button>
|
<i class="fa fa-fw fa-pencil"></i> <span class="caret"></span>
|
||||||
<ul class="dropdown-menu">
|
</button>
|
||||||
<li><a href="#" class="rotate-photo-left"><i class="fa fa-fw fa-rotate-left"></i> @lang('admin.photo_actions.rotate_left')</a></li>
|
<div class="dropdown-menu">
|
||||||
<li><a href="#" class="rotate-photo-right"><i class="fa fa-fw fa-rotate-right"></i> @lang('admin.photo_actions.rotate_right')</a></li>
|
<a href="#" class="dropdown-item rotate-photo-left" v-on:click="rotateLeft"><i class="fa fa-fw fa-rotate-left"></i> @lang('admin.photo_actions.rotate_left')</a>
|
||||||
<li class="divider"></li>
|
<a href="#" class="dropdown-item rotate-photo-right" v-on:click="rotateRight"><i class="fa fa-fw fa-rotate-right"></i> @lang('admin.photo_actions.rotate_right')</a>
|
||||||
<li><a href="#" class="flip-photo-horizontal"><i class="fa fa-fw fa-arrows-h"></i> @lang('admin.photo_actions.flip_horizontal')</a></li>
|
<div class="dropdown-divider"></div>
|
||||||
<li><a href="#" class="flip-photo-vertical"><i class="fa fa-fw fa-arrows-v"></i> @lang('admin.photo_actions.flip_vertical')</a></li>
|
<a href="#" class="dropdown-item flip-photo-horizontal" v-on:click="flipHorizontal"><i class="fa fa-fw fa-arrows-h"></i> @lang('admin.photo_actions.flip_horizontal')</a>
|
||||||
<li><a href="#" class="flip-photo-both"><i class="fa fa-fw fa-retweet"></i> @lang('admin.photo_actions.flip_both')</a></li>
|
<a href="#" class="dropdown-item flip-photo-vertical" v-on:click="flipVertical"><i class="fa fa-fw fa-arrows-v"></i> @lang('admin.photo_actions.flip_vertical')</a>
|
||||||
</ul>
|
<a href="#" class="dropdown-item flip-photo-both" v-on:click="flipBoth"><i class="fa fa-fw fa-retweet"></i> @lang('admin.photo_actions.flip_both')</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<i class="fa fa-fw fa-cog"></i> <span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<a href="#" class="dropdown-item change-album" v-on:click="changeAlbum"><i class="fa fa-fw fa-share"></i> @lang('admin.photo_actions.change_album')</a>
|
||||||
|
<a href="#" class="dropdown-item regenerate-thumbnails" v-on:click="regenerateThumbnails"><i class="fa fa-fw fa-picture-o"></i> @lang('admin.photo_actions.refresh_thumbnails')</a>
|
||||||
|
<a href="#" class="dropdown-item delete-photo" v-on:click="deletePhoto"><i class="fa fa-fw fa-trash text-danger"></i> <span class="text-danger">@lang('admin.photo_actions.delete')</span></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-group" role="group">
|
<div class="mt-2">
|
||||||
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<input type="checkbox" id="select-photo-{{ $photo->id }}" name="select-photo[]" value="{{ $photo->id }}" v-bind:checked="isPhotoSelected({{ $photo->id }})" /> <label for="select-photo-{{ $photo->id }}">@lang('forms.select')</label>
|
||||||
<i class="fa fa-fw fa-cog"></i> <span class="caret"></span>
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li><a href="#" class="change-album"><i class="fa fa-fw fa-share"></i> @lang('admin.photo_actions.change_album')</a></li>
|
|
||||||
<li><a href="#" class="regenerate-thumbnails"><i class="fa fa-fw fa-picture-o"></i> @lang('admin.photo_actions.refresh_thumbnails')</a></li>
|
|
||||||
<li><a href="#" class="delete-photo"><i class="fa fa-fw fa-trash text-danger"></i> <span class="text-danger">@lang('admin.photo_actions.delete')</span></a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p style="margin-top: 10px;">
|
|
||||||
<input type="checkbox" id="select-photo-{{ $photo->id }}" name="select-photo[]" value="{{ $photo->id }}" data-bind="checked: photoIDs" /> <label for="select-photo-{{ $photo->id }}">@lang('forms.select')</label>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-sm-10">
|
<div class="col-xs-12 col-sm-10">
|
||||||
@php($validation_field_name = ('photo.' . $photo->id . '.name'))
|
@php($validation_field_name = ('photo.' . $photo->id . '.name'))
|
||||||
|
Loading…
Reference in New Issue
Block a user