diff --git a/ember/app/components/application/user-dropdown.js b/ember/app/components/application/user-dropdown.js index 23dc05a24..6419124ee 100644 --- a/ember/app/components/application/user-dropdown.js +++ b/ember/app/components/application/user-dropdown.js @@ -25,7 +25,8 @@ export default DropdownButton.extend(HasItemLists, { items.pushObjectWithTag(Ember.Component.extend({ tagName: 'li', - layout: precompileTemplate('{{#link-to "settings"}}{{fa-icon "cog"}} Settings{{/link-to}}') + layout: precompileTemplate('{{#link-to "user.settings" user}}{{fa-icon "cog"}} Settings{{/link-to}}'), + user: this.get('user') })); if (this.get('user.groups').findBy('id', '1')) { diff --git a/ember/app/components/ui/field-set.js b/ember/app/components/ui/field-set.js new file mode 100644 index 000000000..f12ddb4f1 --- /dev/null +++ b/ember/app/components/ui/field-set.js @@ -0,0 +1,13 @@ +import Ember from 'ember'; + +/** + A set of fields with a heading. + */ +export default Ember.Component.extend({ + layoutName: 'components/ui/field-set', + tagName: 'fieldset', + classNameBindings: ['className'], + + label: '', + fields: [] +}); diff --git a/ember/app/components/ui/switch-input.js b/ember/app/components/ui/switch-input.js new file mode 100644 index 000000000..278eae4c3 --- /dev/null +++ b/ember/app/components/ui/switch-input.js @@ -0,0 +1,19 @@ +import Ember from 'ember'; + +/** + A toggle switch. + */ +export default Ember.Component.extend({ + layoutName: 'components/ui/switch-input', + classNames: ['checkbox', 'checkbox-switch'], + + label: '', + toggleState: true, + + didInsertElement: function() { + var component = this; + this.$('input').on('change', function() { + component.get('changed')($(this).prop('checked'), component); + }); + } +}); diff --git a/ember/app/components/ui/yesno-input.js b/ember/app/components/ui/yesno-input.js new file mode 100644 index 000000000..915254259 --- /dev/null +++ b/ember/app/components/ui/yesno-input.js @@ -0,0 +1,19 @@ +import Ember from 'ember'; + +/** + A toggle switch. + */ +export default Ember.Component.extend({ + layoutName: 'components/ui/yesno-input', + tagName: 'label', + classNames: ['yesno-control'], + + toggleState: true, + + didInsertElement: function() { + var component = this; + this.$('input').on('change', function() { + component.get('changed')($(this).prop('checked'), component); + }); + } +}); diff --git a/ember/app/components/user/notification-grid.js b/ember/app/components/user/notification-grid.js new file mode 100644 index 000000000..2041f4448 --- /dev/null +++ b/ember/app/components/user/notification-grid.js @@ -0,0 +1,105 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ + layoutName: 'components/user/notification-grid', + classNames: ['notification-grid'], + + methods: [ + { name: 'alert', icon: 'bell', label: 'Alert' }, + { name: 'email', icon: 'envelope-o', label: 'Email' } + ], + + didInsertElement: function() { + var component = this; + this.$('thead .toggle-group').bind('mouseenter mouseleave', function(e) { + var i = parseInt($(this).index()) + 1; + component.$('table').find('td:nth-child('+i+')').toggleClass('highlighted', e.type === 'mouseenter'); + }); + this.$('tbody .toggle-group').bind('mouseenter mouseleave', function(e) { + $(this).parent().find('td').toggleClass('highlighted', e.type === 'mouseenter'); + }); + }, + + preferenceKey: function(type, method) { + return 'notify_'+type+'_'+method; + }, + + grid: Ember.computed('methods', 'notificationTypes', function() { + var grid = []; + var component = this; + var notificationTypes = this.get('notificationTypes'); + var methods = this.get('methods'); + var user = this.get('user'); + + notificationTypes.forEach(function(type) { + var row = Ember.Object.create({ + type: type, + label: type.label, + cells: [] + }); + methods.forEach(function(method) { + var preferenceKey = 'preferences.'+component.preferenceKey(type.name, method.name); + var cell = Ember.Object.create({ + type: type, + method: method, + enabled: !!user.get(preferenceKey), + loading: false + }); + cell.set('save', function(value, component) { + cell.set('loading', true); + user.set(preferenceKey, value).save().then(function() { + cell.set('loading', false); + }); + }); + row.get('cells').pushObject(cell); + }); + grid.pushObject(row); + }); + + return grid; + }), + + toggleCells: function(cells) { + var enabled = !cells[0].get('enabled'); + var user = this.get('user'); + var component = this; + cells.forEach(function(cell) { + cell.set('loading', true); + cell.set('enabled', enabled); + user.set('preferences.'+component.preferenceKey(cell.get('type.name'), cell.get('method.name')), enabled); + }); + user.save().then(function() { + cells.forEach(function(cell) { + cell.set('loading', false); + }) + }); + }, + + actions: { + toggleMethod: function(method) { + var grid = this.get('grid'); + var component = this; + var cells = []; + grid.forEach(function(row) { + row.get('cells').some(function(cell) { + if (cell.get('method') === method) { + cells.pushObject(cell); + return true; + } + }); + }); + component.toggleCells(cells); + }, + + toggleType: function(type) { + var grid = this.get('grid'); + var component = this; + grid.some(function(row) { + if (row.get('type') === type) { + component.toggleCells(row.get('cells')); + return true; + } + }); + } + } +}); diff --git a/ember/app/models/user.js b/ember/app/models/user.js index ebbecaaf6..aa45485b6 100644 --- a/ember/app/models/user.js +++ b/ember/app/models/user.js @@ -12,6 +12,7 @@ export default DS.Model.extend(HasItemLists, { avatarUrl: DS.attr('string'), bio: DS.attr('string'), bioHtml: DS.attr('string'), + preferences: DS.attr(), groups: DS.hasMany('group'), diff --git a/ember/app/router.js b/ember/app/router.js index 89bd4f337..e281d62bc 100644 --- a/ember/app/router.js +++ b/ember/app/router.js @@ -15,9 +15,8 @@ Router.map(function() { this.resource('user', {path: '/u/:username'}, function() { this.route('activity', {path: '/'}); this.route('edit'); + this.route('settings'); }); - - this.resource('settings'); }); export default Router; diff --git a/ember/app/routes/user/settings.js b/ember/app/routes/user/settings.js new file mode 100644 index 000000000..8b44a23e0 --- /dev/null +++ b/ember/app/routes/user/settings.js @@ -0,0 +1,7 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ + model: function() { + return Ember.RSVP.resolve(this.modelFor('user')); + } +}); diff --git a/ember/app/styles/app.less b/ember/app/styles/app.less index dd963f4c6..c8cf10e30 100644 --- a/ember/app/styles/app.less +++ b/ember/app/styles/app.less @@ -35,5 +35,6 @@ @import "@{flarum-base}index.less"; @import "@{flarum-base}discussion.less"; @import "@{flarum-base}user.less"; +@import "@{flarum-base}settings.less"; @import "@{flarum-base}login.less"; @import "@{flarum-base}signup.less"; diff --git a/ember/app/styles/bootstrap/variables.less b/ember/app/styles/bootstrap/variables.less index 7b2ea8133..14b671c92 100644 --- a/ember/app/styles/bootstrap/variables.less +++ b/ember/app/styles/bootstrap/variables.less @@ -84,6 +84,7 @@ @body-bg: @fl-body-bg; @text-color: @fl-body-color; +@legend-color: @fl-body-color; @font-size-base: 13px; @font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; diff --git a/ember/app/styles/flarum/buttons.less b/ember/app/styles/flarum/buttons.less index 5597e5dc0..d6e9eea6d 100644 --- a/ember/app/styles/flarum/buttons.less +++ b/ember/app/styles/flarum/buttons.less @@ -39,8 +39,11 @@ border-radius: 2px; line-height: 1; } +.btn-danger { + .button-variant(#d66, #fdd, #fdd); +} -// Redefine Bootstrap's mixin to make some general changes +// Add to Bootstrap's mixin to make some general changes .button-variant(@color; @background; @border) { &:hover, &:focus, diff --git a/ember/app/styles/flarum/dropdowns.less b/ember/app/styles/flarum/dropdowns.less index fdef09008..bb5d2dfa4 100644 --- a/ember/app/styles/flarum/dropdowns.less +++ b/ember/app/styles/flarum/dropdowns.less @@ -132,4 +132,7 @@ color: @fl-body-primary-color; font-weight: bold; } + & > li.divider { + background: none; + } } diff --git a/ember/app/styles/flarum/forms.less b/ember/app/styles/flarum/forms.less index 3e218e322..4d480e528 100644 --- a/ember/app/styles/flarum/forms.less +++ b/ember/app/styles/flarum/forms.less @@ -10,6 +10,12 @@ .box-shadow(none); } } +legend { + font-size: 14px; + border: 0; + font-weight: bold; + margin-bottom: 10px; +} // Search inputs // @todo Extract some of this into header-specific definitions @@ -69,3 +75,79 @@ pointer-events: none; color: @fl-body-muted-color; } + +.checkbox-switch { + & label { + padding-left: 65px; + } + & .switch-control { + float: left; + margin-left: -65px; + margin-top: -4px; + } + & .loading-indicator { + display: inline-block; + margin-left: 10px; + } +} +.switch-control, .yesno-control { + & input[type=checkbox] { + display: none; + } +} +.switch { + width: 50px; + height: 28px; + padding: 3px; + position: relative; + border-radius: 14px; + background: @fl-body-control-bg; + .transition(background-color 0.2s); + + input:checked + & { + background: @fl-body-primary-color; + } + + & .loading-indicator { + opacity: 0; + + .loading& { + opacity: 1; + } + } + &:before, & .loading-indicator { + position: absolute; + width: 22px; + height: 22px; + padding: 0; + left: 3px; + .transition(~"opacity 0.2s, left 0.2s"); + + input:checked + & { + left: 25px; + } + } + &:before { + content: ' '; + background: @fl-body-bg; + border-radius: 11px; + box-shadow: 0 2px 4px @fl-shadow-color; + } +} +.yesno-control { + cursor: pointer; + margin: 0; +} +.yesno { + font-size: 14px; + + &.yes { + color: #58A400; + } + &.no { + color: #D0021B; + } + &.disabled { + color: @fl-body-muted-more-color !important; + } +} diff --git a/ember/app/styles/flarum/settings.less b/ember/app/styles/flarum/settings.less new file mode 100644 index 000000000..4baf77948 --- /dev/null +++ b/ember/app/styles/flarum/settings.less @@ -0,0 +1,61 @@ +.settings { + margin-top: 5px; + + & > ul { + list-style: none; + margin: 0; + padding: 0; + + & > li { + margin-bottom: 40px; + } + } + & fieldset > ul { + list-style: none; + margin: 0; + padding: 0; + } +} +.settings-account { + & li { + display: inline-block; + margin-right: 5px; + } +} +.notification-grid { + & table { + background: @fl-body-control-bg; + border-radius: @border-radius-base; + } + & table td, & table th { + border-bottom: 1px solid @fl-body-bg; + color: @fl-body-control-color; + } + & td, & th, & .yesno-cell .yesno { + padding: 10px 15px; + } + & thead { + & th { + text-align: center; + padding: 15px 25px; + } + & .fa { + display: block; + font-size: 14px; + width: auto; + margin-bottom: 5px; + } + } + & .yesno-cell { + text-align: center; + cursor: pointer; + padding: 0; + + &:hover, &.highlighted { + background: darken(@fl-body-control-bg, 4%); + } + } + & .toggle-group { + cursor: pointer; + } +} diff --git a/ember/app/styles/flarum/user.less b/ember/app/styles/flarum/user.less index cc8aa370b..1df4cdf1e 100644 --- a/ember/app/styles/flarum/user.less +++ b/ember/app/styles/flarum/user.less @@ -106,10 +106,10 @@ } } -.user-content .loading-indicator { +.user-activity .loading-indicator { height: 46px; } -.user-activity { +.activity-list { border-left: 3px solid @fl-body-secondary-color; list-style: none; margin: 0 0 0 16px; diff --git a/ember/app/templates/components/ui/field-set.hbs b/ember/app/templates/components/ui/field-set.hbs new file mode 100644 index 000000000..d4e10daa0 --- /dev/null +++ b/ember/app/templates/components/ui/field-set.hbs @@ -0,0 +1,3 @@ + + +{{ui/item-list items=fields}} diff --git a/ember/app/templates/components/ui/switch-input.hbs b/ember/app/templates/components/ui/switch-input.hbs new file mode 100644 index 000000000..be78ff97c --- /dev/null +++ b/ember/app/templates/components/ui/switch-input.hbs @@ -0,0 +1,12 @@ + diff --git a/ember/app/templates/components/ui/yesno-input.hbs b/ember/app/templates/components/ui/yesno-input.hbs new file mode 100644 index 000000000..f443a0fd9 --- /dev/null +++ b/ember/app/templates/components/ui/yesno-input.hbs @@ -0,0 +1,10 @@ +{{input type="checkbox" checked=toggleState disabled=disabled}} +
+ {{#each methods as |method|}} + | {{fa-icon method.icon}} {{method.label}} | + {{/each}} +
---|---|
{{row.label}} | + {{#each row.cells as |cell|}} +{{ui/yesno-input toggleState=cell.enabled changed=cell.save loading=cell.loading}} | + {{/each}} +