Improved user gallery

Improved public view
This commit is contained in:
Sergio Brighenti 2018-11-20 18:46:39 +01:00
parent f2c6936b8a
commit 7c4c02fac4
9 changed files with 281 additions and 56 deletions

View file

@ -5,6 +5,7 @@ namespace App\Controllers;
use App\Exceptions\UnauthorizedException;
use App\Web\Session;
use Intervention\Image\ImageManagerStatic as Image;
use League\Flysystem\FileExistsException;
use League\Flysystem\FileNotFoundException;
use League\Flysystem\Filesystem;
@ -89,18 +90,15 @@ class UploadController extends Controller
$filesystem = $this->getStorage();
if (stristr($request->getHeaderLine('User-Agent'), 'TelegramBot') ||
stristr($request->getHeaderLine('User-Agent'), 'facebookexternalhit/') ||
stristr($request->getHeaderLine('User-Agent'), 'Discordbot/') ||
stristr($request->getHeaderLine('User-Agent'), 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0') || // The discord service bot?
stristr($request->getHeaderLine('User-Agent'), 'Facebot')) {
if (isBot($request->getHeaderLine('User-Agent'))) {
return $this->streamMedia($request, $response, $filesystem, $media);
} else {
try {
$mime = $filesystem->getMimetype($media->storage_path);
$media->mimetype = $filesystem->getMimetype($media->storage_path);
$media->size = humanFileSize($filesystem->getSize($media->storage_path));
$type = explode('/', $mime)[0];
$type = explode('/', $media->mimetype)[0];
if ($type === 'text') {
$media->text = $filesystem->read($media->storage_path);
} else if (in_array($type, ['image', 'video'])) {
@ -119,7 +117,6 @@ class UploadController extends Controller
return $this->view->render($response, 'upload/public.twig', [
'delete_token' => isset($args['token']) ? $args['token'] : null,
'media' => $media,
'type' => $mime,
'extension' => pathinfo($media->filename, PATHINFO_EXTENSION),
]);
}
@ -319,21 +316,17 @@ class UploadController extends Controller
if ($request->getParam('width') !== null && explode('/', $mime)[0] === 'image') {
$image = imagecreatefromstring($storage->read($media->storage_path));
$scaled = imagescale($image, $request->getParam('width'), $request->getParam('height') !== null ? $request->getParam('height') : -1);
imagedestroy($image);
ob_start();
imagepng($scaled, null, 9);
$imagedata = ob_get_contents();
ob_end_clean();
imagedestroy($scaled);
$image = Image::make($storage->readStream($media->storage_path))
->resizeCanvas(
$request->getParam('width'),
$request->getParam('height'),
'center')
->encode('png');
return $response
->withHeader('Content-Type', 'image/png')
->withHeader('Content-Disposition', $disposition . ';filename="scaled-' . pathinfo($media->filename)['filename'] . '.png"')
->write($imagedata);
->write($image);
} else {
ob_end_clean();
return $response

View file

@ -13,7 +13,7 @@ if (!function_exists('humanFileSize')) {
{
for ($i = 0; ($size / 1024) > 0.9; $i++, $size /= 1024) {
}
return round($size, $precision) . ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$i];
return round($size, $precision) . ' ' . ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$i];
}
}
@ -77,7 +77,7 @@ if (!function_exists('urlFor')) {
* @param string $path
* @return string
*/
function urlFor(string $path)
function urlFor(string $path): string
{
global $app;
$baseUrl = $app->getContainer()->get('settings')['base_url'];
@ -92,7 +92,7 @@ if (!function_exists('route')) {
* @param array $args
* @return string
*/
function route(string $path, array $args = [])
function route(string $path, array $args = []): string
{
global $app;
$uri = $app->getContainer()->get('router')->pathFor($path, $args);
@ -106,8 +106,67 @@ if (!function_exists('lang')) {
* @param array $args
* @return string
*/
function lang(string $key, $args = [])
function lang(string $key, $args = []): string
{
return \App\Web\Lang::getInstance()->get($key, $args);
}
}
if (!function_exists('isBot')) {
/**
* @param string $userAgent
* @return boolean
*/
function isBot(string $userAgent)
{
$bots = [
'TelegramBot',
'facebookexternalhit/',
'Discordbot/',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0', // The discord service bot?
'Facebot',
];
foreach ($bots as $bot) {
if (stripos($userAgent, $bot) !== false) {
return true;
}
}
return false;
}
}
if (!function_exists('mime2font')) {
function mime2font($mime)
{
$classes = [
'image' => 'fa-file-image',
'audio' => 'fa-file-audio',
'video' => 'fa-file-video',
'application/pdf' => 'fa-file-pdf',
'application/msword' => 'fa-file-word',
'application/vnd.ms-word' => 'fa-file-word',
'application/vnd.oasis.opendocument.text' => 'fa-file-word',
'application/vnd.openxmlformats-officedocument.wordprocessingml' => 'fa-file-word',
'application/vnd.ms-excel' => 'fa-file-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml' => 'fa-file-excel',
'application/vnd.oasis.opendocument.spreadsheet' => 'fa-file-excel',
'application/vnd.ms-powerpoint' => 'fa-file-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml' => 'fa-file-powerpoint',
'application/vnd.oasis.opendocument.presentation' => 'fa-file-powerpoint',
'text/plain' => 'fa-file-alt',
'text/html' => 'fa-file-code',
'application/json' => 'fa-file-code',
'application/gzip' => 'fa-file-archive',
'application/zip' => 'fa-file-archive',
];
foreach ($classes as $fullMime => $class) {
if (strpos($mime, $fullMime) === 0) {
return $class;
}
}
return 'fa-file';
}
}

View file

@ -83,6 +83,7 @@ $container['view'] = function ($container) use (&$config) {
$view->getEnvironment()->addFunction(new Twig_Function('route', 'route'));
$view->getEnvironment()->addFunction(new Twig_Function('lang', 'lang'));
$view->getEnvironment()->addFunction(new Twig_Function('urlFor', 'urlFor'));
$view->getEnvironment()->addFunction(new Twig_Function('mime2font', 'mime2font'));
return $view;
};

View file

@ -1,6 +1,6 @@
{
"name": "sergix44/xbackbone",
"version": "2.2",
"version": "2.3",
"description": "A lightweight ShareX PHP backend",
"type": "project",
"require": {
@ -9,6 +9,7 @@
"slim/twig-view": "^2.4",
"league/flysystem": "^1.0.45",
"monolog/monolog": "^1.23",
"intervention/image": "^2.4",
"ext-json": "*",
"ext-gd": "*",
"ext-pdo": "*"

137
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "0273ad770ec6dd004ae0d12f9db81ed4",
"content-hash": "61a3ac382bb0960dd8e44736c0be5178",
"packages": [
{
"name": "container-interop/container-interop",
@ -37,6 +37,141 @@
"homepage": "https://github.com/container-interop/container-interop",
"time": "2017-02-14T19:40:03+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "1.4.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
"reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
"shasum": ""
},
"require": {
"php": ">=5.4.0",
"psr/http-message": "~1.0"
},
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Tobias Schultze",
"homepage": "https://github.com/Tobion"
}
],
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": [
"http",
"message",
"request",
"response",
"stream",
"uri",
"url"
],
"time": "2017-03-20T17:10:46+00:00"
},
{
"name": "intervention/image",
"version": "2.4.2",
"source": {
"type": "git",
"url": "https://github.com/Intervention/image.git",
"reference": "e82d274f786e3d4b866a59b173f42e716f0783eb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Intervention/image/zipball/e82d274f786e3d4b866a59b173f42e716f0783eb",
"reference": "e82d274f786e3d4b866a59b173f42e716f0783eb",
"shasum": ""
},
"require": {
"ext-fileinfo": "*",
"guzzlehttp/psr7": "~1.1",
"php": ">=5.4.0"
},
"require-dev": {
"mockery/mockery": "~0.9.2",
"phpunit/phpunit": "^4.8 || ^5.7"
},
"suggest": {
"ext-gd": "to use GD library based image processing.",
"ext-imagick": "to use Imagick based image processing.",
"intervention/imagecache": "Caching extension for the Intervention Image library"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
},
"laravel": {
"providers": [
"Intervention\\Image\\ImageServiceProvider"
],
"aliases": {
"Image": "Intervention\\Image\\Facades\\Image"
}
}
},
"autoload": {
"psr-4": {
"Intervention\\Image\\": "src/Intervention/Image"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Oliver Vogel",
"email": "oliver@olivervogel.com",
"homepage": "http://olivervogel.com/"
}
],
"description": "Image handling and manipulation library with support for Laravel integration",
"homepage": "http://image.intervention.io/",
"keywords": [
"gd",
"image",
"imagick",
"laravel",
"thumbnail",
"watermark"
],
"time": "2018-05-29T14:19:03+00:00"
},
{
"name": "league/flysystem",
"version": "1.0.48",

View file

@ -13,35 +13,33 @@
<div class="col-md-4" id="media_{{ media.id }}">
<div class="card mb-4 box-shadow">
{% if media.mimetype starts with 'image' %}
<a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension) }}" target="_blank">
<img class="card-img-top user-img" src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension ~ '/raw?width=348&height=192') }}" alt="{{ media.filename }}">
</a>
<img class="card-img" src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension ~ '/raw?width=286&height=219') }}" alt="Card image">
{% else %}
<a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension) }}" target="_blank">
<div class="card-header text-center"><i class="far fa-file fa-10x"></i></div>
</a>
<div class="text-center" style="font-size: 178px;"><i class="far {{ mime2font(media.mimetype) }} mb-4 mt-4"></i></div>
{% endif %}
<div class="card-body">
<p class="card-text">{{ media.filename }}
<small class="float-right">{{ media.size }}</small>
</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-success btn-clipboard" data-toggle="tooltip" title="{{ lang('copy_link') }}" data-clipboard-text="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension) }}">
<div class="card-img-overlay" title="{{ media.filename }}">
<div class="user-img-buttons">
<span class="badge badge-dark box-shadow-strong">{{ media.size }}</span>
<div class="btn-group box-shadow-strong float-right">
<a class="btn btn-sm btn-light" href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension) }}" data-toggle="tooltip" title="{{ lang('open') }}" target="_blank"><i class="fas fa-external-link-alt"></i></a>
<button type="button" class="btn btn-sm btn-success btn-clipboard" data-toggle="tooltip" title="{{ lang('copy_link') }}" data-clipboard-text="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension) }}">
<i class="fas fa-link"></i>
</button>
{% if media.published %}
<a href="javascript:void(0)" class="btn btn-sm btn-outline-warning publish-toggle" data-toggle="tooltip" title="{{ lang('hide') }}" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-times-circle"></i></a>
<a href="javascript:void(0)" class="btn btn-sm btn-warning publish-toggle" data-toggle="tooltip" title="{{ lang('hide') }}" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-times-circle"></i></a>
{% else %}
<a href="javascript:void(0)" class="btn btn-sm btn-outline-info publish-toggle" data-toggle="tooltip" title="{{ lang('publish') }}" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-check-circle"></i></a>
<a href="javascript:void(0)" class="btn btn-sm btn-info publish-toggle" data-toggle="tooltip" title="{{ lang('publish') }}" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-check-circle"></i></a>
{% endif %}
<button type="button" class="btn btn-sm btn-outline-danger media-delete" data-link="{{ route('upload.delete', {'id': media.id}) }}" data-id="{{ media.id }}" data-toggle="tooltip" title="{{ lang('delete') }}">
<button type="button" class="btn btn-sm btn-danger media-delete" data-link="{{ route('upload.delete', {'id': media.id}) }}" data-id="{{ media.id }}" data-toggle="tooltip" title="{{ lang('delete') }}">
<i class="fas fa-trash"></i>
</button>
</div>
<small class="text-muted">{{ media.timestamp|date("d/m/Y H:i:s") }}</small>
</div>
</div>
<div class="card-footer d-flex justify-content-between">
<span class="user-title">{{ media.filename }}</span>
<small>{{ media.timestamp|date("d/m/Y H:i") }}</small>
</div>
</div>
</div>
{% endfor %}

