Flatten GUI tree somewhat

The very deep tree structure didn't really aggree with me, sorry. This
makes the core module rather large, but on the other hand that just
highlights that it is rather large.
This commit is contained in:
Jakob Borg
2015-08-02 09:05:19 +02:00
parent 26d52bedb3
commit 9513e91d66
63 changed files with 81 additions and 76 deletions

View File

@@ -0,0 +1,7 @@
angular.module('syncthing.core')
.directive('aboutModal', function () {
return {
restrict: 'A',
templateUrl: 'syncthing/core/aboutModalView.html'
};
});

View File

@@ -0,0 +1,90 @@
<modal id="about" large="yes" close="yes" status="info" title="{{'About' | translate}}">
<h1 class="text-center"><img alt="Syncthing" title="Syncthing" src="assets/img/logo-horizontal.svg" style="vertical-align: -16px" height="100" width="366"/><br/><small>{{versionString()}}</small></h1>
<hr/>
<p translate>Copyright &copy; 2015 the following Contributors:</p>
<div class="row">
<div class="col-md-12">
<ul class="list-unstyled three-columns" id="contributor-list">
<li class="auto-generated">Aaron Bieber</li>
<li class="auto-generated">Alexander Graf</li>
<li class="auto-generated">Andrew Dunham</li>
<li class="auto-generated">Antony Male</li>
<li class="auto-generated">Arthur Axel fREW Schmidt</li>
<li class="auto-generated">Audrius Butkevicius</li>
<li class="auto-generated">Bart De Vries</li>
<li class="auto-generated">Ben Curthoys</li>
<li class="auto-generated">Ben Schulz</li>
<li class="auto-generated">Ben Sidhom</li>
<li class="auto-generated">Brandon Philips</li>
<li class="auto-generated">Brendan Long</li>
<li class="auto-generated">Brian R. Becker</li>
<li class="auto-generated">Caleb Callaway</li>
<li class="auto-generated">Carsten Hagemann</li>
<li class="auto-generated">Cathryne Linenweaver</li>
<li class="auto-generated">Chris Howie</li>
<li class="auto-generated">Chris Joel</li>
<li class="auto-generated">Colin Kennedy</li>
<li class="auto-generated">Daniel Bergmann</li>
<li class="auto-generated">Daniel Martí</li>
<li class="auto-generated">Denis A.</li>
<li class="auto-generated">Dennis Wilson</li>
<li class="auto-generated">Dominik Heidler</li>
<li class="auto-generated">Elias Jarlebring</li>
<li class="auto-generated">Emil Hessman</li>
<li class="auto-generated">Erik Meitner</li>
<li class="auto-generated">Federico Castagnini</li>
<li class="auto-generated">Felix Ableitner</li>
<li class="auto-generated">Felix Unterpaintner</li>
<li class="auto-generated">Francois-Xavier Gsell</li>
<li class="auto-generated">Frank Isemann</li>
<li class="auto-generated">Gilli Sigurdsson</li>
<li class="auto-generated">Jacek Szafarkiewicz</li>
<li class="auto-generated">Jakob Borg</li>
<li class="auto-generated">James Patterson</li>
<li class="auto-generated">Jaroslav Malec</li>
<li class="auto-generated">Jens Diemer</li>
<li class="auto-generated">Jochen Voss</li>
<li class="auto-generated">Johan Vromans</li>
<li class="auto-generated">Karol Różycki</li>
<li class="auto-generated">Ken'ichi Kamada</li>
<li class="auto-generated">Lode Hoste</li>
<li class="auto-generated">Lord Landon Agahnim</li>
<li class="auto-generated">Marc Laporte</li>
<li class="auto-generated">Marc Pujol</li>
<li class="auto-generated">Marcin Dziadus</li>
<li class="auto-generated">Michael Jephcote</li>
<li class="auto-generated">Michael Tilli</li>
<li class="auto-generated">Pascal Jungblut</li>
<li class="auto-generated">Peter Hoeg</li>
<li class="auto-generated">Philippe Schommers</li>
<li class="auto-generated">Phill Luby</li>
<li class="auto-generated">Piotr Bejda</li>
<li class="auto-generated">Ryan Sullivan</li>
<li class="auto-generated">Sergey Mishin</li>
<li class="auto-generated">Stefan Tatschner</li>
<li class="auto-generated">Tim Abell</li>
<li class="auto-generated">Tobias Nygren</li>
<li class="auto-generated">Tomas Cerveny</li>
<li class="auto-generated">Tully Robinson</li>
<li class="auto-generated">Veeti Paananen</li>
<li class="auto-generated">Vil Brekin</li>
</ul>
</div>
</div>
<hr/>
<p translate>Syncthing includes the following software or portions thereof:</p>
<ul class="list-unstyled two-columns">
<li><a href="https://golang.org">The Go Programming Language</a>, Copyright &copy; 2012 The Go Authors.</li>
<li><a href="https://github.com/bkaradzic/go-lz4">bkaradzic/go-lz4</a>, Copyright &copy; 2011-2012 Branimir Karadzic, 2013 Damian Gryski.</li>
<li><a href="https://github.com/kardianos/osext">kardianos/osext</a>, Copyright &copy; 2012 Daniel Theophanes.</li>
<li><a href="https://github.com/golang/snappy">golang/snappy</a>, Copyright &copy; 2011 The Snappy-Go Authors.</li>
<li><a href="https://github.com/juju/ratelimit">juju/ratelimit</a>, Copyright &copy; 2015 Canonical Ltd.</li>
<li><a href="https://github.com/thejerf/suture">thejerf/suture</a>, Copyright &copy; 2014-2015 Barracuda Networks, Inc.</li>
<li><a href="https://github.com/syndtr/goleveldb">syndtr/goleveldb</a>, Copyright &copy; 2012, Suryandaru Triandana</li>
<li><a href="https://github.com/vitrun/qart">vitrun/qart</a>, Copyright &copy; The Go Authors.</li>
<li><a href="https://angularjs.org/">AngularJS</a>, Copyright &copy; 2010-2015 Google, Inc.</li>
<li><a href="http://getbootstrap.com/">Bootstrap</a>, Copyright &copy; 2011-2015 Twitter, Inc.</li>
</ul>
</modal>

