1
0
Fork 0
mirror of https://github.com/nextcloud/calendar.git synced 2024-10-07 16:40:09 +02:00

timezone select (#743)

* add config option for timezone to SettingsController

Signed-off-by: Georg Ehrke <developer@georgehrke.com>

* add timezone config to SettingsService

Signed-off-by: Georg Ehrke <developer@georgehrke.com>

* add timezone input in settings area

Signed-off-by: Georg Ehrke <developer@georgehrke.com>

* connect timezone setting to actual calendar grid

Signed-off-by: Georg Ehrke <developer@georgehrke.com>
This commit is contained in:
Georg Ehrke 2018-03-05 15:47:35 +01:00 committed by GitHub
parent 3ac45b2f04
commit 28dc8bf407
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 256 additions and 48 deletions

View file

@ -77,6 +77,8 @@ class SettingsController extends Controller {
return $this->getShowWeekNr();
case 'firstRun':
return $this->getFirstRun();
case 'timezone':
return $this->getTimezone();
default:
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
}
@ -101,6 +103,8 @@ class SettingsController extends Controller {
return $this->setShowWeekNr($value);
case 'firstRun':
return $this->setFirstRun();
case 'timezone':
return $this->setTimezone($value);
default:
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
}
@ -336,4 +340,47 @@ class SettingsController extends Controller {
'value' => $value,
]);
}
/**
* sets display timezone for user
*
* @param string $value
* @return JSONResponse
*/
private function setTimezone($value) {
try {
$this->config->setUserValue(
$this->userId,
$this->appName,
'timezone',
$value
);
} catch(\Exception $e) {
return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
}
return new JSONResponse();
}
/**
* gets display timezone for user
*
* @return JSONResponse
*/
private function getTimezone() {
try {
$value = $this->config->getUserValue(
$this->userId,
$this->appName,
'timezone',
'automatic'
);
} catch(\Exception $e) {
return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
}
return new JSONResponse([
'value' => $value,
]);
}
}

View file

@ -81,6 +81,7 @@ class ViewController extends Controller {
$skipPopover = $this->config->getUserValue($userId, $this->appName, 'skipPopover', 'no');
$weekNumbers = $this->config->getUserValue($userId, $this->appName, 'showWeekNr', 'no');
$firstRun = $this->config->getUserValue($userId, $this->appName, 'firstRun', null);
$timezone = $this->config->getUserValue($userId, $this->appName, 'timezone', 'automatic');
// the default view will be saved as soon as a user
// opens the calendar app, therefore this is a good
@ -107,6 +108,7 @@ class ViewController extends Controller {
'isPublic' => false,
'isEmbedded' => false,
'token' => '',
'timezone' => $timezone,
]));
}
@ -198,6 +200,7 @@ class ViewController extends Controller {
'shareeCanEditShares' => $shareeCanEditShares ? 'yes' : 'no',
'shareeCanEditCalendarProperties' => $shareeCanEditCalendarProperties ? 'yes' : 'no',
'canSharePublicLink' => $canSharePublicLink,
'timezone' => 'automatic',
];
}
@ -234,6 +237,7 @@ class ViewController extends Controller {
'webcalURL' => $webcalUrl,
'downloadURL' => $downloadUrl,
'token' => $token,
'timezone' => 'automatic',
];
}
}

View file

@ -21,17 +21,13 @@
*
*/
#app-settings .settings-fieldset .settings-fieldset-interior input:not([type="checkbox"]) {
width: 90%;
}
#app-settings .settings-fieldset .settings-fieldset-interior input:not([type="checkbox"]),
#app-settings .settings-fieldset .settings-fieldset-interior select {
width: 40%;
width: 90%;
}
#app-settings .settings-fieldset .settings-upload {
position: absolute;
top: 12.5px;
width: 100%;
display: block;
height: 33px;
@ -54,12 +50,11 @@
#app-settings .settings-fieldset .settings-fieldset-interior-upload {
width: 95%;
height: 55px;
height: 40px;
}
#app-settings .settings-fieldset .settings-fieldset-interior-upload input {
position: absolute;
top: 18px;
}
#app-settings .settings-fieldset .settings-fieldset-interior-inline-label {
@ -67,10 +62,8 @@
}
#app-settings .settings-fieldset .settings-fieldset-interior input[type="file"] {
top: 12.5px;
position: absolute;
width: 100%;
height: 33.21569px;
height: 33px;
padding: 0;
font-size: 16px;
margin-left: 0;