View file

@ -37,7 +37,7 @@
</div>
</form>
{% endif %}
{% if type starts with 'image' %}
{% if media.mimetype starts with 'image' %}
<div class="row mb-2">
<div class="col-md-12">
<img src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/raw') }}" class="img-thumbnail rounded mx-auto d-block" alt="{{ media.filename }}">
@ -48,7 +48,7 @@
{{ media.filename }}
</div>
</div>
{% elseif type starts with 'text' %}
{% elseif media.mimetype starts with 'text' %}
<div class="row mb-2">
<div class="col-md-12">
<pre><code>{{ media.text }}</code></pre>
@ -59,24 +59,37 @@
{{ media.filename }}
</div>
</div>
{% elseif type starts with 'video' %}
<div class="video-content">
<video class="video-js vjs-fluid vjs-big-play-centered" data-setup='{"controls": true, "autoplay": true, "preload": "auto"}'>
<source src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/raw') }}" type="{{ type }}">
{% elseif media.mimetype starts with 'video' %}
<div class="media-player">
<video class="video-js vjs-fluid vjs-big-play-centered" data-setup='{"controls": true, "autoplay": true, "preload": "auto", "responsive": true}'>
<source src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/raw') }}" type="{{ media.mimetype }}">
Your browser does not support HTML5 video.
<a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/download') }}" class="btn btn-dark btn-lg"><i class="fas fa-cloud-download-alt fa-fw"></i> Download</a>
</video>
</div>
{% elseif media.mimetype starts with 'audio' %}
<div class="media-player">
<audio class="video-js vjs-fluid vjs-big-play-centered" data-setup='{"controls": true, "autoplay": true, "preload": "auto", "responsive": true}'>
<source src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/raw') }}" type="{{ media.mimetype }}">
Your browser does not support HTML5 audio.
<a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/download') }}" class="btn btn-dark btn-lg"><i class="fas fa-cloud-download-alt fa-fw"></i> Download</a>
</audio>
</div>
{% else %}
<div class="text-center">
<div class="row mb-2">
<div class="row mb-3">
<div class="col-md-12">
<i class="far fa-file-archive fa-10x"></i>
<i class="far {{ mime2font(media.mimetype) }} fa-10x"></i>
</div>
</div>
<div class="row">
<div class="col-md-12">
<b>{{ media.filename }}</b>
</div>
</div>
<div class="row mb-4">
<div class="col-md-12">
{{ media.filename }}
{{ media.size }}
</div>
</div>
<div class="row">