View File

@@ -0,0 +1,9 @@
angular.module('syncthing.core')
.filter('alwaysNumber', function () {
return function (input) {
if (input === undefined) {
return 0;
}
return input;
};
});

View File

@@ -0,0 +1,12 @@
angular.module('syncthing.core')
.filter('basename', function () {
return function (input) {
if (input === undefined)
return "";
var parts = input.split(/[\/\\]/);
if (!parts || parts.length < 1) {
return input;
}
return parts[parts.length - 1];
};
});

View File

@@ -0,0 +1,21 @@
angular.module('syncthing.core')
.filter('binary', function () {
return function (input) {
if (input === undefined) {
return '0 ';
}
if (input > 1024 * 1024 * 1024) {
input /= 1024 * 1024 * 1024;
return input.toFixed(decimals(input, 2)) + ' Gi';
}
if (input > 1024 * 1024) {
input /= 1024 * 1024;
return input.toFixed(decimals(input, 2)) + ' Mi';
}
if (input > 1024) {
input /= 1024;
return input.toFixed(decimals(input, 2)) + ' Ki';
}
return Math.round(input) + ' ';
};
});

View File

@@ -0,0 +1,35 @@
/** convert amount of seconds to string format "d h m s" without zero values
* precision must be one of 'd', 'h', 'm', 's'(default)
* Example:
* {{121020003|duration}} --> 1400d 16h 40m 3s
* {{121020003|duration:"m"}} --> 1400d 16h 40m
* {{121020003|duration:"h"}} --> 1400d 16h
* {{1|duration:"h"}} --> <1h
**/
angular.module('syncthing.core')
.filter('duration', function () {
'use strict';
var SECONDS_IN = {"d": 86400, "h": 3600, "m": 60, "s": 1};
return function (input, precision) {
var result = "";
if (!precision) {
precision = "s";
}
input = parseInt(input, 10);
for (var k in SECONDS_IN) {
var t = (input/SECONDS_IN[k] | 0); // Math.floor
if (t > 0) {
result += " " + t + k;
}
if (precision == k) {
return result ? result : "<1" + k;
} else {
input %= SECONDS_IN[k];
}
}
return "[Error: incorrect usage, precision must be one of " + Object.keys(SECONDS_IN) + "]";
};
});

View File

