122
Gruntfile.js
Normal file
122
Gruntfile.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
This Gruntfile downloads the necessary CSS and JS includes (Bootstrap, etc.) and creates a combined file ready for
|
||||||
|
deployment with the application.
|
||||||
|
|
||||||
|
Available tasks:
|
||||||
|
|
||||||
|
- build-debug: Builds the minified CSS and JS files
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = function(grunt)
|
||||||
|
{
|
||||||
|
var download_url = 'https://cdn.andysh.uk/';
|
||||||
|
|
||||||
|
const sass = require('node-sass');
|
||||||
|
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||||
|
grunt.loadNpmTasks('grunt-curl');
|
||||||
|
grunt.loadNpmTasks('grunt-dart-sass');
|
||||||
|
grunt.loadNpmTasks('grunt-exec');
|
||||||
|
|
||||||
|
grunt.initConfig({
|
||||||
|
pkg: grunt.file.readJSON('package.json'),
|
||||||
|
clean: {
|
||||||
|
build_css: [
|
||||||
|
// Clean the build folder - downloaded files
|
||||||
|
'build/css',
|
||||||
|
// Clean the resources folder - compiled SASS files
|
||||||
|
'resources/css'
|
||||||
|
],
|
||||||
|
build_js: [
|
||||||
|
'build/js',
|
||||||
|
],
|
||||||
|
output: [
|
||||||
|
'public/css',
|
||||||
|
'public/js'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
concat: {
|
||||||
|
// Concatenate all third-party stylesheets into blue-twilight.css
|
||||||
|
bt_css: {
|
||||||
|
src: [
|
||||||
|
'build/css/*.css',
|
||||||
|
'resources/css/*.css'
|
||||||
|
],
|
||||||
|
dest: 'public/css/blue-twilight.css'
|
||||||
|
},
|
||||||
|
// Concatenate all third-party and application Javascripts into blue-twilight.js
|
||||||
|
bt_js: {
|
||||||
|
src: [
|
||||||
|
'build/js/*.js',
|
||||||
|
'resources/js/*.js',
|
||||||
|
],
|
||||||
|
dest: 'public/js/blue-twilight.js'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
curl: {
|
||||||
|
/* These elements are sorted according to the order we desire them to be loaded when concatenated */
|
||||||
|
/** CSS **/
|
||||||
|
bootstrap_css: {
|
||||||
|
src: download_url + 'bootstrap/4.4.1/css/bootstrap.css',
|
||||||
|
dest: 'build/css/01-bootstrap.css'
|
||||||
|
},
|
||||||
|
|
||||||
|
/** JS **/
|
||||||
|
jquery: {
|
||||||
|
src: download_url + 'jquery/3.3.1/jquery.js',
|
||||||
|
dest: 'build/js/01-jquery.js'
|
||||||
|
},
|
||||||
|
bootstrap_js: {
|
||||||
|
src: download_url + 'bootstrap/4.4.1/js/bootstrap.bundle.js',
|
||||||
|
dest: 'build/js/03-bootstrap.js'
|
||||||
|
},
|
||||||
|
font_awesome_js: {
|
||||||
|
src: download_url + 'font-awesome/5.4.1/js/all.js',
|
||||||
|
dest: 'build/js/04-fontawesome.js'
|
||||||
|
},
|
||||||
|
vuejs: {
|
||||||
|
src: download_url + 'vuejs/2.6.10/vue.js',
|
||||||
|
dest: 'build/js/06-vuejs.js'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'dart-sass': {
|
||||||
|
bt_sass: {
|
||||||
|
options: {
|
||||||
|
sourceMap: false
|
||||||
|
},
|
||||||
|
files: [{
|
||||||
|
expand: true,
|
||||||
|
cwd: 'resources/sass/',
|
||||||
|
src: ['*.scss'],
|
||||||
|
dest: 'resources/css/',
|
||||||
|
ext: '.css'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register our tasks
|
||||||
|
grunt.registerTask('build-css-debug', [
|
||||||
|
// Download third-party stylesheets
|
||||||
|
'curl:bootstrap_css',
|
||||||
|
// SASS our own CSS
|
||||||
|
'dart-sass:bt_sass',
|
||||||
|
// Create our blue-twilight.css
|
||||||
|
'concat:bt_css'
|
||||||
|
]);
|
||||||
|
|
||||||
|
grunt.registerTask('build-js-debug', [
|
||||||
|
// Download third-party Javascripts
|
||||||
|
'curl:jquery',
|
||||||
|
'curl:bootstrap_js',
|
||||||
|
'curl:font_awesome_js',
|
||||||
|
'curl:vuejs',
|
||||||
|
// Create our blue-twilight.js
|
||||||
|
'concat:bt_js'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Shortcut tasks for the ones above
|
||||||
|
grunt.registerTask('clean-all', ['clean:build_css', 'clean:build_js', 'clean:output']);
|
||||||
|
grunt.registerTask('build-debug', ['clean-all', 'build-css-debug', 'build-js-debug']);
|
||||||
|
};
|
23
app/ExternalService.php
Normal file
23
app/ExternalService.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ExternalService extends Model
|
||||||
|
{
|
||||||
|
public const DROPBOX = 'Dropbox';
|
||||||
|
public const FACEBOOK = 'Facebook';
|
||||||
|
public const GOOGLE = 'Google';
|
||||||
|
public const TWITTER = 'Twitter';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the details for the given service type.
|
||||||
|
* @param $serviceType
|
||||||
|
* @return ExternalService
|
||||||
|
*/
|
||||||
|
public static function getForService($serviceType)
|
||||||
|
{
|
||||||
|
return ExternalService::where('service_type', $serviceType)->first();
|
||||||
|
}
|
||||||
|
}
|
@ -265,7 +265,7 @@ class PhotoController extends Controller
|
|||||||
return response()->json($result);
|
return response()->json($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function rotate($photoId, $angle)
|
public function rotate(Request $request, $photoId, $angle)
|
||||||
{
|
{
|
||||||
$this->authorizeAccessToAdminPanel();
|
$this->authorizeAccessToAdminPanel();
|
||||||
|
|
||||||
@ -273,7 +273,7 @@ class PhotoController extends Controller
|
|||||||
|
|
||||||
if ($angle != 90 && $angle != 180 && $angle != 270)
|
if ($angle != 90 && $angle != 180 && $angle != 270)
|
||||||
{
|
{
|
||||||
App::aport(400);
|
App::abort(400);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,6 +282,8 @@ class PhotoController extends Controller
|
|||||||
|
|
||||||
// Log an activity record for the user's feed
|
// Log an activity record for the user's feed
|
||||||
$this->createActivityRecord($photo, 'photo.edited');
|
$this->createActivityRecord($photo, 'photo.edited');
|
||||||
|
|
||||||
|
return $photo->thumbnailUrl($request->get('t', 'admin-preview'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
10
app/Http/Controllers/Admin/ServiceController.php
Normal file
10
app/Http/Controllers/Admin/ServiceController.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
|
||||||
|
class ServiceController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -11,9 +11,8 @@ use App\Policies\AlbumPolicy;
|
|||||||
use App\Policies\PhotoPolicy;
|
use App\Policies\PhotoPolicy;
|
||||||
use App\Policies\UserPolicy;
|
use App\Policies\UserPolicy;
|
||||||
use App\User;
|
use App\User;
|
||||||
use function GuzzleHttp\Psr7\mimetype_from_extension;
|
|
||||||
use Illuminate\Support\Facades\Gate;
|
|
||||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
|
||||||
class AuthServiceProvider extends ServiceProvider
|
class AuthServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
@ -66,6 +65,10 @@ class AuthServiceProvider extends ServiceProvider
|
|||||||
{
|
{
|
||||||
return $this->userHasAdminPermission($user, 'manage-labels');
|
return $this->userHasAdminPermission($user, 'manage-labels');
|
||||||
});
|
});
|
||||||
|
Gate::define('admin:manage-services', function ($user)
|
||||||
|
{
|
||||||
|
return $this->userHasAdminPermission($user, 'manage-services');
|
||||||
|
});
|
||||||
Gate::define('admin:manage-storage', function ($user)
|
Gate::define('admin:manage-storage', function ($user)
|
||||||
{
|
{
|
||||||
return $this->userHasAdminPermission($user, 'manage-storage');
|
return $this->userHasAdminPermission($user, 'manage-storage');
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"classmap": [
|
"classmap": [
|
||||||
|
"database/data_migrations",
|
||||||
"database/seeds",
|
"database/seeds",
|
||||||
"database/factories"
|
"database/factories"
|
||||||
],
|
],
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
// Version number of Blue Twilight
|
// Version number of Blue Twilight
|
||||||
'version' => '2.2.0-beta.1',
|
'version' => '2.2.0-beta.2',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateExternalServicesTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('external_services', function (Blueprint $table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->string('service_type', 50);
|
||||||
|
$table->text('app_id')->nullable();
|
||||||
|
$table->text('app_secret')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('external_services');
|
||||||
|
}
|
||||||
|
}
|
34
package.json
34
package.json
@ -1,25 +1,15 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"name": "blue-twilight",
|
||||||
"scripts": {
|
"version": "2.2.0-beta.2",
|
||||||
"prod": "gulp --production",
|
|
||||||
"dev": "gulp watch"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bootstrap-sass": "^3.3.7",
|
"grunt": "^1.0.4",
|
||||||
"gulp": "^3.9.1",
|
"grunt-contrib-clean": "^2.0.0",
|
||||||
"gulp-concat": "^2.6.1",
|
"grunt-contrib-concat": "^1.0.1",
|
||||||
"gulp-copy": "^1.0.0",
|
"grunt-contrib-cssmin": "^3.0.0",
|
||||||
"gulp-help": "^1.6.1",
|
"grunt-contrib-uglify": "^4.0.1",
|
||||||
"gulp-rename": "^1.2.2",
|
"grunt-curl": "^2.5.1",
|
||||||
"gulp-uglify-es": "^0.1.3",
|
"grunt-dart-sass": "^1.1.3",
|
||||||
"uglify-js": "^3.0.28",
|
"grunt-exec": "^3.0.0",
|
||||||
"gulp-uglifycss": "^1.0.8",
|
"node-sass": "^4.13.0"
|
||||||
"jquery": "^3.1.0",
|
|
||||||
"laravel-elixir": "^6.0.0-14",
|
|
||||||
"laravel-elixir-vue-2": "^0.3.0",
|
|
||||||
"laravel-elixir-webpack-official": "^1.0.2",
|
|
||||||
"lodash": "^4.16.2",
|
|
||||||
"vue": "^2.0.1",
|
|
||||||
"vue-resource": "^1.0.3"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
1912
resources/assets/bootstrap/css/bootstrap-grid.css
vendored
1912
resources/assets/bootstrap/css/bootstrap-grid.css
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
331
resources/assets/bootstrap/css/bootstrap-reboot.css
vendored
331
resources/assets/bootstrap/css/bootstrap-reboot.css
vendored
@ -1,331 +0,0 @@
|
|||||||
/*!
|
|
||||||
* Bootstrap Reboot v4.1.2 (https://getbootstrap.com/)
|
|
||||||
* Copyright 2011-2018 The Bootstrap Authors
|
|
||||||
* Copyright 2011-2018 Twitter, Inc.
|
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
||||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
|
||||||
*/
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-family: sans-serif;
|
|
||||||
line-height: 1.15;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
-ms-text-size-adjust: 100%;
|
|
||||||
-ms-overflow-style: scrollbar;
|
|
||||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@-ms-viewport {
|
|
||||||
width: device-width;
|
|
||||||
}
|
|
||||||
|
|
||||||
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: #212529;
|
|
||||||
text-align: left;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
[tabindex="-1"]:focus {
|
|
||||||
outline: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
box-sizing: content-box;
|
|
||||||
height: 0;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
abbr[title],
|
|
||||||
abbr[data-original-title] {
|
|
||||||
text-decoration: underline;
|
|
||||||
-webkit-text-decoration: underline dotted;
|
|
||||||
text-decoration: underline dotted;
|
|
||||||
cursor: help;
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
address {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
font-style: normal;
|
|
||||||
line-height: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol,
|
|
||||||
ul,
|
|
||||||
dl {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol ol,
|
|
||||||
ul ul,
|
|
||||||
ol ul,
|
|
||||||
ul ol {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
dt {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd {
|
|
||||||
margin-bottom: .5rem;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
dfn {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
b,
|
|
||||||
strong {
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
small {
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub,
|
|
||||||
sup {
|
|
||||||
position: relative;
|
|
||||||
font-size: 75%;
|
|
||||||
line-height: 0;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub {
|
|
||||||
bottom: -.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
sup {
|
|
||||||
top: -.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #007bff;
|
|
||||||
text-decoration: none;
|
|
||||||
background-color: transparent;
|
|
||||||
-webkit-text-decoration-skip: objects;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #0056b3;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:not([href]):not([tabindex]) {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:not([href]):not([tabindex]):focus {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre,
|
|
||||||
code,
|
|
||||||
kbd,
|
|
||||||
samp {
|
|
||||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
overflow: auto;
|
|
||||||
-ms-overflow-style: scrollbar;
|
|
||||||
}
|
|
||||||
|
|
||||||
figure {
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
vertical-align: middle;
|
|
||||||
border-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg:not(:root) {
|
|
||||||
overflow: hidden;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
caption {
|
|
||||||
padding-top: 0.75rem;
|
|
||||||
padding-bottom: 0.75rem;
|
|
||||||
color: #6c757d;
|
|
||||||
text-align: left;
|
|
||||||
caption-side: bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
text-align: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:focus {
|
|
||||||
outline: 1px dotted;
|
|
||||||
outline: 5px auto -webkit-focus-ring-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
button,
|
|
||||||
select,
|
|
||||||
optgroup,
|
|
||||||
textarea {
|
|
||||||
margin: 0;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
line-height: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
button,
|
|
||||||
input {
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
button,
|
|
||||||
select {
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
button,
|
|
||||||
html [type="button"],
|
|
||||||
[type="reset"],
|
|
||||||
[type="submit"] {
|
|
||||||
-webkit-appearance: button;
|
|
||||||
}
|
|
||||||
|
|
||||||
button::-moz-focus-inner,
|
|
||||||
[type="button"]::-moz-focus-inner,
|
|
||||||
[type="reset"]::-moz-focus-inner,
|
|
||||||
[type="submit"]::-moz-focus-inner {
|
|
||||||
padding: 0;
|
|
||||||
border-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="radio"],
|
|
||||||
input[type="checkbox"] {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="date"],
|
|
||||||
input[type="time"],
|
|
||||||
input[type="datetime-local"],
|
|
||||||
input[type="month"] {
|
|
||||||
-webkit-appearance: listbox;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
overflow: auto;
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
min-width: 0;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
legend {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
padding: 0;
|
|
||||||
margin-bottom: .5rem;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
line-height: inherit;
|
|
||||||
color: inherit;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
progress {
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
[type="number"]::-webkit-inner-spin-button,
|
|
||||||
[type="number"]::-webkit-outer-spin-button {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
[type="search"] {
|
|
||||||
outline-offset: -2px;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[type="search"]::-webkit-search-cancel-button,
|
|
||||||
[type="search"]::-webkit-search-decoration {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-file-upload-button {
|
|
||||||
font: inherit;
|
|
||||||
-webkit-appearance: button;
|
|
||||||
}
|
|
||||||
|
|
||||||
output {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
summary {
|
|
||||||
display: list-item;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
template {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[hidden] {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
|
File diff suppressed because one or more lines are too long
@ -1,8 +0,0 @@
|
|||||||
/*!
|
|
||||||
* Bootstrap Reboot v4.1.2 (https://getbootstrap.com/)
|
|
||||||
* Copyright 2011-2018 The Bootstrap Authors
|
|
||||||
* Copyright 2011-2018 Twitter, Inc.
|
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
||||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
|
||||||
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
|
|
||||||
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
|
|
File diff suppressed because one or more lines are too long
9023
resources/assets/bootstrap/css/bootstrap.css
vendored
9023
resources/assets/bootstrap/css/bootstrap.css
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6461
resources/assets/bootstrap/js/bootstrap.bundle.js
vendored
6461
resources/assets/bootstrap/js/bootstrap.bundle.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3944
resources/assets/bootstrap/js/bootstrap.js
vendored
3944
resources/assets/bootstrap/js/bootstrap.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -411,10 +411,11 @@ function EditPhotosViewModel(album_id, language, urls) {
|
|||||||
url = url.replace(/\/1$/, '/' + angle);
|
url = url.replace(/\/1$/, '/' + angle);
|
||||||
|
|
||||||
$('.loading', parent).show();
|
$('.loading', parent).show();
|
||||||
$.post(url, function () {
|
$.post(url, function (response) {
|
||||||
var image = $('img.photo-thumbnail', parent);
|
var image = $('img.photo-thumbnail', parent);
|
||||||
var originalUrl = image.data('original-src');
|
|
||||||
image.attr('src', originalUrl + "&_=" + new Date().getTime());
|
// response from server is the URL to the modified image
|
||||||
|
image.attr('src', response);
|
||||||
|
|
||||||
$('.loading', parent).hide();
|
$('.loading', parent).hide();
|
||||||
});
|
});
|
||||||
|
10244
resources/js/001-jquery.js
Normal file
10244
resources/js/001-jquery.js
Normal file
File diff suppressed because it is too large
Load Diff
985
resources/js/002-bootbox.js
Normal file
985
resources/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'>" +
|
||||||
|
"<h5 class='modal-title'></h5>" +
|
||||||
|
"</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").append(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;
|
||||||
|
}));
|
9175
resources/js/003-vue.js
Normal file
9175
resources/js/003-vue.js
Normal file
File diff suppressed because it is too large
Load Diff
1792
resources/js/004-tether.js
Normal file
1792
resources/js/004-tether.js
Normal file
File diff suppressed because it is too large
Load Diff
126
resources/js/admin.js
Normal file
126
resources/js/admin.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/**
|
||||||
|
* This model is used by admin/about.blade.php, to perform a version check against Github.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function AboutViewModel(urls) {
|
||||||
|
this.el = '#about-app';
|
||||||
|
|
||||||
|
this.data = {
|
||||||
|
can_upgrade: false,
|
||||||
|
is_loading: true,
|
||||||
|
version_body: '',
|
||||||
|
version_date: '',
|
||||||
|
version_name: '',
|
||||||
|
version_url: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
this.computed = {
|
||||||
|
};
|
||||||
|
|
||||||
|
this.methods = {
|
||||||
|
init: function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
$.ajax(
|
||||||
|
urls.latest_release_url,
|
||||||
|
{
|
||||||
|
complete: function() {
|
||||||
|
self.is_loading = false;
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
error: function (xhr, textStatus, errorThrown) {
|
||||||
|
},
|
||||||
|
method: 'GET',
|
||||||
|
success: function (data) {
|
||||||
|
self.version_body = data.body;
|
||||||
|
self.version_date = data.publish_date;
|
||||||
|
self.version_name = data.name;
|
||||||
|
self.version_url = data.url;
|
||||||
|
|
||||||
|
// Set this last so any watchers on this property execute after all version data has been set
|
||||||
|
self.can_upgrade = data.can_upgrade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This model is used by admin/create_album.blade.php.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function CreateAlbumViewModel() {
|
||||||
|
this.el = '#create-album-app';
|
||||||
|
|
||||||
|
this.data = {
|
||||||
|
is_inherit_permissions: true,
|
||||||
|
is_private: false,
|
||||||
|
parent_id: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
this.computed = {
|
||||||
|
isParentAlbum: function() {
|
||||||
|
return this.parent_id == '';
|
||||||
|
},
|
||||||
|
isPrivateDisabled: function() {
|
||||||
|
return !this.isParentAlbum && this.is_inherit_permissions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This model is used by admin/edit_album.blade.php.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function EditAlbumViewModel() {
|
||||||
|
this.el = '#edit-album-app';
|
||||||
|
|
||||||
|
this.data = {
|
||||||
|
parent_id: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
this.computed = {
|
||||||
|
isParentAlbum: function() {
|
||||||
|
return this.parent_id == '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This model is used by admin/settings.blade.php.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function SettingsViewModel(urls, lang) {
|
||||||
|
this.el = '#settings-app';
|
||||||
|
|
||||||
|
this.data = {
|
||||||
|
is_rebuilding_permissions_cache: false
|
||||||
|
};
|
||||||
|
|
||||||
|
this.methods = {
|
||||||
|
rebuildPermissionsCache: function (e) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
$.ajax(
|
||||||
|
urls.rebuild_permissions_cache,
|
||||||
|
{
|
||||||
|
complete: function() {
|
||||||
|
self.is_rebuilding_permissions_cache = false;
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
error: function (xhr, textStatus, errorThrown) {
|
||||||
|
alert(lang.permissions_cache_rebuild_failed);
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
success: function (data) {
|
||||||
|
alert(lang.permissions_cache_rebuild_succeeded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
717
resources/js/albums.js
Normal file
717
resources/js/albums.js
Normal file
@ -0,0 +1,717 @@
|
|||||||
|
/**
|
||||||
|
* This model is used by admin/analyse_album.blade.php, to analyse all images.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function AnalyseAlbumViewModel() {
|
||||||
|
this.el = '#analyse-album';
|
||||||
|
|
||||||
|
this.data = {
|
||||||
|
imagesFailed: [],
|
||||||
|
imagesToAnalyse: [],
|
||||||
|
imagesInProgress: [],
|
||||||
|
imagesRecentlyCompleted: [],
|
||||||
|
numberSuccessful: 0,
|
||||||
|
numberFailed: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
this.computed = {
|
||||||
|
failedPercentage: function () {
|
||||||
|
var result = 0;
|
||||||
|
|
||||||
|
if (this.numberTotal > 0)
|
||||||
|
{
|
||||||
|
result = (this.numberFailed / this.numberTotal) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toFixed(2) + '%';
|
||||||
|
},
|
||||||
|
isCompleted: function () {
|
||||||
|
return this.numberTotal > 0 && (this.numberSuccessful + this.numberFailed >= this.numberTotal);
|
||||||
|
},
|
||||||
|
latestCompletedImages: function() {
|
||||||
|
var startIndex = this.imagesRecentlyCompleted.length - 3 < 0
|
||||||
|
? 0
|
||||||
|
: this.imagesRecentlyCompleted.length - 3;
|
||||||
|
var endIndex = startIndex + 3;
|
||||||
|
|
||||||
|
return this.imagesRecentlyCompleted.slice(startIndex, endIndex);
|
||||||
|
},
|
||||||
|
numberTotal: function () {
|
||||||
|
return this.imagesToAnalyse.length;
|
||||||
|
},
|
||||||
|
successfulPercentage: function () {
|
||||||
|
var result = 0;
|
||||||
|
|
||||||
|
if (this.numberTotal > 0)
|
||||||
|
{
|
||||||
|
result = (this.numberSuccessful / this.numberTotal) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toFixed(2) + '%';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.methods = {
|
||||||
|
// This method is called when an image is added to the array, automatically issue it for analysis
|
||||||
|
// item is an instance of AnalyseImageViewModel
|
||||||
|
analyseImage: function (item) {
|
||||||
|
var self = this;
|
||||||
|
this.imagesToAnalyse.push(item);
|
||||||
|
|
||||||
|
$.ajax(
|
||||||
|
item.url,
|
||||||
|
{
|
||||||
|
beforeSend: function() {
|
||||||
|
self.imagesInProgress.push(item);
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
error: function (xhr, textStatus, errorThrown) {
|
||||||
|
self.numberFailed++;
|
||||||
|
self.imagesFailed.push({
|
||||||
|
'name': item.name,
|
||||||
|
'reason': textStatus
|
||||||
|
});
|
||||||
|
item.isSuccessful = false;
|
||||||
|
item.isPending = false;
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
success: function (data) {
|
||||||
|
if (data.is_successful) {
|
||||||
|
self.numberSuccessful++;
|
||||||
|
item.isSuccessful = true;
|
||||||
|
item.isPending = false;
|
||||||
|
|
||||||
|
// Push into our "recently completed" array
|
||||||
|
self.imagesRecentlyCompleted.push(item);
|
||||||
|
var indexToRemove = self.imagesInProgress.indexOf(item);
|
||||||
|
if (indexToRemove > -1)
|
||||||
|
{
|
||||||
|
self.imagesInProgress.splice(indexToRemove, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove it again after a few seconds
|
||||||
|
/*window.setTimeout(function() {
|
||||||
|
var indexToRemove = self.imagesRecentlyCompleted.indexOf(item);
|
||||||
|
if (indexToRemove > -1) {
|
||||||
|
self.imagesRecentlyCompleted.splice(indexToRemove, 1);
|
||||||
|
}
|
||||||
|
}, 2000);*/
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self.numberFailed++;
|
||||||
|
self.imagesFailed.push({
|
||||||
|
'name': item.name,
|
||||||
|
'reason': data.message
|
||||||
|
});
|
||||||
|
item.isSuccessful = false;
|
||||||
|
item.isPending = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This model is used by admin/analyse_album.blade.php, as a sub-model of AnalyseAlbumViewModel.
|
||||||
|
* @param image_info Array of information about the image
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function AnalyseImageViewModel(image_info) {
|
||||||
|
this.isPending = true;
|
||||||
|
this.isSuccessful = false;
|
||||||
|
this.name = image_info.name;
|
||||||
|
this.photoID = image_info.photo_id;
|
||||||
|
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 self = this;
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
replacePhoto: function (e) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.selectPhotoSingle(e.target);
|
||||||
|
var parent = $(e.target).closest('.photo');
|
||||||
|
|
||||||
|
bootbox.dialog({
|
||||||
|
message: $('<p/>').html(language.replace_image_message).prop('outerHTML') +
|
||||||
|
$('#replace-image-form').clone().removeAttr('style').attr('id', 'replace-image-form-visible').prop('outerHTML'),
|
||||||
|
title: language.replace_image_title,
|
||||||
|
buttons: {
|
||||||
|
cancel: {
|
||||||
|
label: language.action_cancel,
|
||||||
|
className: 'btn-secondary'
|
||||||
|
},
|
||||||
|
confirm: {
|
||||||
|
label: language.action_continue,
|
||||||
|
className: 'btn-success',
|
||||||
|
callback: function () {
|
||||||
|
$('input[name="photo_id"]', '#replace-image-form-visible').val(self.photoIDs[0]);
|
||||||
|
$('#replace-image-form-visible').submit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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 (response) {
|
||||||
|
var image = $('img.photo-thumbnail', parent);
|
||||||
|
|
||||||
|
// response from server is the URL to the modified image
|
||||||
|
image.attr('src', response);
|
||||||
|
|
||||||
|
$('.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);
|
||||||
|
},
|
||||||
|
switchToUploadTab: function (e) {
|
||||||
|
$('.nav-tabs a[href="#upload-tab"]').tab('show');
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function SlideShowViewModel(required_timeout_ms) {
|
||||||
|
this.el = '#slideshow-container';
|
||||||
|
|
||||||
|
this.data = {
|
||||||
|
current: null,
|
||||||
|
currentIndex: 0,
|
||||||
|
images: [],
|
||||||
|
interval: null,
|
||||||
|
isPaused: false,
|
||||||
|
isRunning: false
|
||||||
|
};
|
||||||
|
|
||||||
|
this.methods = {
|
||||||
|
changeCurrentImage: function(photo_id)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < this.images.length; i++)
|
||||||
|
{
|
||||||
|
var this_image = this.images[i];
|
||||||
|
if (this_image.id === photo_id)
|
||||||
|
{
|
||||||
|
this.current = this_image;
|
||||||
|
this.currentIndex = i;
|
||||||
|
window.clearInterval(this.interval);
|
||||||
|
this.interval = window.setInterval(this.rotateNextImage, required_timeout_ms);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
continueSlideshow: function() {
|
||||||
|
this.isPaused = false;
|
||||||
|
this.interval = window.setInterval(this.rotateNextImage, required_timeout_ms);
|
||||||
|
},
|
||||||
|
pauseSlideshow: function(e) {
|
||||||
|
this.isPaused = true;
|
||||||
|
window.clearInterval(this.interval);
|
||||||
|
},
|
||||||
|
rotateNextImage: function() {
|
||||||
|
var next_index = this.currentIndex + 1;
|
||||||
|
if (next_index >= this.images.length)
|
||||||
|
{
|
||||||
|
next_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.current = this.images[next_index];
|
||||||
|
this.currentIndex = next_index;
|
||||||
|
},
|
||||||
|
startSlideshow: function() {
|
||||||
|
if (this.images.length <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.interval = window.setInterval(this.rotateNextImage, required_timeout_ms);
|
||||||
|
|
||||||
|
this.current = this.images[0];
|
||||||
|
this.isRunning = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 queue_token Unique token of the upload queue to save the photos to
|
||||||
|
* @param language Array containing language strings
|
||||||
|
* @param urls Array containing URLs
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function UploadPhotosViewModel(album_id, queue_token, language, urls) {
|
||||||
|
this.el = '#upload-tab';
|
||||||
|
|
||||||
|
this.data = {
|
||||||
|
currentStatus: '',
|
||||||
|
imagesFailed: 0,
|
||||||
|
imagesTotal: 0,
|
||||||
|
imagesUploaded: 0,
|
||||||
|
isBulkUploadInProgress: false,
|
||||||
|
isUploadInProgress: false,
|
||||||
|
statusMessages: []
|
||||||
|
};
|
||||||
|
|
||||||
|
this.computed = {
|
||||||
|
failedPercentage: function () {
|
||||||
|
var result = 0;
|
||||||
|
|
||||||
|
if (this.imagesTotal > 0)
|
||||||
|
{
|
||||||
|
result = (this.imagesFailed / this.imagesTotal) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toFixed(2) + '%';
|
||||||
|
},
|
||||||
|
successfulPercentage: function () {
|
||||||
|
var result = 0;
|
||||||
|
|
||||||
|
if (this.imagesTotal > 0)
|
||||||
|
{
|
||||||
|
result = (this.imagesUploaded / this.imagesTotal) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toFixed(2) + '%';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.methods = {
|
||||||
|
// This method is called when an image is uploaded - regardless if it fails or not
|
||||||
|
onUploadCompleted: function () {
|
||||||
|
this.currentStatus = language.upload_status
|
||||||
|
.replace(':current', (this.imagesUploaded + this.imagesFailed))
|
||||||
|
.replace(':total', this.imagesTotal);
|
||||||
|
|
||||||
|
if ((this.imagesFailed + this.imagesUploaded) >= this.imagesTotal) {
|
||||||
|
this.isUploadInProgress = false;
|
||||||
|
|
||||||
|
if (this.imagesFailed === 0 && this.imagesUploaded > 0) {
|
||||||
|
window.location = urls.analyse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// This method is called when an uploaded image fails
|
||||||
|
onUploadFailed: function (data, file_name) {
|
||||||
|
this.imagesFailed++;
|
||||||
|
this.statusMessages.push({
|
||||||
|
'message_class': 'text-danger',
|
||||||
|
'message_text': language.image_failed.replace(':file_name', file_name)
|
||||||
|
});
|
||||||
|
this.onUploadCompleted();
|
||||||
|
},
|
||||||
|
// This method is called when an uploaded image succeeds
|
||||||
|
onUploadSuccessful: function (data, file_name) {
|
||||||
|
if (data.is_successful) {
|
||||||
|
this.imagesUploaded++;
|
||||||
|
// Don't add to statusMessages() array so user only sees errors
|
||||||
|
/*self.statusMessages.push({
|
||||||
|
'message_class': 'text-success',
|
||||||
|
'message_text': language.image_uploaded.replace(':file_name', file_name)
|
||||||
|
});*/
|
||||||
|
this.onUploadCompleted();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.onUploadFailed(data, file_name);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
uploadFile: function uploadImageFile(formObject, imageFile) {
|
||||||
|
var self = this;
|
||||||
|
var formData = new FormData();
|
||||||
|
formData.append('album_id', album_id);
|
||||||
|
formData.append('queue_token', queue_token);
|
||||||
|
formData.append('photo[]', imageFile, imageFile.name);
|
||||||
|
|
||||||
|
$.ajax(
|
||||||
|
{
|
||||||
|
contentType: false,
|
||||||
|
data: formData,
|
||||||
|
error: function (data) {
|
||||||
|
self.onUploadFailed(data, imageFile.name);
|
||||||
|
},
|
||||||
|
method: $(formObject).attr('method'),
|
||||||
|
processData: false,
|
||||||
|
success: function (data) {
|
||||||
|
self.onUploadSuccessful(data, imageFile.name);
|
||||||
|
},
|
||||||
|
url: $(formObject).attr('action')
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.isUploadInProgress = true;
|
||||||
|
|
||||||
|
this.currentStatus = language.upload_status
|
||||||
|
.replace(':current', 0)
|
||||||
|
.replace(':total', this.imagesTotal);
|
||||||
|
},
|
||||||
|
uploadBulkFiles: function(event) {
|
||||||
|
this.isBulkUploadInProgress = true;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
uploadIndividualFiles: function(event) {
|
||||||
|
var fileSelect = $('input[type=file]', event.target);
|
||||||
|
|
||||||
|
// Get the selected files
|
||||||
|
var files = fileSelect[0].files;
|
||||||
|
|
||||||
|
if (files.length === 0)
|
||||||
|
{
|
||||||
|
alert(language.no_file_selected);
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset statistics
|
||||||
|
this.currentStatus = '';
|
||||||
|
this.statusMessages = [];
|
||||||
|
this.imagesUploaded = 0;
|
||||||
|
this.imagesFailed = 0;
|
||||||
|
this.imagesTotal = files.length;
|
||||||
|
this.isUploadInProgress = true;
|
||||||
|
|
||||||
|
// Loop through each of the selected files and upload them individually
|
||||||
|
for (var i = 0; i < files.length; i++)
|
||||||
|
{
|
||||||
|
var file = files[i];
|
||||||
|
|
||||||
|
// We're only interested in image files
|
||||||
|
if (!file.type.match('image.*'))
|
||||||
|
{
|
||||||
|
alert(language.not_an_image_file.replace(':file_name', file.name));
|
||||||
|
this.onUploadFailed(null, file.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload the file
|
||||||
|
this.uploadFile(event.target, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent standard form upload
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
18494
resources/js/chart.bundle.js
Normal file
18494
resources/js/chart.bundle.js
Normal file
File diff suppressed because it is too large
Load Diff
235
resources/js/gallery.js
Normal file
235
resources/js/gallery.js
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
/**
|
||||||
|
* This model is used by gallery/explore_users.blade.php, to handle following/unfollowing users profiles.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function ExploreUsersViewModel(urls)
|
||||||
|
{
|
||||||
|
this.el = '#explore-users-app';
|
||||||
|
|
||||||
|
this.data = {
|
||||||
|
};
|
||||||
|
|
||||||
|
this.computed = {
|
||||||
|
};
|
||||||
|
|
||||||
|
this.methods = {
|
||||||
|
followUser: function(e)
|
||||||
|
{
|
||||||
|
var userIDToFollow = $(e.target).data('user-id');
|
||||||
|
var urlToPost = urls.follow_user_url.replace('/-1/', '/' + userIDToFollow + '/');
|
||||||
|
|
||||||
|
$.post(urlToPost, '', function(data)
|
||||||
|
{
|
||||||
|
window.location.reload(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
unFollowUser: function(e)
|
||||||
|
{
|
||||||
|
var userIDToUnfollow = $(e.target).data('user-id');
|
||||||
|
var urlToPost = urls.unfollow_user_url.replace('/-1/', '/' + userIDToUnfollow + '/');
|
||||||
|
|
||||||
|
$.post(urlToPost, '', function(data)
|
||||||
|
{
|
||||||
|
window.location.reload(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This model is used by gallery/photo.blade.php, to handle comments and individual photo actions.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function PhotoViewModel(urls) {
|
||||||
|
this.el = '#photo-app';
|
||||||
|
|
||||||
|
this.data = {
|
||||||
|
is_reply_form_loading: false,
|
||||||
|
reply_comment_id: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
this.computed = {
|
||||||
|
replyFormStyle: function()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
'display': this.is_reply_form_loading ? 'none' : 'block'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.methods = {
|
||||||
|
init: function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Load the right comment reply form
|
||||||
|
$('#comment-reply-modal').on('show.bs.modal', function (event) {
|
||||||
|
var url = urls.reply_comment_form.replace(/-1$/, self.reply_comment_id);
|
||||||
|
$.get(url, function(result)
|
||||||
|
{
|
||||||
|
$('#comment-reply-form-content').html(result);
|
||||||
|
initTinyMce('#comment-text-reply');
|
||||||
|
self.is_reply_form_loading = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#comment-reply-modal').on('hide.bs.modal', function (event) {
|
||||||
|
tinymce.remove('#comment-text-reply');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
postCommentReply: function() {
|
||||||
|
var form = $('form', '#comment-reply-form-content');
|
||||||
|
var formUrl = $(form).attr('action');
|
||||||
|
|
||||||
|
// Seems like the TinyMCE editor in the BS modal does not persist back to the textarea correctly - so do
|
||||||
|
// this manually (bit of a hack!)
|
||||||
|
$('#comment-text-reply', form).html(tinymce.get('comment-text-reply').getContent());
|
||||||
|
|
||||||
|
var formData = form.serialize();
|
||||||
|
|
||||||
|
$.post(formUrl, formData, function(result)
|
||||||
|
{
|
||||||
|
if (result.redirect_url)
|
||||||
|
{
|
||||||
|
window.location = result.redirect_url;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tinymce.remove('#comment-text-reply');
|
||||||
|
$('#comment-reply-form-content').html(result);
|
||||||
|
initTinyMce('#comment-text-reply');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
replyToComment: function(e) {
|
||||||
|
var replyButton = $(e.target).closest('.photo-comment');
|
||||||
|
this.reply_comment_id = replyButton.data('comment-id');
|
||||||
|
|
||||||
|
this.is_reply_form_loading = true;
|
||||||
|
$('#comment-reply-modal').modal('show');
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This model is used by gallery/user_profile.blade.php, to handle a user's profile.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function UserViewModel(urls)
|
||||||
|
{
|
||||||
|
this.el = '#user-app';
|
||||||
|
|
||||||
|
this.data = {
|
||||||
|
feed_items: [],
|
||||||
|
is_loading: true,
|
||||||
|
selected_view: 'profile',
|
||||||
|
user_id: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
this.computed = {
|
||||||
|
isFeed: function() {
|
||||||
|
return this.selected_view === 'feed';
|
||||||
|
},
|
||||||
|
isProfile: function() {
|
||||||
|
return this.selected_view === 'profile';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.methods = {
|
||||||
|
followUser: function(e)
|
||||||
|
{
|
||||||
|
$.post(urls.follow_user_url, '', function(data)
|
||||||
|
{
|
||||||
|
window.location.reload(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
loadFeedItems: function(e)
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
$.get(urls.feed_url, function (data)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < data.length; i++)
|
||||||
|
{
|
||||||
|
// User name
|
||||||
|
if (data[i].params.user_name && data[i].params.user_url)
|
||||||
|
{
|
||||||
|
data[i].description = data[i].description
|
||||||
|
.replace(
|
||||||
|
':user_name',
|
||||||
|
'<a href="' + data[i].params.user_url + '">' + data[i].params.user_name + '</a>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Photo name
|
||||||
|
if (data[i].params.photo_name && data[i].params.photo_url)
|
||||||
|
{
|
||||||
|
data[i].description = data[i].description
|
||||||
|
.replace(
|
||||||
|
':photo_name',
|
||||||
|
'<a href="' + data[i].params.photo_url + '">' + data[i].params.photo_name + '</a>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Album name
|
||||||
|
if (data[i].params.album_name && data[i].params.album_url)
|
||||||
|
{
|
||||||
|
data[i].description = data[i].description
|
||||||
|
.replace(
|
||||||
|
':album_name',
|
||||||
|
'<a href="' + data[i].params.album_url + '">' + data[i].params.album_name + '</a>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// App name
|
||||||
|
if (data[i].params.app_name && data[i].params.app_url)
|
||||||
|
{
|
||||||
|
data[i].description = data[i].description
|
||||||
|
.replace(
|
||||||
|
':app_name',
|
||||||
|
'<a href="' + data[i].params.app_url + '">' + data[i].params.app_name + '</a>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.feed_items = data;
|
||||||
|
self.is_loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
switchToFeed: function(e) {
|
||||||
|
this.selected_view = 'feed';
|
||||||
|
history.pushState('', '', urls.current_url + '?tab=feed');
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
switchToProfile: function(e) {
|
||||||
|
this.selected_view = 'profile';
|
||||||
|
history.pushState('', '', urls.current_url + '?tab=profile');
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
unFollowUser: function(e)
|
||||||
|
{
|
||||||
|
$.post(urls.unfollow_user_url, '', function(data)
|
||||||
|
{
|
||||||
|
window.location.reload(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
7
resources/js/storage_locations.js
Normal file
7
resources/js/storage_locations.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
function StorageLocationViewModel()
|
||||||
|
{
|
||||||
|
this.el = '#storage-options';
|
||||||
|
this.data = {
|
||||||
|
storage_driver: 'LocalFilesystemSource'
|
||||||
|
};
|
||||||
|
}
|
2451
resources/js/typeahead.bundle.js
Normal file
2451
resources/js/typeahead.bundle.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -30,6 +30,7 @@ return [
|
|||||||
'metadata_upgrade' => 'Update Photo Metadata',
|
'metadata_upgrade' => 'Update Photo Metadata',
|
||||||
'reject_comment' => 'Reject comment',
|
'reject_comment' => 'Reject comment',
|
||||||
'reject_comments' => 'Reject comments',
|
'reject_comments' => 'Reject comments',
|
||||||
|
'services' => 'Services',
|
||||||
'settings' => 'Settings',
|
'settings' => 'Settings',
|
||||||
'storage' => 'Storage',
|
'storage' => 'Storage',
|
||||||
'users' => 'Users'
|
'users' => 'Users'
|
||||||
|
38
resources/sass/admin.scss
Normal file
38
resources/sass/admin.scss
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
.admin-sidebar-card {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.album-expand-handle {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-label,
|
||||||
|
.meta-value {
|
||||||
|
vertical-align: middle !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-red {
|
||||||
|
color: #ff0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
[v-cloak] {
|
||||||
|
display: none;
|
||||||
|
}
|
85
resources/sass/gallery.scss
Normal file
85
resources/sass/gallery.scss
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
.activity-grid {
|
||||||
|
font-size: smaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-grid th,
|
||||||
|
.activity-grid td {
|
||||||
|
padding: 5px !important;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-grid td {
|
||||||
|
color: #fff;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-grid .has-activity {
|
||||||
|
background-color: #1e90ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-grid .invalid-date {
|
||||||
|
background-color: #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-grid .no-activity {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-grid th:first-child,
|
||||||
|
.activity-grid td:first-child {
|
||||||
|
border-left: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-grid th,
|
||||||
|
.activity-grid td {
|
||||||
|
border-right: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-grid tr:last-child td {
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-grid .border-spacer-element {
|
||||||
|
border-right-width: 0;
|
||||||
|
padding: 0 !important;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.album-slideshow-container #image-preview {
|
||||||
|
height: 600px;
|
||||||
|
max-width: 100%;
|
||||||
|
width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.album-slideshow-container #image-preview img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.album-slideshow-container .thumbnails {
|
||||||
|
overflow-x: scroll;
|
||||||
|
overflow-y: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-comment .card-subtitle {
|
||||||
|
font-size: smaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-table .icon-col {
|
||||||
|
font-size: 1.4em;
|
||||||
|
width: 20%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-table .stat-col {
|
||||||
|
font-size: 1.8em;
|
||||||
|
font-weight: bold;
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-table .text-col {
|
||||||
|
font-size: 1.2em;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 40%;
|
||||||
|
}
|
30
resources/sass/global.scss
Normal file
30
resources/sass/global.scss
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
html {
|
||||||
|
font-size: 14px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.album-photo-cards .card {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container, .container-fluid {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
border: solid 1px rgb(221, 221, 221);
|
||||||
|
border-top: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
21
resources/sass/tether-theme-basic.scss
Normal file
21
resources/sass/tether-theme-basic.scss
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/sass/tether.scss
Normal file
8
resources/sass/tether.scss
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; }
|
39
resources/sass/typeahead.scss
Normal file
39
resources/sass/typeahead.scss
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
.tt-query {
|
||||||
|
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
|
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tt-hint {
|
||||||
|
color: #999
|
||||||
|
}
|
||||||
|
|
||||||
|
.tt-menu { /* used to be tt-dropdown-menu in older versions */
|
||||||
|
width: 422px;
|
||||||
|
margin-top: 4px;
|
||||||
|
padding: 4px 0;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
||||||
|
-moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
||||||
|
box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tt-suggestion {
|
||||||
|
padding: 3px 20px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tt-suggestion.tt-cursor,.tt-suggestion:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #0097cf;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tt-suggestion p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
@ -10,7 +10,7 @@
|
|||||||
<div class="container album-container">
|
<div class="container album-container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="pull-right">
|
<div class="float-right">
|
||||||
@can('edit', $album)
|
@can('edit', $album)
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<a class="btn btn-secondary" href="{{ route('albums.show', ['album' => $album->id]) }}"><i class="fa fa-fw fa-eye"></i> @lang('gallery.manage_album_link_2')</a>
|
<a class="btn btn-secondary" href="{{ route('albums.show', ['album' => $album->id]) }}"><i class="fa fa-fw fa-eye"></i> @lang('gallery.manage_album_link_2')</a>
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
$canManageStorage = Auth::user()->can('admin:manage-storage');
|
$canManageStorage = Auth::user()->can('admin:manage-storage');
|
||||||
$canManageUsers = Auth::user()->can('admin:manage-users');
|
$canManageUsers = Auth::user()->can('admin:manage-users');
|
||||||
$canManageComments = Auth::user()->can('admin:manage-comments');
|
$canManageComments = Auth::user()->can('admin:manage-comments');
|
||||||
|
$canManageServices = Auth::user()->can('admin:manage-services');
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
@if ($canConfigure || $canManageAlbums || $canManageGroups || $canManageStorage || $canManageUsers)
|
@if ($canConfigure || $canManageAlbums || $canManageGroups || $canManageStorage || $canManageUsers)
|
||||||
@ -30,6 +31,9 @@
|
|||||||
@if ($canManageStorage)
|
@if ($canManageStorage)
|
||||||
<a class="btn btn-link" href="{{ route('storage.index') }}"><i class="fa fa-fw fa-folder"></i> @lang('navigation.breadcrumb.storage')</a>
|
<a class="btn btn-link" href="{{ route('storage.index') }}"><i class="fa fa-fw fa-folder"></i> @lang('navigation.breadcrumb.storage')</a>
|
||||||
@endif
|
@endif
|
||||||
|
@if ($canManageServices)
|
||||||
|
<a class="btn btn-link" href="{{ route('services.index') }}"><i class="fa fa-fw fa-refresh"></i> @lang('navigation.breadcrumb.services')</a>
|
||||||
|
@endif
|
||||||
@if ($canConfigure)
|
@if ($canConfigure)
|
||||||
<a class="btn btn-link" href="{{ route('admin.settings') }}"><i class="fa fa-fw fa-cog"></i> @lang('navigation.breadcrumb.settings')</a>
|
<a class="btn btn-link" href="{{ route('admin.settings') }}"><i class="fa fa-fw fa-cog"></i> @lang('navigation.breadcrumb.settings')</a>
|
||||||
@endif
|
@endif
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<nav class="navbar navbar-expand-lg navbar-dark">
|
<nav class="navbar navbar-expand-lg navbar-dark">
|
||||||
<a class="navbar-brand" href="{{ route('home') }}"><i class="fa fa-fw fa-photo"></i> {{ UserConfig::get('app_name') }}</a>
|
<a class="navbar-brand" href="{{ route('home') }}"><i class="fa fa-fw fa-image"></i> {{ UserConfig::get('app_name') }}</a>
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-content" aria-controls="navbar-content" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-content" aria-controls="navbar-content" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<i class="fa fa-fw fa-bars"></i>
|
<i class="fa fa-fw fa-bars"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -57,7 +57,7 @@
|
|||||||
|
|
||||||
@if (count($g_albums) > 0 && \App\User::currentOrAnonymous()->can('statistics.public-access'))
|
@if (count($g_albums) > 0 && \App\User::currentOrAnonymous()->can('statistics.public-access'))
|
||||||
<li class="nav-item ml-2">
|
<li class="nav-item ml-2">
|
||||||
<a class="nav-link" href="{{ route('statistics.index') }}"><i class="fa fa-bar-chart"></i> @lang('navigation.navbar.statistics')</a>
|
<a class="nav-link" href="{{ route('statistics.index') }}"><i class="fa fa-chart-line"></i> @lang('navigation.navbar.statistics')</a>
|
||||||
</li>
|
</li>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="loading"><img src="{{ asset('ripple.svg') }}" /></div>
|
<div class="loading"><img src="{{ asset('ripple.svg') }}" /></div>
|
||||||
|
|
||||||
<a href="{{ $photo->thumbnailUrl() }}" target="_blank">
|
<a href="{{ $photo->thumbnailUrl() }}" target="_blank">
|
||||||
<img class="photo-thumbnail" src="{{ $photo->thumbnailUrl('admin-preview') }}" data-original-src="{{ $photo->thumbnailUrl('admin-preview') }}" style="max-width: 100%;"/>
|
<img class="photo-thumbnail" src="{{ $photo->thumbnailUrl('admin-preview') }}" style="max-width: 100%;"/>
|
||||||
</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 --}}
|
||||||
|
@ -79,6 +79,10 @@ Route::group(['prefix' => 'admin'], function () {
|
|||||||
Route::post('comments/apply-bulk-action', 'Admin\PhotoCommentController@applyBulkAction')->name('comments.applyBulkAction');
|
Route::post('comments/apply-bulk-action', 'Admin\PhotoCommentController@applyBulkAction')->name('comments.applyBulkAction');
|
||||||
Route::post('comments/bulk-action', 'Admin\PhotoCommentController@bulkAction')->name('comments.bulkAction');
|
Route::post('comments/bulk-action', 'Admin\PhotoCommentController@bulkAction')->name('comments.bulkAction');
|
||||||
Route::resource('comments', 'Admin\PhotoCommentController');
|
Route::resource('comments', 'Admin\PhotoCommentController');
|
||||||
|
|
||||||
|
// Services management
|
||||||
|
Route::get('services/{services}/delete', 'Admin\ServiceController@delete')->name('services.delete');
|
||||||
|
Route::resource('services', 'Admin\ServiceController');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Installation
|
// Installation
|
||||||
|
Loading…
Reference in New Issue
Block a user