View file

@ -45,6 +45,10 @@ body {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
.box-shadow-strong {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .15);
}
.admin-img {
max-height: 25px;
}
@ -53,6 +57,27 @@ body {
max-height: 192px;
}
.user-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200px;
}
.user-img-buttons {
opacity: 0;
transition: opacity .5s ease-in-out;
-moz-transition: opacity .5s ease-in-out;
-webkit-transition: opacity .5s ease-in-out;
}
.card-img-overlay:hover .user-img-buttons {
opacity: 1;
transition: opacity .2s ease-in-out;
-moz-transition: opacity .2s ease-in-out;
-webkit-transition: opacity .2s ease-in-out;
}
.footer {
position: absolute;
bottom: 0;
@ -62,7 +87,7 @@ body {
text-align: right;
}
.video-content {
.media-player{
width: 80%;
margin-right: auto;
margin-left: auto;

View file

@ -31,8 +31,8 @@ var app = {
.tooltip('dispose')
.attr('title', 'Publish')
.tooltip()
.removeClass('btn-outline-warning')
.addClass('btn-outline-info')
.removeClass('btn-warning')
.addClass('btn-info')
.html('<i class="fas fa-check-circle"></i>');
$('#published_' + id).html('<span class="badge badge-danger"><i class="fas fa-times"></i></span>');
});
@ -43,8 +43,8 @@ var app = {
.tooltip('dispose')
.attr('title', 'Unpublish')
.tooltip()
.removeClass('btn-outline-info')
.addClass('btn-outline-warning')
.removeClass('btn-info')
.addClass('btn-warning')
.html('<i class="fas fa-times-circle"></i>');
$('#published_' + id).html('<span class="badge badge-success"><i class="fas fa-check"></i></span>');
});