@@ -0,0 +1,86 @@
var debugEvents = !true;
angular.module('syncthing.core')
.service('Events', ['$http', '$rootScope', '$timeout', function ($http, $rootScope, $timeout) {
'use strict';
var lastID = 0;
var self = this;
function successFn (data) {
// When Syncthing restarts while the long polling connection is in
// progress the browser on some platforms returns a 200 (since the
// headers has been flushed with the return code 200), with no data.
// This basically means that the connection has been reset, and the call
// was not actually successful.
if (!data) {
errorFn(data);
return;
}
$rootScope.$broadcast(self.ONLINE);
if (lastID > 0) { // not emit events from first response
data.forEach(function (event) {
if (debugEvents) {
console.log("event", event.id, event.type, event.data);
}
$rootScope.$broadcast(event.type, event);
});
}
var lastEvent = data.pop();
if (lastEvent) {
lastID = lastEvent.id;
}
$timeout(function () {
$http.get(urlbase + '/events?since=' + lastID)
.success(successFn)
.error(errorFn);
}, 500, false);
}
function errorFn (dummy) {
$rootScope.$broadcast(self.OFFLINE);
$timeout(function () {
$http.get(urlbase + '/events?limit=1')
.success(successFn)
.error(errorFn);
}, 1000, false);
}
angular.extend(self, {
// emitted by this
ONLINE: 'UIOnline',
OFFLINE: 'UIOffline',
// emitted by syncthing process
CONFIG_SAVED: 'ConfigSaved', // Emitted after the config has been saved by the user or by Syncthing itself
DEVICE_CONNECTED: 'DeviceConnected', // Generated each time a connection to a device has been established
DEVICE_DISCONNECTED: 'DeviceDisconnected', // Generated each time a connection to a device has been terminated
DEVICE_DISCOVERED: 'DeviceDiscovered', // Emitted when a new device is discovered using local discovery
DEVICE_REJECTED: 'DeviceRejected', // Emitted when there is a connection from a device we are not configured to talk to
DOWNLOAD_PROGRESS: 'DownloadProgress', // Emitted during file downloads for each folder for each file
FOLDER_COMPLETION: 'FolderCompletion', //Emitted when the local or remote contents for a folder changes
FOLDER_REJECTED: 'FolderRejected', // Emitted when a device sends index information for a folder we do not have, or have but do not share with the device in question
FOLDER_SUMMARY: 'FolderSummary', // Emitted when folder contents have changed locally
ITEM_FINISHED: 'ItemFinished', // Generated when Syncthing ends synchronizing a file to a newer version
ITEM_STARTED: 'ItemStarted', // Generated when Syncthing begins synchronizing a file to a newer version
LOCAL_INDEX_UPDATED: 'LocalIndexUpdated', // Generated when the local index information has changed, due to synchronizing one or more items from the cluster or discovering local changes during a scan
PING: 'Ping', // Generated automatically every 60 seconds
REMOTE_INDEX_UPDATED: 'RemoteIndexUpdated', // Generated each time new index information is received from a device
STARTING: 'Starting', // Emitted exactly once, when Syncthing starts, before parsing configuration etc
STARTUP_COMPLETED: 'StartupCompleted', // Emitted exactly once, when initialization is complete and Syncthing is ready to start exchanging data with other devices
STATE_CHANGED: 'StateChanged', // Emitted when a folder changes state
FOLDER_ERRORS: 'FolderErrors', // Emitted when a folder has errors preventing a full sync
start: function() {
$http.get(urlbase + '/events?limit=1')
.success(successFn)
.error(errorFn);
}
});
}]);

View File

@@ -0,0 +1,27 @@
/**
* m59peacemaker's filterStabilize
*
* See https://github.com/m59peacemaker/angular-pmkr-components/tree/master/src/filterStabilize
* Released under the MIT license
*/
angular.module('syncthing.core')
.factory('pmkr.filterStabilize', [
'pmkr.memoize',
function(memoize) {
function service(fn) {
function filter() {
var args = [].slice.call(arguments);
// always pass a copy of the args so that the original input can't be modified
args = angular.copy(args);
// return the `fn` return value or input reference (makes `fn` return optional)
var filtered = fn.apply(this, args) || args[0];
return filtered;
}
var memoized = memoize(filter);
return memoized;
}
return service;
}
]);

View File

@@ -0,0 +1,25 @@
/**
* Groups input in chunks of the specified size
*
* E.g. [1, 2, 3, 4, 5] with groupSize = 3 => [[1, 2, 3], [4, 5]]
* Uses pmkr.memoize to avoid infdig, see 'Johnny Hauser's "Filter Stablize" Solution'
* here: http://sobrepere.com/blog/2014/10/14/creating-groupby-filter-angularjs/
*/
angular.module('syncthing.core')
.filter('group', [
'pmkr.filterStabilize',
function (stabilize) {
return stabilize(function(items, groupSize) {
var groups = [];
var inner;
for (var i = 0; i < items.length; i++) {
if (i % groupSize === 0) {
inner = [];
groups.push(inner);
}
inner.push(items[i]);
}
return groups;
});
}
]);

