Merge branch 'feature/add_dark_theme'

This commit is contained in:
Kenneth Skovhede
2017-05-31 23:33:15 +02:00
7 changed files with 212 additions and 13 deletions
+24 -2
View File
@@ -835,7 +835,8 @@ namespace Duplicati.Server.Database
),
@"SELECT ""Key"", ""Value"" FROM ""UIStorage"" WHERE ""Scheme"" = ?",
scheme)
.ToDictionary(x => x.Key, x => x.Value);
.GroupBy(x => x.Key)
.ToDictionary(x => x.Key, x => x.Last().Value);
}
public void SetUISettings(string scheme, IDictionary<string, string> values, System.Data.IDbTransaction transaction = null)
@@ -856,7 +857,28 @@ namespace Duplicati.Server.Database
if (tr != null)
tr.Commit();
}
}
}
public void UpdateUISettings(string scheme, IDictionary<string, string> values, System.Data.IDbTransaction transaction = null)
{
lock (m_lock)
using (var tr = transaction == null ? m_connection.BeginTransaction() : null)
{
OverwriteAndUpdateDb(
tr,
@"DELETE FROM ""UIStorage"" WHERE ""Scheme"" = ? AND ""Key"" IN (?)", new object[] { scheme, values.Keys },
values.Where(x => x.Value != null),
@"INSERT INTO ""UIStorage"" (""Scheme"", ""Key"", ""Value"") VALUES (?, ?, ?)",
(f) =>
{
return new object[] { scheme, f.Key ?? "", f.Value ?? "" };
}
);
if (tr != null)
tr.Commit();
}
}
public TempFile[] GetTempFiles()
{
@@ -18,8 +18,45 @@ using System; using System.Collections.Generic; using Duplicati.Server.Serializa
using System.Collections.Generic;
using Duplicati.Server.Serialization;
using System.IO;
namespace Duplicati.Server.WebServer.RESTMethods
{
namespace Duplicati.Server.WebServer.RESTMethods
{
public class UISettings : IRESTMethodGET, IRESTMethodPOST, IRESTMethodPATCH
{
public void GET(string key, RequestInfo info)
{
if (string.IsNullOrWhiteSpace(key))
{
info.OutputOK(Program.DataConnection.GetUISettingsSchemes());
}
else
{
info.OutputOK(Program.DataConnection.GetUISettings(key));
}
}
public void POST(string key, RequestInfo info)
{
PATCH(key, info);
}
public void PATCH(string key, RequestInfo info)
{
if (string.IsNullOrWhiteSpace(key))
{
info.ReportClientError("Scheme is missing");
return;
}
IDictionary<string, string> data;
try
{
data = Serializer.Deserialize<Dictionary<string, string>>(new StreamReader(info.Request.Body));
}
catch (Exception ex)
{
info.ReportClientError(string.Format("Unable to parse settings object: {0}", ex.Message));
return;
}
if (data == null)
+2 -1
View File
@@ -27,6 +27,7 @@
<link rel="stylesheet" type="text/css" href="styles/smoothness/jquery-ui.min.css">
<link rel="stylesheet" type="text/css" href="styles/style.css">
<link rel="stylesheet" type="text/css" href="styles/themes.css">
<link rel="stylesheet" type="text/css" href="../oem/ngax/styles/oem.css" />
<link rel="stylesheet" type="text/css" href="../customized/customized.css" />
@@ -119,7 +120,7 @@
<script type="text/javascript" src="../customized/customized.js"></script>
</head>
<body ng-controller="AppController">
<body ng-controller="AppController" class="theme-{{active_theme}}">
<div class="container">
<div class="header">
@@ -5,6 +5,12 @@ backupApp.controller('AppController', function($scope, $cookies, $location, AppS
$scope.localized = {};
$scope.location = $location;
$scope.saved_theme = $scope.active_theme = $cookies.get('current-theme') || 'default';
// If we want the theme settings
// to be persisted on the server,
// set to "true" here
var save_theme_on_server = false;
$scope.doReconnect = function() {
ServerStatus.reconnect();
@@ -50,6 +56,9 @@ backupApp.controller('AppController', function($scope, $cookies, $location, AppS
};
function updateCurrentPage() {
$scope.active_theme = $scope.saved_theme;
if ($location.$$path == '/' || $location.$$path == '')
$scope.current_page = 'home';
else if ($location.$$path == '/addstart' || $location.$$path == '/add' || $location.$$path == '/import')
@@ -78,4 +87,58 @@ backupApp.controller('AppController', function($scope, $cookies, $location, AppS
$scope.$watch('location.$$path', updateCurrentPage);
updateCurrentPage();
function loadCurrentTheme() {
if (save_theme_on_server) {
AppService.get('/uisettings/ngax').then(
function(data) {
var theme = 'default';
if (data.data != null && (data.data['theme'] || '').trim().length > 0)
theme = data.data['theme'];
var now = new Date();
var exp = new Date(now.getFullYear()+10, now.getMonth(), now.getDate());
$cookies.put('current-theme', theme, { expires: exp });
$scope.saved_theme = $scope.active_theme = theme;
}, function() {}
);
}
};
// In case the cookie is out-of-sync
loadCurrentTheme();
$scope.$on('update_theme', function(event, args) {
var theme = 'default';
if (args != null && (args.theme || '').trim().length != 0)
theme = args.theme;
if (save_theme_on_server) {
// Set it here to avoid flickering when the page changes
$scope.saved_theme = $scope.active_theme = theme;
AppService.patch('/uisettings/ngax', { 'theme': theme }, {'headers': {'Content-Type': 'application/json'}}).then(
function(data) {
var now = new Date();
var exp = new Date(now.getFullYear()+10, now.getMonth(), now.getDate());
$cookies.put('current-theme', theme, { expires: exp });
$scope.saved_theme = $scope.active_theme = theme;
}, function() {}
);
} else {
var now = new Date();
var exp = new Date(now.getFullYear()+10, now.getMonth(), now.getDate());
$cookies.put('current-theme', theme, { expires: exp });
$scope.saved_theme = $scope.active_theme = theme;
}
loadCurrentTheme();
});
$scope.$on('preview_theme', function(event, args) {
if (args == null || (args.theme + '').trim().length == 0)
$scope.active_theme = $scope.saved_theme;
else
$scope.active_theme = args.theme || '';
});
});
@@ -1,6 +1,11 @@
backupApp.controller('SystemSettingsController', function($rootScope, $scope, $location, $cookies, AppService, AppUtils, SystemInfo, gettextCatalog) {
$scope.SystemInfo = SystemInfo.watch($scope);
$scope.SystemInfo = SystemInfo.watch($scope);
$scope.theme = $scope.$parent.$parent.saved_theme;
if (($scope.theme || '').trim().length == 0)
$scope.theme = 'default';
$scope.usageReporterLevel = '';
function reloadOptionsList() {
$scope.advancedOptionList = AppUtils.buildOptionList($scope.SystemInfo, false, false, false);
@@ -15,12 +20,15 @@ backupApp.controller('SystemSettingsController', function($rootScope, $scope, $l
$scope.ServerModules = mods;
AppUtils.extractServerModuleOptions($scope.advancedOptions, $scope.ServerModules, $scope.servermodulesettings, 'SupportedGlobalCommands');
}
};
reloadOptionsList();
$scope.$on('systeminfochanged', reloadOptionsList);
$scope.$watch('theme', function() {
$rootScope.$broadcast('preview_theme', { theme: $scope.theme });
});
$scope.uiLanguage = $cookies.get('ui-locale');
$scope.lang_browser_default = gettextCatalog.getString('Browser default');
$scope.lang_default = gettextCatalog.getString('Default');
@@ -37,7 +45,7 @@ backupApp.controller('SystemSettingsController', function($rootScope, $scope, $l
gettextCatalog.setCurrentLanguage($scope.uiLanguage.replace("-", "_"));
}
$rootScope.$broadcast('ui_language_changed');
}
};
AppService.get('/serversettings').then(function(data) {
@@ -62,7 +70,7 @@ backupApp.controller('SystemSettingsController', function($rootScope, $scope, $l
$scope.save = function() {
if ($scope.requireRemotePassword && $scope.remotePassword.trim().length == 0)
return AppUtil.notifyInputError('Cannot use empty password');
return AppUtils.notifyInputError('Cannot use empty password');
var patchdata = {
'server-passphrase': $scope.requireRemotePassword ? $scope.remotePassword : '',
@@ -73,7 +81,6 @@ backupApp.controller('SystemSettingsController', function($rootScope, $scope, $l
'usage-reporter-level': $scope.usageReporterLevel
};
if ($scope.requireRemotePassword) {
if ($scope.rawdata['server-passphrase'] != $scope.remotePassword) {
patchdata['server-passphrase-salt'] = CryptoJS.lib.WordArray.random(256/8).toString(CryptoJS.enc.Base64);
@@ -88,6 +95,8 @@ backupApp.controller('SystemSettingsController', function($rootScope, $scope, $l
for(var n in $scope.servermodulesettings)
patchdata['--' + n] = $scope.servermodulesettings[n];
$rootScope.$broadcast('update_theme', { theme: $scope.theme } );
AppService.patch('/serversettings', patchdata, {headers: {'Content-Type': 'application/json; charset=utf-8'}}).then(
function() {
setUILanguage();
@@ -0,0 +1,59 @@
body.theme-dark
{
background-color: #1a1a1a !important;
}
body.theme-dark .footer
{
background-color: #333333 !important;
}
body.theme-dark .header
{
background-color: #333333 !important;
}
body.theme-dark .state
{
background-color: #1a1a1a !important;
}
body.theme-dark form.styled .buttons input, body.theme-dark form.styled .buttons a
{
background: #4a5879;
}
body.theme-dark form.styled .buttons input:hover, body.theme-dark form.styled .buttons a:hover
{
background: #6089b5;
}
body.theme-dark .button
{
background: #4a5879;
}
body.theme-dark .button:hover
{
background: #6089b5;
}
body.theme-dark .container .body .mainmenu>ul>li>a.active
{
color: black;
}
body.theme-dark .container .body .content div.add .steps .step, body.theme-dark .container .body .content div.restore .steps .step
{
color: #2780b3;
}
body.theme-dark .step3 source-folder-picker, body.theme-dark #folder_path_picker, body.theme-dark #restore_file_picker
{
background-color: #ffffff;
}
body.theme-dark form.styled input, body.theme-dark form.styled textarea, body.theme-dark form.styled select
{
color: #000000;
}
@@ -34,7 +34,7 @@
</select>
</div>
<h2 translate>User interface language</h2>
<h2 translate>User interface settings</h2>
<div class="input mixed multiple">
<label for="userinterfacelanguage" translate>Language in user interface</label>
@@ -45,6 +45,14 @@
</select>
</div>
<div class="input mixed multiple">
<label for="themedisplay" translate>Display and color theme</label>
<select id="themedisplay" ng-model="theme">
<option value="default" translate>The default blue on white theme (by Alex)</option>
<option value="dark" translate>The dark theme (by Michal)</option>
</select>
</div>
<h2 translate>Donation messages</h2>
<div class="input checkbox">