Improved Bootstrap experience and services improvements #154

Manually merged
aheathershaw merged 8 commits from feature/146-bootstrap-experience into master 2020-04-30 08:48:54 +01:00
14 changed files with 342 additions and 34 deletions
Showing only changes of commit e06b227147 - Show all commits

View File

@ -1,7 +1,7 @@
APP_ENV=local APP_ENV=production
APP_KEY= APP_KEY=
APP_DEBUG=true APP_DEBUG=false
APP_LOG_LEVEL=debug APP_LOG_LEVEL=warning
APP_URL=http://localhost APP_URL=http://localhost
DB_CONNECTION=mysql DB_CONNECTION=mysql

View File

@ -14,7 +14,9 @@ module.exports = function(grunt)
const sass = require('node-sass'); const sass = require('node-sass');
grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-curl'); grunt.loadNpmTasks('grunt-curl');
grunt.loadNpmTasks('grunt-dart-sass'); grunt.loadNpmTasks('grunt-dart-sass');
grunt.loadNpmTasks('grunt-exec'); grunt.loadNpmTasks('grunt-exec');
@ -97,6 +99,23 @@ module.exports = function(grunt)
ext: '.css' ext: '.css'
}] }]
}, },
},
cssmin: {
bt_css: {
files: {
'public/css/blue-twilight.min.css': ['public/css/blue-twilight.css']
}
}
},
uglify: {
bt_js: {
options: {
sourceMap: true
},
files: {
'public/js/blue-twilight.min.js': ['public/js/blue-twilight.js']
}
}
} }
}); });
@ -121,7 +140,18 @@ module.exports = function(grunt)
'concat:bt_js' 'concat:bt_js'
]); ]);
grunt.registerTask('build-css-release', [
'build-css-debug',
'cssmin:bt_css'
]);
grunt.registerTask('build-js-release', [
'build-js-debug',
'uglify:bt_js'
]);
// Shortcut tasks for the ones above // Shortcut tasks for the ones above
grunt.registerTask('clean-all', ['clean:build_css', 'clean:build_js', 'clean:output']); grunt.registerTask('clean-all', ['clean:build_css', 'clean:build_js', 'clean:output']);
grunt.registerTask('build-debug', ['clean-all', 'build-css-debug', 'build-js-debug']); grunt.registerTask('build-debug', ['clean-all', 'build-css-debug', 'build-js-debug']);
grunt.registerTask('build-release', ['clean-all', 'build-css-release', 'build-js-release']);
}; };

View File

@ -2,7 +2,7 @@
return [ return [
// Version number of Blue Twilight // Version number of Blue Twilight
'version' => '2.1.2', 'version' => '2.2.0-beta.1',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -12,4 +12,4 @@
"grunt-exec": "^3.0.0", "grunt-exec": "^3.0.0",
"node-sass": "^4.13.0" "node-sass": "^4.13.0"
} }
} }

View File

