Calendar/Date localization

New: Choose calendar starting day of week
New: Choose prefered date/time formats
New: Option to disable relative dates and show absolute dates instead
This commit is contained in:
Mark McDowall 2014-08-04 22:44:09 -07:00
parent 0ba19f0cd7
commit 18874e2c79
32 changed files with 4049 additions and 1808 deletions

View File

@ -0,0 +1,45 @@
using System.Linq;
using System.Reflection;
using NzbDrone.Core.Configuration;
using Omu.ValueInjecter;
namespace NzbDrone.Api.Config
{
public class UiConfigModule : NzbDroneRestModule<UiConfigResource>
{
private readonly IConfigService _configService;
public UiConfigModule(IConfigService configService)
: base("/config/ui")
{
_configService = configService;
GetResourceSingle = GetUiConfig;
GetResourceById = GetUiConfig;
UpdateResource = SaveUiConfig;
}
private UiConfigResource GetUiConfig()
{
var resource = new UiConfigResource();
resource.InjectFrom(_configService);
resource.Id = 1;
return resource;
}
private UiConfigResource GetUiConfig(int id)
{
return GetUiConfig();
}
private void SaveUiConfig(UiConfigResource resource)
{
var dictionary = resource.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.ToDictionary(prop => prop.Name, prop => prop.GetValue(resource, null));
_configService.SaveConfigDictionary(dictionary);
}
}
}

View File

@ -0,0 +1,18 @@
using System;
using NzbDrone.Api.REST;
namespace NzbDrone.Api.Config
{
public class UiConfigResource : RestResource
{
//Calendar
public Int32 FirstDayOfWeek { get; set; }
public String CalendarWeekColumnHeader { get; set; }
//Dates
public String ShortDateFormat { get; set; }
public String LongDateFormat { get; set; }
public String TimeFormat { get; set; }
public Boolean ShowRelativeDates { get; set; }
}
}

View File

@ -92,6 +92,8 @@
<Compile Include="ClientSchema\SelectOption.cs" />
<Compile Include="Commands\CommandModule.cs" />
<Compile Include="Commands\CommandResource.cs" />
<Compile Include="Config\UiConfigModule.cs" />
<Compile Include="Config\UiConfigResource.cs" />
<Compile Include="Config\DownloadClientConfigModule.cs" />
<Compile Include="Config\DownloadClientConfigResource.cs" />
<Compile Include="Config\HostConfigModule.cs" />

View File

@ -3,11 +3,9 @@ using Nancy.Routing;
using NzbDrone.Common;
using NzbDrone.Api.Extensions;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Processes;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Lifecycle.Commands;
namespace NzbDrone.Api.System
{
@ -60,7 +58,6 @@ namespace NzbDrone.Api.System
IsWindows = OsInfo.IsWindows,
Branch = _configFileProvider.Branch,
Authentication = _configFileProvider.AuthenticationEnabled,
StartOfWeek = (int)OsInfo.FirstDayOfWeek,
SqliteVersion = _database.Version,
UrlBase = _configFileProvider.UrlBase,
RuntimeVersion = OsInfo.IsMono ? _runtimeInfo.RuntimeVersion : null

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Messaging.Events;
@ -254,6 +255,48 @@ namespace NzbDrone.Core.Configuration
set { SetValue("ChownGroup", value); }
}
public Int32 FirstDayOfWeek
{
get { return GetValueInt("FirstDayOfWeek", (int)OsInfo.FirstDayOfWeek); }
set { SetValue("FirstDayOfWeek", value); }
}
public String CalendarWeekColumnHeader
{
get { return GetValue("CalendarWeekColumnHeader", "ddd M/D"); }
set { SetValue("CalendarWeekColumnHeader", value); }
}
public String ShortDateFormat
{
get { return GetValue("ShortDateFormat", "MMM D YYYY"); }
set { SetValue("ShortDateFormat", value); }
}
public String LongDateFormat
{
get { return GetValue("LongDateFormat", "dddd, MMMM D YYYY"); }
set { SetValue("LongDateFormat", value); }
}
public String TimeFormat
{
get { return GetValue("TimeFormat", "h(:mm)a"); }
set { SetValue("TimeFormat", value); }
}
public Boolean ShowRelativeDates
{
get { return GetValueBoolean("ShowRelativeDates", true); }
set { SetValue("ShowRelativeDates", value); }
}
private string GetValue(string key)
{
return GetValue(key, String.Empty);

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Update;
namespace NzbDrone.Core.Configuration
{
@ -49,5 +48,14 @@ namespace NzbDrone.Core.Configuration
Int32 Retention { get; set; }
Int32 RssSyncInterval { get; set; }
String ReleaseRestrictions { get; set; }
//UI
Int32 FirstDayOfWeek { get; set; }
String CalendarWeekColumnHeader { get; set; }
String ShortDateFormat { get; set; }
String LongDateFormat { get; set; }
String TimeFormat { get; set; }
Boolean ShowRelativeDates { get; set; }
}
}