View File

@@ -0,0 +1,7 @@
angular.module('syncthing.core')
.directive('httpErrorDialog', function () {
return {
restrict: 'A',
templateUrl: 'syncthing/core//httpErrorDialogView.html'
};
});

View File

@@ -0,0 +1,5 @@
<modal id="httpError" status="danger" icon="exclamation-sign" title="{{'Connection Error' | translate}}">
<p translate>
Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.
</p>
</modal>

View File

@@ -0,0 +1,65 @@
angular.module('syncthing.core')
.directive('identicon', ['$window', function ($window) {
var svgNS = 'http://www.w3.org/2000/svg';
function Identicon(value, size) {
var svg = document.createElementNS(svgNS, 'svg');
var shouldFillRectAt = function (row, col) {
return !($window.parseInt(value.charCodeAt(row + col * size), 10) % 2);
};
var shouldMirrorRectAt = function (row, col) {
return !(size % 2 && col === middleCol)
};
var mirrorColFor = function (col) {
return size - col - 1;
};
var fillRectAt = function (row, col) {
var rect = document.createElementNS(svgNS, 'rect');
rect.setAttribute('x', (col * rectSize) + '%');
rect.setAttribute('y', (row * rectSize) + '%');
rect.setAttribute('width', rectSize + '%');
rect.setAttribute('height', rectSize + '%');
svg.appendChild(rect);
};
var rect;
var row;
var col;
var middleCol;
var rectSize;
svg.setAttribute('class', 'identicon');
size = size || 5;
rectSize = 100 / size;
middleCol = Math.ceil(size / 2) - 1;
if (value) {
value = value.toString().replace(/[\W_]/i, '');
for (row = 0; row < size; ++row) {
for (col = middleCol; col > -1; --col) {
if (shouldFillRectAt(row, col)) {
fillRectAt(row, col);
if (shouldMirrorRectAt(row, col)) {
fillRectAt(row, mirrorColFor(col));
}
}
}
}
}
return svg;
}
return {
restrict: 'E',
scope: {
value: '='
},
link: function (scope, element, attributes) {
element.append(new Identicon(scope.value));
}
}
}]);

View File

@@ -0,0 +1,48 @@
angular.module('syncthing.core')
.directive('languageSelect', function (LocaleService) {
'use strict';
return {
restrict: 'EA',
template:
'<a ng-if="visible" href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="true"><span class="glyphicon glyphicon-globe"></span>&nbsp;{{localesNames[currentLocale] || "English"}} <span class="caret"></span></a>'+
'<ul ng-if="visible" class="dropdown-menu">'+
'<li ng-repeat="(i,name) in localesNames" ng-class="{active: i==currentLocale}">'+
'<a href="#" data-ng-click="changeLanguage(i)">{{name}}</a>'+
'</li>'+
'</ul>',
link: function ($scope) {
var availableLocales = LocaleService.getAvailableLocales();
var localeNames = LocaleService.getLocalesDisplayNames();
var availableLocaleNames = {};
// get only locale names that present in available locales
for (var i = 0; i < availableLocales.length; i++) {
var a = availableLocales[i];
if (localeNames[a]) {
availableLocaleNames[a] = localeNames[a];
} else {
// show code lang if it is not in the dict
availableLocaleNames[a] = '[' + a + ']';
}
}
$scope.localesNames = availableLocaleNames;
$scope.visible = $scope.localesNames && $scope.localesNames['en'];
// using $watch cause LocaleService.currentLocale will be change after receive async query accepted-languages
// in LocaleService.readBrowserLocales
var remove_watch = $scope.$watch(LocaleService.getCurrentLocale, function (newValue) {
if (newValue) {
$scope.currentLocale = newValue;
remove_watch();
}
});
$scope.changeLanguage = function (locale) {
LocaleService.useLocale(locale, true);
$scope.currentLocale = locale;
};
}
};
});

View File

@@ -0,0 +1,12 @@
angular.module('syncthing.core')
.filter('lastErrorComponent', function () {
return function (input) {
if (input === undefined)
return "";
var parts = input.split(/:\s*/);
if (!parts || parts.length < 1) {
return input;
}
return parts[parts.length - 1];
};
});