@ -3,6 +3,7 @@
namespace AppBootstrap; namespace AppBootstrap;
use App\Services\GiteaService; use App\Services\GiteaService;
use Illuminate\Support\Facades\Artisan;
/** /**
* This class handles the downloading and extracting of the vendors directory. * This class handles the downloading and extracting of the vendors directory.
@ -13,17 +14,54 @@ use App\Services\GiteaService;
*/ */
class Bootstrapper class Bootstrapper
{ {
/**
* Path to /public/bootstrap
* @var string
*/
private $bootstrapDir;
/**
* Path to /app/config
* @var string
*/
private $configDir; private $configDir;
/**
* Path to / - the app's root
* @var string
*/
private $rootDir; private $rootDir;
/**
* Path to /public/bootstrap/temp
* @var string
*/
private $tempDir; private $tempDir;
/**
* Path to /vendor
* @var string
*/
private $vendorDir;
/**
* Path to /public/bootstrap/views
* @var string
*/
private $viewsDir; private $viewsDir;
public function __construct() public function __construct()
{ {
$this->rootDir = dirname(__DIR__); $this->bootstrapDir = dirname(__DIR__);
$this->configDir = sprintf('%s/config', dirname(dirname($this->rootDir))); $this->rootDir = dirname(dirname($this->bootstrapDir));
$this->tempDir = sprintf('%s/temp', $this->rootDir);
$this->viewsDir = sprintf('%s/views', $this->rootDir); $this->configDir = sprintf('%s/config', dirname(dirname($this->bootstrapDir)));
$this->tempDir = sprintf('%s/temp', $this->bootstrapDir);
$this->vendorDir = sprintf('%s/vendor', $this->rootDir);
$this->viewsDir = sprintf('%s/views', $this->bootstrapDir);
chdir($this->rootDir);
putenv('HOME=' . $this->rootDir);
} }
public function handleRequest() public function handleRequest()
@ -40,13 +78,21 @@ class Bootstrapper
$this->download(); $this->download();
return; return;
case 'extract':
$this->extract();
return;
case 'finalise':
$this->finalise();
return;
default: default:
throw new \Exception(sprintf('ERROR: Action \'%s\' was not recognised.', $_GET['act'])); throw new \Exception(sprintf('Action \'%s\' was not recognised.', $_GET['act']));
} }
} }
} }
private function download() protected function download()
{ {
$appConfig = require_once sprintf('%s/app.php', $this->configDir); $appConfig = require_once sprintf('%s/app.php', $this->configDir);
$servicesConfig = require_once sprintf('%s/services.php', $this->configDir); $servicesConfig = require_once sprintf('%s/services.php', $this->configDir);
@ -58,16 +104,17 @@ class Bootstrapper
if (is_null($releaseInfo)) if (is_null($releaseInfo))
{ {
throw new \Exception(sprintf('No release info found in Gitea for Blue Twilight version \'%\'', $versionNumber)); throw new \Exception(sprintf('No release info found in Gitea for Blue Twilight version \'%s\'', $versionNumber));
} }
else if (!isset($releaseInfo->assets)) else if (!isset($releaseInfo->assets))
{ {
throw new \Exception(sprintf('No assets found in Gitea for Blue Twilight version \'%\'', $versionNumber)); throw new \Exception(sprintf('No assets found in Gitea for Blue Twilight version \'%s\'', $versionNumber));
} }
$vendorsPrefix = 'vendors'; $vendorsPrefix = 'vendors';
$vendorsSuffix = '.tar.gz'; $vendorsSuffix = '.tar.gz';
$selectedAsset = null; $selectedAsset = null;
foreach ($releaseInfo->assets as $asset) foreach ($releaseInfo->assets as $asset)
{ {
/* /*
@ -87,13 +134,68 @@ class Bootstrapper
if (is_null($selectedAsset)) if (is_null($selectedAsset))
{ {
throw new \Exception('No vendors.tar.gz found in Gitea for Blue Twilight version \'%\'', $versionNumber); throw new \Exception(sprintf('No vendors.tar.gz found in Gitea for Blue Twilight version \'%s\'', $versionNumber));
} }
$targetFileName = sprintf('%s/vendors.tar.gz', $this->tempDir); $targetFileName = $this->getVendorsTempFileName();
$this->downloadFile($selectedAsset->browser_download_url, $targetFileName); $this->downloadFile($selectedAsset->browser_download_url, $targetFileName);
var_dump('done'); $this->json([
'result' => true,
'fileName' => $targetFileName
]);
}
protected function extract()
{
$targetFileName = $this->getVendorsTempFileName();
if (!file_exists($targetFileName) || !is_readable($targetFileName))
{
throw new \Exception(sprintf('The file \'%s\' does not exist or is not readable', $targetFileName));
}
$phar = new \PharData($targetFileName);
$phar->extractTo($this->rootDir, null, true);
// We should always have a vendor/autoload.php
$vendorsTestFile = $this->getVendorsAutoloadFileName();
if (file_exists($vendorsTestFile))
{
$this->json([
'result' => true,
'testFile' => $vendorsTestFile
]);
}
else
{
throw new \Exception('The extraction failed');
}
}
protected function finalise()
{
$result = [
'envFileCreated' => false
];
$result['envFileCreated'] = $this->createEnvFileIfNotExist();
require sprintf('%s/bootstrap/autoload.php', $this->rootDir);
$app = require_once sprintf('%s/bootstrap/app.php', $this->rootDir);
$kernel = $app->make(\Illuminate\Contracts\Console\Kernel::class);
$kernel->bootstrap();
if ($result['envFileCreated'])
{
Artisan::call('key:generate');
}
$kernel->terminate(null, null);
$this->json($result);
} }
private function downloadFile($sourceURL, $targetFilename) private function downloadFile($sourceURL, $targetFilename)
@ -120,6 +222,44 @@ class Bootstrapper
@fclose($tempFilename); @fclose($tempFilename);
} }
private function createEnvFileIfNotExist()
{
$envFile = $this->getEnvFileName();
if (!file_exists($envFile))
{
copy($this->getEnvExampleFileName(), $envFile);
return true;
}
return false;
}
private function getEnvExampleFileName()
{
return sprintf('%s/.env.example', $this->rootDir);
}
private function getEnvFileName()
{
return sprintf('%s/.env', $this->rootDir);
}
private function getVendorsAutoloadFileName()
{
return sprintf('%s/autoload.php', $this->vendorDir);
}
private function getVendorsTempFileName()
{
return sprintf('%s/vendors.tar.gz', $this->tempDir);
}
private function json($data)
{
echo json_encode($data);
}
private function view($name, array $viewData = []) private function view($name, array $viewData = [])
{ {
$viewFile = sprintf('%s/%s.php', $this->viewsDir, $name); $viewFile = sprintf('%s/%s.php', $this->viewsDir, $name);

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="../css/blue-twilight.css"> <link rel="stylesheet" href="../css/blue-twilight.min.css">
<title><?php echo $appName; ?></title> <title><?php echo $appName; ?></title>
<style type="text/css"> <style type="text/css">
* { * {
@ -23,12 +23,12 @@
<p class="lead"><b>Welcome to Blue Twilight - the self-hosted PHP photo gallery.</b></p> <p class="lead"><b>Welcome to Blue Twilight - the self-hosted PHP photo gallery.</b></p>
<div v-if="!isRunning"> <div v-if="!isRunning">
<p>We need to download and install a few more files before you get started.</p> <p>We need to download and install a few more files before you get started.</p>
<p>Click the &quot;Let's Go&quot; button below to begin.</p> <p>Click the &quot;Let's Go&quot; button below to begin.</p>
<p class="mb-0 mt-4"><button class="btn btn-primary btn-lg" @click.prevent="bootstrap">Let's Go</button></p> <p class="mb-0 mt-4"><button class="btn btn-primary btn-lg" @click.prevent="bootstrap">Let's Go</button></p>
</div> </div>
<div v-else> <div v-else>
<ul> <ul v-cloak>
<li class="operation mb-3" v-for="operation in operations"> <li class="operation mb-3" v-for="operation in operations">
<div class="status mr-1"> <div class="status mr-1">
<img src="images/waiting.svg" v-if="!operation.isRunning && !operation.isCompleted"></img> <img src="images/waiting.svg" v-if="!operation.isRunning && !operation.isCompleted"></img>
@ -45,7 +45,7 @@
</div> </div>
</div> </div>
<script src="../js/blue-twilight.js"></script> <script src="../js/blue-twilight.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
$(function() $(function()
{ {

View File

@ -10260,6 +10260,29 @@ a.text-dark:hover, a.text-dark:focus {
[v-cloak] { [v-cloak] {
display: none; display: none;
} }
#bootstrapper [v-cloak] {
display: none;
}
#bootstrapper .operation .status {
display: inline-block;
height: 32px;
width: 32px;
}
#bootstrapper .operation .status i,
#bootstrapper .operation .status img {
height: 100%;
width: 100%;
}
#bootstrapper .operation .status span {
display: inline-block;
height: 100%;
vertical-align: middle;
}
#bootstrapper ul {
list-style-type: none;
margin: 0;
padding: 0;
}
.activity-grid { .activity-grid {
font-size: smaller; font-size: smaller;
} }

6
public/css/blue-twilight.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -35700,6 +35700,12 @@ function AnalyseAlbumViewModel() {
}); });
item.isSuccessful = false; item.isSuccessful = false;
item.isPending = false; item.isPending = false;
var indexToRemove = self.imagesInProgress.indexOf(item);
if (indexToRemove > -1)
{
self.imagesInProgress.splice(indexToRemove, 1);
}
} }
} }
} }
@ -35824,6 +35830,7 @@ function EditPhotosViewModel(album_id, language, urls) {
deletePhoto: function (e) { deletePhoto: function (e) {
var self = this; var self = this;
this.selectPhotoSingle(e.target); this.selectPhotoSingle(e.target);
var parent = $(e.target).closest('.photo');
var photo_id = self.photoIDs[0]; var photo_id = self.photoIDs[0];
this.photoIDs = []; this.photoIDs = [];
@ -35846,7 +35853,7 @@ function EditPhotosViewModel(album_id, language, urls) {
$('.loading', parent).show(); $('.loading', parent).show();
$.post(url, {'_method': 'DELETE'}, function (data) { $.post(url, {'_method': 'DELETE'}, function (data) {
window.location.reload(); $(parent).remove();
}); });
} }
} }
@ -35875,10 +35882,11 @@ function EditPhotosViewModel(album_id, language, urls) {
} }
$('.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();
}); });
@ -35958,10 +35966,10 @@ function EditPhotosViewModel(album_id, language, urls) {
url = url.replace(/\/0$/, '/' + this.photoIDs[0]); url = url.replace(/\/0$/, '/' + this.photoIDs[0]);
$('.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()); image.attr('src', response.thumbnail_url);
$('.loading', parent).hide(); $('.loading', parent).hide();
}); });
@ -36310,6 +36318,79 @@ function UploadPhotosViewModel(album_id, queue_token, language, urls) {
} }
}; };
} }
/**
* This model is used by the system bootstrapper in public/bootstrap.
* @constructor
*/
function BootstrapperViewModel() {
this.el = '#bootstrapper';
this.data = {
isCompleted: false,
isRunning: false,
operations: []
}
this.methods = {
bootstrap: function()
{
this.operations.push({
'isCompleted': false,
'isRunning': false,
'name': 'Removing any previous versions',
'url': '?act=removePrevious'
});
this.operations.push({
'isCompleted': false,
'isRunning': false,
'name': 'Downloading new files',
'url': '?act=download'
});
this.operations.push({
'isCompleted': false,
'isRunning': false,
'name': 'Extracting new files',
'url': '?act=extract'
});
this.operations.push({
'isCompleted': false,
'isRunning': false,
'name': 'Cleaning up',
'url': '?act=finalise'
});
this.isRunning = true;
this.runOperation(this.operations[0], 0);
},
runOperation: function(operation, index)
{
var self = this;
operation.isRunning = true;
$.post(operation.url)
.done(function(result)
{
operation.isRunning = false;
operation.isCompleted = true;
index++;
if (index < self.operations.length)
{
self.runOperation(self.operations[index], index);
}
else
{
//self.isRunning = false;
self.isCompleted = true;
window.location = '../';
}
})
}
}
}
/*! /*!
* Chart.js * Chart.js
* http://chartjs.org/ * http://chartjs.org/
@ -54804,6 +54885,27 @@ module.exports = function(Chart) {
},{"25":25,"45":45,"6":6}]},{},[7])(7) },{"25":25,"45":45,"6":6}]},{},[7])(7)
}); });
function ExternalServiceViewModel()
{
this.el = '#external-service-options';
this.data = {
service_type: ''
};
this.computed = {
hasOAuthStandardOptions: function()
{
// This logic must be mirrored in App\ExternalService
return this.service_type === 'facebook' ||
this.service_type === 'google' ||
this.service_type === 'twitter';
},
isDropbox: function()
{
// This logic must be mirrored in App\ExternalService
return this.service_type === 'dropbox';
}
}
}
/** /**
* This model is used by gallery/explore_users.blade.php, to handle following/unfollowing users profiles. * This model is used by gallery/explore_users.blade.php, to handle following/unfollowing users profiles.
* @constructor * @constructor

2
public/js/blue-twilight.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,7 @@ function BootstrapperViewModel() {
operations: [] operations: []
} }
this.methods = { this.methods = {
bootstrap() bootstrap: function()
{ {
this.operations.push({ this.operations.push({
'isCompleted': false, 'isCompleted': false,
@ -37,14 +37,14 @@ function BootstrapperViewModel() {
'isCompleted': false, 'isCompleted': false,
'isRunning': false, 'isRunning': false,
'name': 'Cleaning up', 'name': 'Cleaning up',
'url': '?act=cleanup' 'url': '?act=finalise'
}); });
this.isRunning = true; this.isRunning = true;
this.runOperation(this.operations[0], 0); this.runOperation(this.operations[0], 0);
}, },
runOperation(operation, index) runOperation: function(operation, index)
{ {
var self = this; var self = this;
operation.isRunning = true; operation.isRunning = true;
@ -64,6 +64,8 @@ function BootstrapperViewModel() {
{ {
//self.isRunning = false; //self.isRunning = false;
self.isCompleted = true; self.isCompleted = true;
window.location = '../';
} }
}) })
} }

View File

@ -5,14 +5,14 @@ function ExternalServiceViewModel()
service_type: '' service_type: ''
}; };
this.computed = { this.computed = {
hasOAuthStandardOptions() hasOAuthStandardOptions: function()
{ {
// This logic must be mirrored in App\ExternalService // This logic must be mirrored in App\ExternalService
return this.service_type === 'facebook' || return this.service_type === 'facebook' ||
this.service_type === 'google' || this.service_type === 'google' ||
this.service_type === 'twitter'; this.service_type === 'twitter';
}, },
isDropbox() isDropbox: function()
{ {
// This logic must be mirrored in App\ExternalService // This logic must be mirrored in App\ExternalService
return this.service_type === 'dropbox'; return this.service_type === 'dropbox';

View File

@ -1,5 +1,7 @@
#bootstrapper #bootstrapper
{ {
[v-cloak] { display: none }
.operation .operation
{ {
.status .status