View File

@ -7,13 +7,13 @@ define(
'marionette',
'moment',
'Calendar/Collection',
'System/StatusModel',
'Shared/UiSettingsModel',
'History/Queue/QueueCollection',
'Config',
'Mixins/backbone.signalr.mixin',
'fullcalendar',
'jquery.easypiechart'
], function ($, vent, Marionette, moment, CalendarCollection, StatusModel, QueueCollection, Config) {
], function ($, vent, Marionette, moment, CalendarCollection, UiSettings, QueueCollection, Config) {
return Marionette.ItemView.extend({
storageKey: 'calendar.view',
@ -44,39 +44,45 @@ define(
this.$(element).addClass(event.statusLevel);
this.$(element).children('.fc-event-inner').addClass(event.statusLevel);
if (event.progress > 0) {
this.$(element).find('.fc-event-time')
.after('<span class="chart pull-right" data-percent="{0}"></span>'.format(event.progress));
if (event.downloading) {
var progress = 100 - (event.downloading.get('sizeleft') / event.downloading.get('size') * 100);
var releaseTitle = event.downloading.get('title');
var estimatedCompletionTime = moment(event.downloading.get('estimatedCompletionTime')).fromNow();
this.$(element).find('.chart').easyPieChart({
barColor : '#ffffff',
trackColor: false,
scaleColor: false,
lineWidth : 2,
size : 14,
animate : false
});
if (event.downloading.get('status').toLocaleLowerCase() === 'pending') {
this.$(element).find('.fc-event-time')
.after('<span class="pending pull-right"><i class="icon-time"></i></span>');
this.$(element).find('.chart').tooltip({
title: 'Episode is downloading - {0}% {1}'.format(event.progress.toFixed(1), event.releaseTitle),
container: 'body'
});
}
this.$(element).find('.pending').tooltip({
title: 'Release will be processed {0}'.format(estimatedCompletionTime),
container: 'body'
});
}
if (event.pending) {
this.$(element).find('.fc-event-time')
.after('<span class="pending pull-right"><i class="icon-time"></i></span>');
else {
this.$(element).find('.fc-event-time')
.after('<span class="chart pull-right" data-percent="{0}"></span>'.format(progress));
this.$(element).find('.pending').tooltip({
title: 'Release will be processed {0}'.format(event.pending),
container: 'body'
});
this.$(element).find('.chart').easyPieChart({
barColor : '#ffffff',
trackColor: false,
scaleColor: false,
lineWidth : 2,
size : 14,
animate : false
});
this.$(element).find('.chart').tooltip({
title: 'Episode is downloading - {0}% {1}'.format(progress.toFixed(1), releaseTitle),
container: 'body'
});
}
}
},
_getEvents: function (view) {
var start = moment(view.visStart).toISOString();
var end = moment(view.visEnd).toISOString();
var start = view.start.toISOString();
var end = view.end.toISOString();
this.$el.fullCalendar('removeEvents');
@ -99,13 +105,10 @@ define(
var event = {
title : seriesTitle,
start : start,
end : end,
start : moment(start),
end : moment(end),
allDay : false,
statusLevel : self._getStatusLevel(model, end),
progress : self._getDownloadProgress(model),
pending : self._getPendingInfo(model),
releaseTitle: self._getReleaseTitle(model),
downloading : QueueCollection.findEpisode(model.get('id')),
model : model
};
@ -153,47 +156,12 @@ define(
this._setEventData(this.collection);
},
_getDownloadProgress: function (element) {
var downloading = QueueCollection.findEpisode(element.get('id'));
if (!downloading) {
return 0;
}
return 100 - (downloading.get('sizeleft') / downloading.get('size') * 100);
},
_getPendingInfo: function (element) {
var pending = QueueCollection.findEpisode(element.get('id'));
if (!pending || pending.get('status').toLocaleLowerCase() !== 'pending') {
return undefined;
}
return moment(pending.get('estimatedCompletionTime')).fromNow();
},
_getReleaseTitle: function (element) {
var downloading = QueueCollection.findEpisode(element.get('id'));
if (!downloading) {
return '';
}
return downloading.get('title');
},
_getOptions: function () {
var options = {
allDayDefault : false,
ignoreTimezone: false,
weekMode : 'variable',
firstDay : StatusModel.get('startOfWeek'),
timeFormat : 'h(:mm)tt',
buttonText : {
prev: '<i class="icon-arrow-left"></i>',
next: '<i class="icon-arrow-right"></i>'
},
firstDay : UiSettings.get('firstDayOfWeek'),
timeFormat : 'h(:mm)a',
viewRender : this._viewRender.bind(this),
eventRender : this._eventRender.bind(this),
eventClick : function (event) {
@ -204,12 +172,6 @@ define(
if ($(window).width() < 768) {
options.defaultView = Config.getValue(this.storageKey, 'basicDay');
options.titleFormat = {
month: 'MMM yyyy', // September 2009
week: 'MMM d[ yyyy]{ \'&#8212;\'[ MMM] d yyyy}', // Sep 7 - 13 2009
day: 'ddd, MMM d, yyyy' // Tuesday, Sep 8, 2009
};
options.header = {
left : 'prev,next today',
center: 'title',
@ -220,12 +182,6 @@ define(
else {
options.defaultView = Config.getValue(this.storageKey, 'basicWeek');
options.titleFormat = {
month: 'MMM yyyy', // September 2009
week: 'MMM d[ yyyy]{ \'&#8212;\'[ MMM] d yyyy}', // Sep 7 - 13 2009
day: 'dddd, MMM d, yyyy' // Tues, Sep 8, 2009
};
options.header = {
left : 'prev,next today',
center: 'title',
@ -233,6 +189,22 @@ define(
};
}
options.titleFormat = {
month : 'MMMM YYYY',
week : UiSettings.get('shortDateFormat'),
day : UiSettings.get('longDateFormat')
};
options.columnFormat = {
month : 'ddd', // Mon
week : UiSettings.get('calendarWeekColumnHeader'),
day : 'dddd' // Monday
};
options.timeFormat = {
'default': UiSettings.get('timeFormat')
};
return options;
}
});

View File

@ -4,18 +4,18 @@ define(
'backbone',
'moment',
'Series/EpisodeModel'
], function (Backbone, Moment, EpisodeModel) {
], function (Backbone, moment, EpisodeModel) {
return Backbone.Collection.extend({
url : window.NzbDrone.ApiRoot + '/calendar',
model: EpisodeModel,
comparator: function (model1, model2) {
var airDate1 = model1.get('airDateUtc');
var date1 = Moment(airDate1);
var date1 = moment(airDate1);
var time1 = date1.unix();
var airDate2 = model2.get('airDateUtc');
var date2 = Moment(airDate2);
var date2 = moment(airDate2);
var time2 = date2.unix();
if (time1 < time2){

View File

@ -5,7 +5,7 @@ define(
'vent',
'marionette',
'moment'
], function (vent, Marionette, Moment) {
], function (vent, Marionette, moment) {
return Marionette.ItemView.extend({
template: 'Calendar/UpcomingItemViewTemplate',
tagName : 'div',
@ -17,7 +17,7 @@ define(
initialize: function () {
var start = this.model.get('airDateUtc');
var runtime = this.model.get('series').runtime;
var end = Moment(start).add('minutes', runtime);
var end = moment(start).add('minutes', runtime);
this.model.set({
end: end.toISOString()

View File

@ -8,7 +8,7 @@ define(
'History/Queue/QueueCollection',
'moment',
'Shared/FormatHelpers'
], function (reqres, Backbone, NzbDroneCell, QueueCollection, Moment, FormatHelpers) {
], function (reqres, Backbone, NzbDroneCell, QueueCollection, moment, FormatHelpers) {
return NzbDroneCell.extend({
className: 'episode-status-cell',
@ -29,7 +29,7 @@ define(
var icon;
var tooltip;
var hasAired = Moment(this.model.get('airDateUtc')).isBefore(Moment());
var hasAired = moment(this.model.get('airDateUtc')).isBefore(moment());
var hasFile = this.model.get('hasFile');
if (hasFile) {

View File

@ -3,18 +3,32 @@ define(
[
'Cells/NzbDroneCell',
'moment',
'Shared/FormatHelpers'
], function (NzbDroneCell, Moment, FormatHelpers) {
'Shared/FormatHelpers',
'Shared/UiSettingsModel'
], function (NzbDroneCell, moment, FormatHelpers, UiSettings) {
return NzbDroneCell.extend({
className: 'relative-date-cell',
render: function () {
var date = this.model.get(this.column.get('name'));
var dateStr = this.model.get(this.column.get('name'));
if (date) {
this.$el.html('<span title="' + Moment(date).format('LLLL') + '" >' + FormatHelpers.dateHelper(date) + '</span>');
if (dateStr) {
var date = moment(dateStr);
var result = '<span title="{0}">{1}</span>';
var tooltip = date.format(UiSettings.longDateTime());
var text;
if (UiSettings.get('showRelativeDates')) {
text = FormatHelpers.relativeDate(dateStr);
}
else {
text = date.format(UiSettings.get('shortDateFormat'));
}
this.$el.html(result.format(tooltip, text));
}
return this;

View File

@ -1,5 +1,5 @@
/*!
* FullCalendar v1.6.4 Stylesheet
* FullCalendar v2.0.2 Stylesheet
* Docs & License: http://arshaw.com/fullcalendar/
* (c) 2013 Adam Shaw
*/
@ -101,11 +101,14 @@ html .fc,
------------------------------------------------------------------------*/
.fc-content {
position: relative;
z-index: 1; /* scopes all other z-index's to be inside this container */
clear: both;
zoom: 1; /* for IE7, gives accurate coordinates for [un]freezeContentHeight */
}
.fc-view {
position: relative;
width: 100%;
overflow: hidden;
}
@ -165,32 +168,38 @@ html .fc,
and we'll try to make them look good cross-browser.
*/
.fc-text-arrow {
.fc-button .fc-icon {
margin: 0 .1em;
font-size: 2em;
font-family: "Courier New", Courier, monospace;
vertical-align: baseline; /* for IE7 */
}
.fc-button-prev .fc-text-arrow,
.fc-button-next .fc-text-arrow { /* for &lsaquo; &rsaquo; */
.fc-icon-left-single-arrow:after {
content: "\02039";
font-weight: bold;
}
.fc-icon-right-single-arrow:after {
content: "\0203A";
font-weight: bold;
}
.fc-icon-left-double-arrow:after {
content: "\000AB";
}
.fc-icon-right-double-arrow:after {
content: "\000BB";
}
/* icon (for jquery ui) */
.fc-button .fc-icon-wrap {
position: relative;
float: left;
top: 50%;
}
.fc-button .ui-icon {
position: relative;
top: 50%;
float: left;
margin-top: -50%;
*margin-top: 0;
*top: -50%;
margin-top: -8px; /* we know jqui icons are always 16px tall */
}
/*
@ -447,10 +456,13 @@ table.fc-border-separate {
padding: 0 4px;
vertical-align: middle;
text-align: right;
white-space: nowrap;
font-weight: normal;
}
.fc-agenda-slots .fc-agenda-axis {
white-space: nowrap;
}
.fc-agenda .fc-week-number {
font-weight: bold;
}

View File

@ -4,7 +4,7 @@
<span class="label label-info">{{network}}</span>
{{/with}}
<span class="label label-info">{{StartTime airDateUtc}}</span>
<span class="label label-info">{{NextAiring airDateUtc}}</span>
<span class="label label-info">{{RelativeDate airDateUtc}}</span>
</div>
<div class="episode-overview">

View File

@ -3,26 +3,39 @@ define(
[
'handlebars',
'moment',
'Shared/FormatHelpers'
], function (Handlebars, Moment, FormatHelpers) {
'Shared/FormatHelpers',
'Shared/UiSettingsModel'
], function (Handlebars, moment, FormatHelpers, UiSettings) {
Handlebars.registerHelper('ShortDate', function (input) {
if (!input) {
return '';
}
var date = Moment(input);
var result = '<span title="' + date.format('LLLL') + '">' + date.format('LL') + '</span>';
var date = moment(input);
var result = '<span title="' + date.format(UiSettings.longDateTime()) + '">' + date.format(UiSettings.get('shortDateFormat')) + '</span>';
return new Handlebars.SafeString(result);
});
Handlebars.registerHelper('NextAiring', function (input) {
Handlebars.registerHelper('RelativeDate', function (input) {
if (!input) {
return '';
}
var date = Moment(input);
var result = '<span title="' + date.format('LLLL') + '">' + FormatHelpers.dateHelper(input) + '</span>';
var date = moment(input);
var result = '<span title="{0}">{1}</span>';
var tooltip = date.format(UiSettings.longDateTime());
var text;
if (UiSettings.get('showRelativeDates')) {
text = FormatHelpers.relativeDate(input);
}
else {
text = date.format(UiSettings.get('shortDateFormat'));
}
result = result.format(tooltip, text);
return new Handlebars.SafeString(result);
});
@ -32,7 +45,7 @@ define(
return '';
}
return Moment(input).format('DD');
return moment(input).format('DD');
});
Handlebars.registerHelper('Month', function (input) {
@ -40,7 +53,7 @@ define(
return '';
}
return Moment(input).format('MMM');
return moment(input).format('MMM');
});
Handlebars.registerHelper('StartTime', function (input) {
@ -48,11 +61,11 @@ define(
return '';
}
var date = Moment(input);
var date = moment(input);
if (date.format('mm') === '00') {
return date.format('ha');
}
return date.format('h.mma');
return date.format('h:mma');
});
});

View File

@ -4,11 +4,11 @@ define(
'handlebars',
'Shared/FormatHelpers',
'moment'
], function (Handlebars, FormatHelpers, Moment) {
], function (Handlebars, FormatHelpers, moment) {
Handlebars.registerHelper('EpisodeNumber', function () {
if (this.series.seriesType === 'daily') {
return Moment(this.airDate).format('L');
return moment(this.airDate).format('L');
}
else {
@ -21,9 +21,9 @@ define(
var hasFile = this.hasFile;
var downloading = require('History/Queue/QueueCollection').findEpisode(this.id) || this.downloading;
var currentTime = Moment();
var start = Moment(this.airDateUtc);
var end = Moment(this.end);
var currentTime = moment();
var start = moment(this.airDateUtc);
var end = moment(this.end);
if (hasFile) {
return 'success';

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,7 @@ define(
EpisodeNumberCell,
EpisodeWarningCell,
CommandController,
Moment,
moment,
_,
Messenger) {
return Marionette.Layout.extend({
@ -213,15 +213,15 @@ define(
},
_shouldShowEpisodes: function () {
var startDate = Moment().add('month', -1);
var endDate = Moment().add('year', 1);
var startDate = moment().add('month', -1);
var endDate = moment().add('year', 1);
return this.episodeCollection.some(function (episode) {
var airDate = episode.get('airDateUtc');
if (airDate) {
var airDateMoment = Moment(airDate);
var airDateMoment = moment(airDate);
if (airDateMoment.isAfter(startDate) && airDateMoment.isBefore(endDate)) {
return true;
@ -235,7 +235,7 @@ define(
templateHelpers: function () {
var episodeCount = this.episodeCollection.filter(function (episode) {
return episode.get('hasFile') || (episode.get('monitored') && Moment(episode.get('airDateUtc')).isBefore(Moment()));
return episode.get('hasFile') || (episode.get('monitored') && moment(episode.get('airDateUtc')).isBefore(moment()));
}).length;
var episodeFileCount = this.episodeCollection.where({ hasFile: true }).length;

View File

@ -37,7 +37,7 @@
<div class="col-md-10 col-xs-8">
{{#if_eq status compare="continuing"}}
{{#if nextAiring}}
<span class="label label-default">{{NextAiring nextAiring}}</span>
<span class="label label-default">{{RelativeDate nextAiring}}</span>
{{/if}}
{{else}}
<span class="label label-danger">Ended</span>

View File

@ -22,7 +22,7 @@
<div class="labels">
{{#if_eq status compare="continuing"}}
{{#if nextAiring}}
<span class="label label-default">{{NextAiring nextAiring}}</span>
<span class="label label-default">{{RelativeDate nextAiring}}</span>
{{/if}}
{{/if_eq}}
{{> EpisodeProgressPartial }}

View File

@ -65,9 +65,9 @@ define(
cell : 'integer'
},
{
name : 'profileId',
label: 'Profile',
cell : ProfileCell
name : 'profileId',
label : 'Profile',
cell : ProfileCell
},
{
name : 'network',

View File

@ -10,7 +10,7 @@ define(
'Mixins/AsSortedCollection',
'Mixins/AsPersistedStateCollection',
'moment'
], function (_, Backbone, PageableCollection, SeriesModel, SeriesData, AsFilteredCollection, AsSortedCollection, AsPersistedStateCollection, Moment) {
], function (_, Backbone, PageableCollection, SeriesModel, SeriesData, AsFilteredCollection, AsSortedCollection, AsPersistedStateCollection, moment) {
var Collection = PageableCollection.extend({
url : window.NzbDrone.ApiRoot + '/series',
model: SeriesModel,
@ -63,13 +63,13 @@ define(
var nextAiring = model.get(attr);
if (nextAiring) {
return Moment(nextAiring).unix();
return moment(nextAiring).unix();
}
var previousAiring = model.get(attr.replace('nextAiring', 'previousAiring'));
if (previousAiring) {
return 10000000000 - Moment(previousAiring).unix();
return 10000000000 - moment(previousAiring).unix();
}
return Number.MAX_VALUE;

View File

@ -21,6 +21,8 @@ define(
'Settings/Notifications/NotificationCollection',
'Settings/Metadata/MetadataLayout',
'Settings/General/GeneralView',
'Settings/UI/UiView',
'Settings/UI/UiSettingsModel',
'Shared/LoadingView',
'Config'
], function ($,
@ -43,6 +45,8 @@ define(
NotificationCollection,
MetadataLayout,
GeneralView,
UiView,
UiSettingsModel,
LoadingView,
Config) {
return Marionette.Layout.extend({
@ -57,6 +61,7 @@ define(
notifications : '#notifications',
metadata : '#metadata',
general : '#general',
uiRegion : '#ui',
loading : '#loading-region'
},
@ -69,6 +74,7 @@ define(
notificationsTab : '.x-notifications-tab',
metadataTab : '.x-metadata-tab',
generalTab : '.x-general-tab',
uiTab : '.x-ui-tab',
advancedSettings : '.x-advanced-settings'
},
@ -81,6 +87,7 @@ define(
'click .x-notifications-tab' : '_showNotifications',
'click .x-metadata-tab' : '_showMetadata',
'click .x-general-tab' : '_showGeneral',
'click .x-ui-tab' : '_showUi',
'click .x-save-settings' : '_save',
'change .x-advanced-settings' : '_toggleAdvancedSettings'
},
@ -103,6 +110,7 @@ define(
this.downloadClientSettings = new DownloadClientSettingsModel();
this.notificationCollection = new NotificationCollection();
this.generalSettings = new GeneralSettingsModel();
this.uiSettings = new UiSettingsModel();
Backbone.$.when(
this.mediaManagementSettings.fetch(),
@ -110,7 +118,8 @@ define(
this.indexerSettings.fetch(),
this.downloadClientSettings.fetch(),
this.notificationCollection.fetch(),
this.generalSettings.fetch()
this.generalSettings.fetch(),
this.uiSettings.fetch()
).done(function () {
if(!self.isClosed)
{
@ -123,6 +132,7 @@ define(
self.notifications.show(new NotificationCollectionView({ collection: self.notificationCollection }));
self.metadata.show(new MetadataLayout());
self.general.show(new GeneralView({ model: self.generalSettings }));
self.uiRegion.show(new UiView({ model: self.uiSettings }));
}
});
@ -155,6 +165,9 @@ define(
case 'general':
this._showGeneral();
break;
case 'ui':
this._showUi();
break;
default:
this._showMediaManagement();
}
@ -232,6 +245,15 @@ define(
this._navigate('settings/general');
},
_showUi: function (e) {
if (e) {
e.preventDefault();
}
this.ui.uiTab.tab('show');
this._navigate('settings/ui');
},
_navigate:function(route){
Backbone.history.navigate(route, { trigger: false, replace: true });
},

View File

@ -7,6 +7,7 @@
<li><a href="#notifications" class="x-notifications-tab no-router">Connect</a></li>
<li><a href="#metadata" class="x-metadata-tab no-router">Metadata</a></li>
<li><a href="#general" class="x-general-tab no-router">General</a></li>
<li><a href="#ui" class="x-ui-tab no-router">UI</a></li>
</ul>
<div class="row settings-controls">
@ -42,6 +43,7 @@
<div class="tab-pane" id="notifications"></div>
<div class="tab-pane" id="metadata"></div>
<div class="tab-pane" id="general"></div>
<div class="tab-pane" id="ui"></div>
</div>
<div id="loading-region"></div>

View File

@ -0,0 +1,12 @@
'use strict';
define(
[
'Settings/SettingsModelBase'
], function (SettingsModelBase) {
return SettingsModelBase.extend({
url : window.NzbDrone.ApiRoot + '/config/ui',
successMessage: 'UI settings saved',
errorMessage : 'Failed to save UI settings'
});
});

View File

@ -0,0 +1,27 @@
'use strict';
define(
[
'vent',
'marionette',
'Shared/UiSettingsModel',
'Mixins/AsModelBoundView',
'Mixins/AsValidatedView'
], function (vent, Marionette, UiSettingsModel, AsModelBoundView, AsValidatedView) {
var view = Marionette.ItemView.extend({
template: 'Settings/UI/UiViewTemplate',
initialize: function () {
this.listenTo(this.model, 'sync', this._reloadUiSettings);
},
_reloadUiSettings: function() {
UiSettingsModel.fetch();
}
});
AsModelBoundView.call(view);
AsValidatedView.call(view);
return view;
});

View File

@ -0,0 +1,95 @@
<div class="form-horizontal">
<fieldset>
<legend>Calendar</legend>
<div class="form-group">
<label class="col-sm-3 control-label">First Day of Week</label>
<div class="col-sm-4">
<select name="firstDayOfWeek" class="form-control">
<option value="0">Sunday</option>
<option value="1">Monday</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Week Column Header</label>
<div class="col-sm-1 col-sm-push-4 help-inline">
<i class="icon-nd-form-warning" title="Shown above each column when week is the active view"/>
</div>
<div class="col-sm-4 col-sm-pull-1">
<select name="calendarWeekColumnHeader" class="form-control">
<option value="ddd M/D">Tue 3/25</option>
<option value="ddd MM/DD">Tue 03/25</option>
<option value="ddd D/M">Tue 25/3</option>
<option value="ddd DD/MM">Tue 25/03</option>
</select>
</div>
</div>
</fieldset>
<fieldset>
<legend>Dates</legend>
<div class="form-group">
<label class="col-sm-3 control-label">Short Date Format</label>
<div class="col-sm-4">
<select name="shortDateFormat" class="form-control">
<option value="MMM D YYYY">Mar 25 2014</option>
<option value="DD MMM YYYY">25 Mar 2014</option>
<option value="MM/D/YYYY">03/25/2014</option>
<option value="DD/MM/YYYY">25/03/2014</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Long Date Format</label>
<div class="col-sm-4">
<select name="longDateFormat" class="form-control">
<option value="dddd, MMMM D YYYY">Tuesday, March 25, 2014</option>
<option value="dddd, D MMMM YYYY">Tuesday, 25 March, 2014</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Time Format</label>
<div class="col-sm-4">
<select name="timeFormat" class="form-control">
<option value="h(:mm)a">5pm/5:30pm</option>
<option value="HH:mm">17:00/17:30</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Show Relative Dates</label>
<div class="col-sm-8">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" name="showRelativeDates"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-nd-form-info" title="Show relative (Today/Yesterday/etc) or absolute dates"/>
</span>
</div>
</div>
</div>
</fieldset>
</div>

View File

@ -109,6 +109,10 @@ li.save-and-add:hover {
}
.settings-tabs {
li>a {
padding : 10px;
}
@media (min-width: @screen-sm-min) and (max-width: @screen-md-max) {
li {
a {

View File

@ -3,8 +3,9 @@
define(
[
'moment',
'filesize'
], function (Moment, Filesize) {
'filesize',
'Shared/UiSettingsModel'
], function (moment, filesize, UiSettings) {
return {
@ -15,16 +16,15 @@ define(
return '';
}
return Filesize(size, { base: 2, round: 1 });
return filesize(size, { base: 2, round: 1 });
},
dateHelper: function (sourceDate) {
relativeDate: function (sourceDate) {
if (!sourceDate) {
return '';
}
var date = Moment(sourceDate);
var date = moment(sourceDate);
var calendarDate = date.calendar();
//TODO: It would be nice to not have to hack this...
@ -34,12 +34,12 @@ define(
return strippedCalendarDate;
}
if (date.isAfter(Moment())) {
if (date.isAfter(moment())) {
return date.fromNow(true);
}
if (date.isBefore(Moment().add('years', -1))) {
return date.format('ll');
if (date.isBefore(moment().add('years', -1))) {
return date.format(UiSettings.get('shortDateFormat'));
}
return date.fromNow();

View File

@ -0,0 +1,22 @@
'use strict';
define(
[
'backbone',
'api!config/ui'
], function (Backbone, uiSettings) {
var UiSettings = Backbone.Model.extend({
url : window.NzbDrone.ApiRoot + '/config/ui',
shortDateTime : function () {
return this.get('shortDateFormat') + ' ' + this.get('timeFormat').replace('(', '').replace(')', '');
},
longDateTime : function () {
return this.get('longDateFormat') + ' ' + this.get('timeFormat').replace('(', '').replace(')', '');
}
});
var instance = new UiSettings(uiSettings);
return instance;
});

View File

@ -2,16 +2,17 @@
define(
[
'Cells/NzbDroneCell',
'moment'
], function (NzbDroneCell, Moment) {
'moment',
'Shared/UiSettingsModel'
], function (NzbDroneCell, moment, UiSettings) {
return NzbDroneCell.extend({
className: 'log-time-cell',
render: function () {
var date = Moment(this._getValue());
this.$el.html('<span title="{1}">{0}</span>'.format(date.format('LT'), date.format('LLLL')));
var date = moment(this._getValue());
this.$el.html('<span title="{1}">{0}</span>'.format(date.format(UiSettings.get('timeFormat')), date.format(UiSettings.longDateFormat())));
return this;
}

View File

@ -199,7 +199,6 @@ require.config({
headerCell: 'NzbDrone',
sortType : 'toggle'
};
});
}
},
@ -247,7 +246,19 @@ define(
'Instrumentation/StringFormat',
'LifeCycle',
'Hotkeys/Hotkeys'
], function ($, Backbone, Marionette, RouteBinder, SignalRBroadcaster, NavbarView, AppLayout, SeriesController, Router, ModalController, ControlPanelController, serverStatusModel, Tooltip) {
], function ($,
Backbone,
Marionette,
RouteBinder,
SignalRBroadcaster,
NavbarView,
AppLayout,
SeriesController,
Router,
ModalController,
ControlPanelController,
serverStatusModel,
Tooltip) {
new SeriesController();
new ModalController();