View file

@ -26,18 +26,23 @@
* Description: The fullcalendar controller.
*/
app.controller('CalController', ['$scope', 'Calendar', 'CalendarService', 'VEventService', 'SettingsService', 'TimezoneService', 'VEvent', 'is', 'fc', 'EventsEditorDialogService', 'PopoverPositioningUtility', '$window', 'isPublic', 'constants',
function ($scope, Calendar, CalendarService, VEventService, SettingsService, TimezoneService, VEvent, is, fc, EventsEditorDialogService, PopoverPositioningUtility, $window, isPublic, constants) {
app.controller('CalController', ['$scope', 'Calendar', 'CalendarService', 'VEventService', 'SettingsService', 'TimezoneService', 'VEvent', 'is', 'fc', 'EventsEditorDialogService', 'PopoverPositioningUtility', '$window', 'isPublic', 'constants', 'settings',
function ($scope, Calendar, CalendarService, VEventService, SettingsService, TimezoneService, VEvent, is, fc, EventsEditorDialogService, PopoverPositioningUtility, $window, isPublic, constants, settings) {
'use strict';
is.loading = true;
$scope.calendars = [];
$scope.eventSource = {};
$scope.defaulttimezone = TimezoneService.current();
$scope.eventModal = null;
var switcher = [];
if (settings.timezone === 'automatic') {
$scope.defaulttimezone = TimezoneService.getDetected();
} else {
$scope.defaulttimezone = settings.timezone;
}
function showCalendar(url) {
if (switcher.indexOf(url) === -1 && $scope.eventSource[url].isRendering === false) {
switcher.push(url);

View file

@ -26,14 +26,50 @@
* Description: Takes care of the Calendar Settings.
*/
app.controller('SettingsController', ['$scope', '$uibModal', '$timeout', 'SettingsService', 'fc', 'isFirstRun', 'settings',
function ($scope, $uibModal, $timeout, SettingsService, fc, isFirstRun, settings) {
app.controller('SettingsController', ['$scope', '$uibModal', '$timeout', 'TimezoneService', 'SettingsService', 'fc', 'isFirstRun', 'settings',
function ($scope, $uibModal, $timeout, TimezoneService, SettingsService, fc, isFirstRun, settings) {
'use strict';
$scope.settingsCalDavLink = OC.linkToRemote('dav') + '/';
$scope.settingsCalDavPrincipalLink = OC.linkToRemote('dav') + '/principals/users/' + escapeHTML(encodeURIComponent(oc_current_user)) + '/';
$scope.skipPopover = settings.skipPopover ? 'yes' : 'no';
$scope.settingsShowWeekNr = settings.showWeekNr ? 'yes' : 'no';
$scope.timezone = settings.timezone;
$scope.timezones = [];
TimezoneService.listAll().then((timezones) => {
$scope.timezones.push({
displayname: t('calendar', 'Automatic ({timezone})', {
timezone: TimezoneService.getDetected()
}),
value: 'automatic'
});
timezones.forEach((timezone) => {
if (timezone.split('/').length === 1) {
$scope.timezones.push({
displayname: timezone,
group: t('calendar', 'Global'),
value: timezone
});
} else {
$scope.timezones.push({
displayname: timezone.split('/').slice(1).join('/'),
group: timezone.split('/', 1),
value: timezone
});
}
});
});
$scope.setTimezone = () => {
SettingsService.setTimezone($scope.timezone);
settings.timezone = $scope.timezone;
if (fc.elm) {
fc.elm.fullCalendar('option', 'timezone', settings.timezone);
}
};
$timeout(() => {
if (isFirstRun) {

View file

@ -46,8 +46,8 @@ app.factory('FcEvent', function(SimpleEvent) {
_isAFcEventObject: true,
id: id,
allDay: allDay,
start: start.toJSDate(),
end: end.toJSDate(),
start: moment(start.toString()),
end: moment(end.toString()),
repeating: context.iCalEvent.isRecurring(),
className: ['fcCalendar-id-' + vevent.calendar.tmpId],
editable: vevent.calendar.isWritable(),

View file

@ -104,4 +104,27 @@ app.service('SettingsService', ['$rootScope', '$http', function($rootScope, $htt
return true;
});
};
this.getTimezone = function() {
return $http({
method: 'GET',
url: $rootScope.baseUrl + 'config',
params: {key: 'timezone'}
}).then(function(response) {
return response.data.value;
});
};
this.setTimezone = function(value) {
return $http({
method: 'POST',
url: $rootScope.baseUrl + 'config',
data: {
key: 'timezone',
value: value
}
}).then(function() {
return true;
});
};
}]);

View file

@ -38,22 +38,6 @@ app.service('TimezoneService', function (TimezoneDataProvider, Timezone) {
return hasSlash && !hasSpace && !startsWithETC && !startsWithUS;
};
/**
* get the browser's timezone id
* @returns {string}
*/
this.current = function () {
const tz = jstz.determine();
let tzname = tz ? tz.name() : 'UTC';
if (TimezoneDataProvider.aliases[tzname]) {
return TimezoneDataProvider.aliases[tzname].aliasTo;
}
return tzname;
};
/**
* get a timezone object by it's id
* @param tzid
@ -79,6 +63,21 @@ app.service('TimezoneService', function (TimezoneDataProvider, Timezone) {
return Promise.resolve(new Timezone(ics));
};
/**
* get timezone detected by jstz
* @return {string}
*/
this.getDetected = () => {
const tz = jstz.determine();
let tzname = tz ? tz.name() : 'UTC';
if (TimezoneDataProvider.aliases[tzname]) {
return TimezoneDataProvider.aliases[tzname].aliasTo;
}
return tzname;
};
/**
* list all timezone ids
* @returns {Promise}

View file

@ -62,7 +62,8 @@ app.config(['$provide', '$httpProvider',
const skipPopover = angular.element('#fullcalendar').attr('data-skipPopover') === 'yes';
const showWeekNr = angular.element('#fullcalendar').attr('data-weekNumbers') === 'yes';
$provide.constant('settings', {skipPopover, showWeekNr});
const timezone = angular.element('#fullcalendar').attr('data-timezone');
$provide.constant('settings', {skipPopover, showWeekNr, timezone});
const initialView = angular.element('#fullcalendar').attr('data-initialView');
const emailAddress = angular.element('#fullcalendar').attr('data-emailAddress');

View file

@ -37,6 +37,7 @@
data-shareeCanEditShares="<?php p($_['shareeCanEditShares']); ?>"
data-shareeCanEditCalendarProperties="<?php p($_['shareeCanEditCalendarProperties']); ?>"
data-canSharePublicLink="<?php p($_['canSharePublicLink']); ?>"
data-timezone="<?php p($_['timezone']); ?>"
fc
id="fullcalendar">
</div>

View file

@ -35,6 +35,11 @@
<div id="app-settings-content">
<fieldset class="settings-fieldset">
<ul class="settings-fieldset-interior">
<li class="settings-fieldset-interior-item settings-fieldset-interior-upload">
<input type="file" name="file" accept="text/calendar" multiple id="import" />
<span href="#" class="button settings-upload svg icon-upload" role="button" id="import-button-overlay"><?php p($l->t('Import calendar')); ?></span>
<span ng-show="!files.length" class="hide"><?php p($l->t('No Calendars selected for import')); ?></span>
</li>
<li class="settings-fieldset-interior-item">
<input class="checkbox" type="checkbox" ng-change="updateSkipPopover()" ng-model="skipPopover" ng-true-value="'yes'" ng-false-value="'no'" id="skip_popover_checkbox"/>
<label for="skip_popover_checkbox">
@ -47,12 +52,11 @@
<?php p($l->t('Show week numbers')); ?>
</label>
</li>
<li class="settings-fieldset-interior-item settings-fieldset-interior-upload">
<input type="file" name="file" accept="text/calendar" multiple id="import" />
<span href="#" class="button settings-upload svg icon-upload" role="button" id="import-button-overlay"><?php p($l->t('Import calendar')); ?></span>
<span ng-show="!files.length" class="hide"><?php p($l->t('No Calendars selected for import')); ?></span>
<li class="settings-fieldset-interior-item">
<label class="settings-input"><?php p($l->t('Timezone')); ?></label>
<select ng-options="timezone.value as timezone.displayname | timezoneWithoutContinentFilter group by timezone.group for timezone in timezones"
ng-model="timezone" ng-change="setTimezone()" class="input settings-input"></select>
</li>
<li class="settings-fieldset-interior-item">
<label class="settings-input"><?php p($l->t('Primary CalDAV address')); ?></label>
<input class="input settings-input" type="text" ng-model="settingsCalDavLink" readonly />

View file

@ -260,8 +260,8 @@ END:VTIMEZONE`)));
expect(fcEvent.id).toEqual('fancy1337');
expect(fcEvent.allDay).toEqual(false);
expect(fcEvent.start.toString()).toEqual(start.toJSDate().toString());
expect(fcEvent.end.toString()).toEqual(end.toJSDate().toString());
expect(fcEvent.start.toString()).toEqual(moment(start.toString()).toString());
expect(fcEvent.end.toString()).toEqual(moment(end.toString()).toString());
expect(fcEvent.repeating).toEqual(false);
expect(fcEvent.backgroundColor).toEqual('#000');
expect(fcEvent.borderColor).toEqual('#000');

View file

@ -98,4 +98,27 @@ describe('Settings Service', function () {
expect(http.flush).not.toThrow();
expect(called).toEqual(true);
});
it ('should set the timezone value', function() {
http.expect('POST', 'fancy-url/config', {
'key': 'timezone',
'value': 'Europe/Berlin'
}).respond(200, {});
SettingsService.setTimezone('Europe/Berlin').then(function(result) {
expect(result).toBe(true);
});
expect(http.flush).not.toThrow();
});
it ('should get the timezone value', function() {
http.expect('GET', 'fancy-url/config?key=timezone').respond(200, {value: 'Europe/Berlin'});
SettingsService.getTimezone().then(function(result) {
expect(result).toEqual('Europe/Berlin');
});
expect(http.flush).not.toThrow();
});
});

View file

@ -77,9 +77,9 @@ describe('Timezone Service', function () {
jstz.determine = jasmine.createSpy().and.returnValues(
{name: () => 'Europe/Berlin'}, {name: () => 'UTC'}, {name: () => 'Etc/UTC'});
expect(TimezoneService.current()).toEqual('Europe/Berlin');
expect(TimezoneService.current()).toEqual('UTC');
expect(TimezoneService.current()).toEqual('UTC');
expect(TimezoneService.getDetected()).toEqual('Europe/Berlin');
expect(TimezoneService.getDetected()).toEqual('UTC');
expect(TimezoneService.getDetected()).toEqual('UTC');
});
it('should get a timezone', function() {

View file

@ -250,6 +250,57 @@ class SettingsControllerTest extends \PHPUnit_Framework_TestCase {
$this->assertEquals(500, $actual->getStatus());
}
public function testSetTimezone() {
$this->config->expects($this->once())
->method('setUserValue')
->with('user123', $this->appName, 'timezone', 'Europe/Berlin');
$actual = $this->controller->setConfig('timezone', 'Europe/Berlin');
$this->assertInstanceOf('OCP\AppFramework\Http\JSONResponse', $actual);
$this->assertEquals([], $actual->getData());
$this->assertEquals(200, $actual->getStatus());
}
public function testSetTimezoneWithException() {
$this->config->expects($this->once())
->method('setUserValue')
->with('user123', $this->appName, 'timezone', 'Europe/Berlin')
->will($this->throwException(new \Exception));
$actual = $this->controller->setConfig('timezone', 'Europe/Berlin');
$this->assertInstanceOf('OCP\AppFramework\Http\JSONResponse', $actual);
$this->assertEquals([], $actual->getData());
$this->assertEquals(500, $actual->getStatus());
}
public function testGetTimezone() {
$this->config->expects($this->once())
->method('getUserValue')
->with('user123', $this->appName, 'timezone', 'automatic')
->will($this->returnValue('Europe/Berlin'));
$actual = $this->controller->getConfig('timezone');
$this->assertInstanceOf('OCP\AppFramework\Http\JSONResponse', $actual);
$this->assertEquals(['value' => 'Europe/Berlin'], $actual->getData());
$this->assertEquals(200, $actual->getStatus());
}
public function testGetTimezoneWithException() {
$this->config->expects($this->once())
->method('getUserValue')
->with('user123', $this->appName, 'timezone', 'automatic')
->will($this->throwException(new \Exception));
$actual = $this->controller->getConfig('timezone');
$this->assertInstanceOf('OCP\AppFramework\Http\JSONResponse', $actual);
$this->assertEquals([], $actual->getData());
$this->assertEquals(500, $actual->getStatus());
}
public function testGetNotExistingConfig() {
$actual = $this->controller->getConfig('foo');

View file

@ -119,6 +119,11 @@ class ViewControllerTest extends \PHPUnit_Framework_TestCase {
->with('user123', $this->appName, 'firstRun', null)
->will($this->returnValue('someFirstRunValue'));
$this->config->expects($this->at(8))
->method('getUserValue')
->with('user123', $this->appName, 'timezone', 'automatic')
->will($this->returnValue('Australia/Adelaide'));
$actual = $this->controller->index();
$this->assertInstanceOf('OCP\AppFramework\Http\TemplateResponse', $actual);
@ -137,6 +142,7 @@ class ViewControllerTest extends \PHPUnit_Framework_TestCase {
'token' => '',
'shareeCanEditShares' => $shareeActions,
'shareeCanEditCalendarProperties' => $shareeCanEdit,
'timezone' => 'Australia/Adelaide',
], $actual->getParams());
$this->assertEquals('main', $actual->getTemplateName());
}
@ -208,6 +214,11 @@ class ViewControllerTest extends \PHPUnit_Framework_TestCase {
->with('user123', $this->appName, 'firstRun', null)
->will($this->returnValue('someFirstRunValue'));
$this->config->expects($this->at(8))
->method('getUserValue')
->with('user123', $this->appName, 'timezone', 'automatic')
->will($this->returnValue('Australia/Adelaide'));
$actual = $this->controller->index();
$this->assertInstanceOf('OCP\AppFramework\Http\TemplateResponse', $actual);
@ -225,7 +236,8 @@ class ViewControllerTest extends \PHPUnit_Framework_TestCase {
'token' => '',
'shareeCanEditShares' => 'no',
'shareeCanEditCalendarProperties' => 'yes',
'canSharePublicLink' => 'no'
'canSharePublicLink' => 'no',
'timezone' => 'Australia/Adelaide',
], $actual->getParams());
$this->assertEquals('main', $actual->getTemplateName());
}
@ -291,8 +303,14 @@ class ViewControllerTest extends \PHPUnit_Framework_TestCase {
->with('user123', $this->appName, 'firstRun', null)
->will($this->returnValue(null));
$this->config->expects($this->at(8))
->method('getUserValue')
->with('user123', $this->appName, 'timezone', 'automatic')
->will($this->returnValue('Australia/Adelaide'));
if ($expectsSetRequest) {
$this->config->expects($this->at(8))
$this->config->expects($this->at(9))
->method('setUserValue')
->with('user123');
}
@ -314,7 +332,8 @@ class ViewControllerTest extends \PHPUnit_Framework_TestCase {
'token' => '',
'shareeCanEditShares' => 'yes',
'shareeCanEditCalendarProperties' => 'no',
'canSharePublicLink' => 'no'
'canSharePublicLink' => 'no',
'timezone' => 'Australia/Adelaide',
], $actual->getParams());
$this->assertEquals('main', $actual->getTemplateName());
}
@ -413,6 +432,7 @@ class ViewControllerTest extends \PHPUnit_Framework_TestCase {
'shareeCanEditShares' => $shareeActions,
'shareeCanEditCalendarProperties' => $shareeCanEdit,
'canSharePublicLink' => 'no',
'timezone' => 'automatic',
], $actual->getParams());
$this->assertEquals('main', $actual->getTemplateName());
}
@ -504,6 +524,7 @@ class ViewControllerTest extends \PHPUnit_Framework_TestCase {
'shareeCanEditShares' => $shareeActions,
'shareeCanEditCalendarProperties' => $shareeCanEdit,
'canSharePublicLink' => 'no',
'timezone' => 'automatic',
], $actual->getParams());
$this->assertEquals('public', $actual->getTemplateName());
}