View File

@@ -0,0 +1,119 @@
angular.module('syncthing.core')
.provider('LocaleService', function () {
'use strict';
function detectLocalStorage() {
// Feature detect localStorage; https://mathiasbynens.be/notes/localstorage-pattern
try {
var uid = new Date();
var storage = window.localStorage;
storage.setItem(uid, uid);
var success = storage.getItem(uid) == uid;
storage.removeItem(uid);
return storage;
} catch (exception) {
return undefined;
}
}
var _defaultLocale,
_availableLocales,
_localStorage = detectLocalStorage();
var _SYNLANG = "SYN_LANG"; // const key for localStorage
this.setDefaultLocale = function (locale) {
_defaultLocale = locale;
};
this.setAvailableLocales = function (locales) {
_availableLocales = locales;
};
this.$get = ['$http', '$translate', '$location', function ($http, $translate, $location) {
/**
* Requests the server in order to get the browser's requested locale strings.
*
* @returns promise which on success resolves with a locales array
*/
function readBrowserLocales() {
// @TODO: check if there is nice way to utilize window.navigator.languages or similar api.
return $http.get(urlbase + "/svc/lang");
}
function autoConfigLocale() {
var params = $location.search();
var savedLang;
if (_localStorage) {
savedLang = _localStorage[_SYNLANG];
}
if(params.lang) {
useLocale(params.lang, true);
} else if (savedLang) {
useLocale(savedLang);
} else {
readBrowserLocales().success(function (langs) {
// Find the first language in the list provided by the user's browser
// that is a prefix of a language we have available. That is, "en"
// sent by the browser will match "en" or "en-US", while "zh-TW" will
// match only "zh-TW" and not "zh-CN".
var i,
lang,
matching,
locale = _defaultLocale;
for (i = 0; i < langs.length; i++) {
lang = langs[i];
if (lang.length < 2) {
continue;
}
matching = _availableLocales.filter(function (possibleLang) {
// The langs returned by the /rest/langs call will be in lower
// case. We compare to the lowercase version of the language
// code we have as well.
possibleLang = possibleLang.toLowerCase();
if (possibleLang.length > lang.length) {
return possibleLang.indexOf(lang) === 0;
} else {
return lang.indexOf(possibleLang) === 0;
}
});
if (matching.length >= 1) {
locale = matching[0];
break;
}
}
// Fallback if nothing matched
useLocale(locale);
});
}
}
function useLocale(language, save2Storage) {
if (language) {
$translate.use(language).then(function () {
if (save2Storage && _localStorage)
_localStorage[_SYNLANG] = language;
});
}
}
return {
autoConfigLocale: autoConfigLocale,
useLocale: useLocale,
getCurrentLocale: function() { return $translate.use() },
getAvailableLocales: function() { return _availableLocales },
// langPrettyprint comes from an included global
getLocalesDisplayNames: function() { return langPrettyprint }
}
}];
});

View File

@@ -0,0 +1,7 @@
angular.module('syncthing.core')
.directive('majorUpgradeModal', function () {
return {
restrict: 'A',
templateUrl: 'syncthing/core//majorUpgradeModalView.html'
};
});

View File

@@ -0,0 +1,26 @@
<div id="majorUpgrade" class="modal fade" tabindex="-1" data-backdrop="true" data-keyboard="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header alert alert-danger">
<h4 class="modal-title">
<span ng-if="icon" class="glyphicon glyphicon-chevron-up"></span>
<span translate>Major Upgrade</span>
</h4>
</div>
<div class="modal-body">
<p>
<span translate>This is a major version upgrade.</span>
<span translate>A new major version may not be compatible with previous versions.</span>
<span translate>Please consult the release notes before performing a major upgrade.</span>
</p>
<p>
<a href="https://github.com/syncthing/syncthing/releases/tag/{{upgradeInfo.latest}}" target="_blank" translate>Release Notes</a>
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-sm" ng-click="upgrade()"><span class="glyphicon glyphicon-ok"></span>&nbsp;<span translate>Upgrade</span></button>
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span>&nbsp;<span translate>Close</span></button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,28 @@
/**
* m59peacemaker's memoize
*
* See https://github.com/m59peacemaker/angular-pmkr-components/tree/master/src/memoize
* Released under the MIT license
*/
angular.module('syncthing.core')
.factory('pmkr.memoize', [
function() {
function service() {
return memoizeFactory.apply(this, arguments);
}
function memoizeFactory(fn) {
var cache = {};
function memoized() {
var args = [].slice.call(arguments);
var key = JSON.stringify(args);
if (cache.hasOwnProperty(key)) {
return cache[key];
}
cache[key] = fn.apply(this, arguments);
return cache[key];
}
return memoized;
}
return service;
}
]);

View File

@@ -0,0 +1,16 @@
angular.module('syncthing.core')
.directive('modal', function () {
return {
restrict: 'E',
templateUrl: 'modal.html',
replace: true,
transclude: true,
scope: {
title: '@',
status: '@',
icon: '@',
close: '@',
large: '@'
}
};
});

View File

@@ -0,0 +1 @@
angular.module('syncthing.core', []);

View File

@@ -0,0 +1,6 @@
angular.module('syncthing.core')
.filter('natural', function () {
return function (input, valid) {
return input.toFixed(decimals(input, valid));
};
});

View File

@@ -0,0 +1,7 @@
angular.module('syncthing.core')
.directive('networkErrorDialog', function () {
return {
restrict: 'A',
templateUrl: 'syncthing/core//networkErrorDialogView.html'
};
});

View File

@@ -0,0 +1,5 @@
<modal id="networkError" status="danger" icon="exclamation-sign" title="{{'Connection Error' | translate}}">
<p translate>
Syncthing seems to be down, or there is a problem with your Internet connection. Retrying&hellip;
</p>
</modal>

View File

@@ -0,0 +1,9 @@
angular.module('syncthing.core')
.directive('popover', function () {
return {
restrict: 'A',
link: function (scope, element, attributes) {
$(element).popover();
}
};
});

View File

@@ -0,0 +1,7 @@
angular.module('syncthing.core')
.directive('restartingDialog', function () {
return {
restrict: 'A',
templateUrl: 'syncthing/core//restartingDialogView.html'
};
});

View File

@@ -0,0 +1,3 @@
<modal id="restarting" icon="refresh" title="{{'Restarting' | translate}}" status="info">
<p><span translate>Syncthing is restarting.</span> <span translate>Please wait</span>...</p>
</modal>

View File

@@ -0,0 +1,14 @@
angular.module('syncthing.core')
.directive('selectOnClick', function ($window) {
return {
link: function (scope, element, attrs) {
element.on('click', function() {
var selection = $window.getSelection();
var range = document.createRange();
range.selectNodeContents(element[0]);
selection.removeAllRanges();
selection.addRange(range);
});
}
};
});

View File

@@ -0,0 +1,7 @@
angular.module('syncthing.core')
.directive('shutdownDialog', function () {
return {
restrict: 'A',
templateUrl: 'syncthing/core//shutdownDialogView.html'
};
});

View File

@@ -0,0 +1,3 @@
<modal id="shutdown" icon="off" status="success" title="{{'Shutdown Complete' | translate}}">
<p translate>Syncthing has been shut down.</p>
</modal>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
angular.module('syncthing.core')
.directive('uniqueFolder', function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function (viewValue) {
if (scope.editingExisting) {
// we shouldn't validate
ctrl.$setValidity('uniqueFolder', true);
} else if (scope.folders.hasOwnProperty(viewValue)) {
// the folder exists already
ctrl.$setValidity('uniqueFolder', false);
} else {
// the folder is unique
ctrl.$setValidity('uniqueFolder', true);
}
return viewValue;
});
}
};
});

View File

@@ -0,0 +1,7 @@
angular.module('syncthing.core')
.directive('upgradingDialog', function () {
return {
restrict: 'A',
templateUrl: 'syncthing/core//upgradingDialogView.html'
};
});

View File

@@ -0,0 +1,3 @@
<modal id="upgrading" icon="refresh" title="{{'Upgrading' | translate}}" status="info">
<p><span translate>Syncthing is upgrading.</span> <span translate>Please wait</span>...</p>
</modal>

View File

@@ -0,0 +1,23 @@
angular.module('syncthing.core')
.directive('validDeviceid', function ($http) {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function (viewValue) {
if (scope.editingExisting) {
// we shouldn't validate
ctrl.$setValidity('validDeviceid', true);
} else {
$http.get(urlbase + '/svc/deviceid?id=' + viewValue).success(function (resp) {
if (resp.error) {
ctrl.$setValidity('validDeviceid', false);
} else {
ctrl.$setValidity('validDeviceid', true);
}
});
}
return viewValue;
});
}
};
});