From 2b4888d062d65546325470d2d9181f4a66a2fb00 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Mon, 22 Jun 2015 14:23:59 -0400 Subject: fixes mm-1316 improves channel notifications UI and updates channellist etag --- model/channel_list.go | 6 + model/channel_member.go | 5 + store/sql_channel_store.go | 12 +- web/react/components/channel_header.jsx | 30 +--- web/react/components/channel_notifications.jsx | 208 +++++++++++++++++++------ 5 files changed, 185 insertions(+), 76 deletions(-) diff --git a/model/channel_list.go b/model/channel_list.go index 088dbea2a..09f14a986 100644 --- a/model/channel_list.go +++ b/model/channel_list.go @@ -53,6 +53,12 @@ func (o *ChannelList) Etag() string { t = member.LastViewedAt id = v.Id } + + if member.LastUpdateAt > t { + t = member.LastUpdateAt + id = v.Id + } + } } diff --git a/model/channel_member.go b/model/channel_member.go index 720ac4c42..50f51304b 100644 --- a/model/channel_member.go +++ b/model/channel_member.go @@ -25,6 +25,7 @@ type ChannelMember struct { MsgCount int64 `json:"msg_count"` MentionCount int64 `json:"mention_count"` NotifyLevel string `json:"notify_level"` + LastUpdateAt int64 `json:"last_update_at"` } func (o *ChannelMember) ToJson() string { @@ -70,6 +71,10 @@ func (o *ChannelMember) IsValid() *AppError { return nil } +func (o *ChannelMember) PreSave() { + o.LastUpdateAt = GetMillis() +} + func IsChannelNotifyLevelValid(notifyLevel string) bool { return notifyLevel == CHANNEL_NOTIFY_ALL || notifyLevel == CHANNEL_NOTIFY_MENTION || notifyLevel == CHANNEL_NOTIFY_NONE || notifyLevel == CHANNEL_NOTIFY_QUIET } diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go index 592657c1c..0a1ea23fe 100644 --- a/store/sql_channel_store.go +++ b/store/sql_channel_store.go @@ -37,6 +37,7 @@ func NewSqlChannelStore(sqlStore *SqlStore) ChannelStore { } func (s SqlChannelStore) UpgradeSchemaIfNeeded() { + s.CreateColumnIfNotExists("ChannelMembers", "LastUpdateAt", "NotifyLevel", "bigint(20)", "0") } func (s SqlChannelStore) CreateIndexesIfNotExists() { @@ -273,6 +274,7 @@ func (s SqlChannelStore) SaveMember(member *model.ChannelMember) StoreChannel { go func() { result := StoreResult{} + member.PreSave() if result.Err = member.IsValid(); result.Err != nil { storeChannel <- result return @@ -484,7 +486,8 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelId string, userId string) Sto SET ChannelMembers.MentionCount = 0, ChannelMembers.MsgCount = Channels.TotalMsgCount, - ChannelMembers.LastViewedAt = Channels.LastPostAt + ChannelMembers.LastViewedAt = Channels.LastPostAt, + ChannelMembers.LastUpdateAt = Channels.LastPostAt WHERE Channels.Id = ChannelMembers.ChannelId AND UserId = ? @@ -533,15 +536,18 @@ func (s SqlChannelStore) UpdateNotifyLevel(channelId, userId, notifyLevel string go func() { result := StoreResult{} + updateAt := model.GetMillis() + _, err := s.GetMaster().Exec( `UPDATE ChannelMembers SET - NotifyLevel = ? + NotifyLevel = ?, + LastUpdateAt = ? WHERE UserId = ? AND ChannelId = ?`, - notifyLevel, userId, channelId) + notifyLevel, updateAt, userId, channelId) if err != nil { result.Err = model.NewAppError("SqlChannelStore.UpdateNotifyLevel", "We couldn't update the notify level", "channel_id="+channelId+", user_id="+userId+", "+err.Error()) } diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index ade58a10a..428d3ed81 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -15,17 +15,8 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; -function getExtraInfoStateFromStores() { - return { - extra_info: ChannelStore.getCurrentExtraInfo() - }; -} - var ExtraMembers = React.createClass({ componentDidMount: function() { - ChannelStore.addExtraInfoChangeListener(this._onChange); - ChannelStore.addChangeListener(this._onChange); - var originalLeave = $.fn.popover.Constructor.prototype.leave; $.fn.popover.Constructor.prototype.leave = function(obj) { var self = obj instanceof this.constructor ? obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type); @@ -49,25 +40,12 @@ var ExtraMembers = React.createClass({ }); }, - componentWillUnmount: function() { - ChannelStore.removeExtraInfoChangeListener(this._onChange); - ChannelStore.removeChangeListener(this._onChange); - }, - _onChange: function() { - var newState = getExtraInfoStateFromStores(); - if (!utils.areStatesEqual(newState, this.state)) { - this.setState(newState); - } - }, - getInitialState: function() { - return getExtraInfoStateFromStores(); - }, render: function() { - var count = this.state.extra_info.members.length == 0 ? "-" : this.state.extra_info.members.length; - count = this.state.extra_info.members.length > 19 ? "20+" : count; + var count = this.props.members.length == 0 ? "-" : this.props.members.length; + count = this.props.members.length > 19 ? "20+" : count; var data_content = ""; - this.state.extra_info.members.forEach(function(m) { + this.props.members.forEach(function(m) { data_content += "
" + m.username + "
"; }); @@ -228,7 +206,7 @@ module.exports = React.createClass({ {channelTitle} } - + { searchForm }
diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx index 085536a0a..fa9ab42ae 100644 --- a/web/react/components/channel_notifications.jsx +++ b/web/react/components/channel_notifications.jsx @@ -1,6 +1,8 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. +var SettingItemMin = require('./setting_item_min.jsx'); +var SettingItemMax = require('./setting_item_max.jsx'); var utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); @@ -9,26 +11,50 @@ var ChannelStore = require('../stores/channel_store.jsx'); module.exports = React.createClass({ componentDidMount: function() { + ChannelStore.addChangeListener(this._onChange); + var self = this; $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { var button = e.relatedTarget; var channel_id = button.dataset.channelid; var notifyLevel = ChannelStore.getMember(channel_id).notify_level; - self.setState({ notify_level: notifyLevel, title: button.dataset.title, channel_id: channel_id }); + var quietMode = false; + if (notifyLevel === "quiet") quietMode = true; + self.setState({ notify_level: notifyLevel, quiet_mode: quietMode, title: button.dataset.title, channel_id: channel_id }); }); }, + componentWillUnmount: function() { + ChannelStore.removeChangeListener(this._onChange); + }, + _onChange: function() { + if (!this.state.channel_id) return; + var notifyLevel = ChannelStore.getMember(this.state.channel_id).notify_level; + var quietMode = false; + if (notifyLevel === "quiet") quietMode = true; + + var newState = this.state; + newState.notify_level = notifyLevel; + newState.quiet_mode = quietMode; + + if (!utils.areStatesEqual(this.state, newState)) { + this.setState(newState); + } + }, + updateSection: function(section) { + this.setState({ activeSection: section }); + }, getInitialState: function() { - return { notify_level: "", title: "", channel_id: "" }; + return { notify_level: "", title: "", channel_id: "", activeSection: "" }; }, - handleUpdate: function(e) { + handleUpdate: function() { var channel_id = this.state.channel_id; - var notify_level = this.state.notify_level; + var notify_level = this.state.quiet_mode ? "quiet" : this.state.notify_level; var data = {}; data["channel_id"] = channel_id; data["user_id"] = UserStore.getCurrentId(); - data["notify_level"] = this.state.notify_level; + data["notify_level"] = notify_level; if (!data["notify_level"] || data["notify_level"].length === 0) return; @@ -37,7 +63,7 @@ module.exports = React.createClass({ var member = ChannelStore.getMember(channel_id); member.notify_level = notify_level; ChannelStore.setChannelMember(member); - $(this.refs.modal.getDOMNode()).modal('hide'); + this.updateSection(""); }.bind(this), function(err) { this.setState({ server_error: err.message }); @@ -45,42 +71,138 @@ module.exports = React.createClass({ ); }, handleRadioClick: function(notifyLevel) { - this.setState({ notify_level: notifyLevel }); + this.setState({ notify_level: notifyLevel, quiet_mode: false }); this.refs.modal.getDOMNode().focus(); }, - handleQuietToggle: function() { - if (this.state.notify_level === "quiet") { - this.setState({ notify_level: "none" }); - this.refs.modal.getDOMNode().focus(); - } else { - this.setState({ notify_level: "quiet" }); - this.refs.modal.getDOMNode().focus(); - } + handleQuietToggle: function(quietMode) { + this.setState({ notify_level: "none", quiet_mode: quietMode }); + this.refs.modal.getDOMNode().focus(); }, render: function() { var server_error = this.state.server_error ?
: null; - var allActive = ""; - var mentionActive = ""; - var noneActive = ""; - var quietActive = ""; - var desktopHidden = ""; - - if (this.state.notify_level === "quiet") { - desktopHidden = "hidden"; - quietActive = "active"; - } else if (this.state.notify_level === "mention") { - mentionActive = "active"; - } else if (this.state.notify_level === "none") { - noneActive = "active"; + var self = this; + + var desktopSection; + if (this.state.activeSection === 'desktop') { + var notifyActive = [false, false, false]; + if (this.state.notify_level === "mention") { + notifyActive[1] = true; + } else if (this.state.notify_level === "all") { + notifyActive[0] = true; + } else { + notifyActive[2] = true; + } + + var inputs = []; + + inputs.push( +
+
+ +
+
+
+ +
+
+
+ +
+
+ ); + + desktopSection = ( + + ); + } else { + var describe = ""; + if (this.state.notify_level === "mention") { + describe = "Only for mentions"; + } else if (this.state.notify_level === "all") { + describe = "For all activity"; + } else { + describe = "Never"; + } + + desktopSection = ( + + ); + } + + var quietSection; + if (this.state.activeSection === 'quiet') { + var quietActive = ["",""]; + if (this.state.quiet_mode) { + quietActive[0] = "active"; + } else { + quietActive[1] = "active"; + } + + var inputs = []; + + inputs.push( +
+
+ + +
+
+ ); + + inputs.push( +
+
+ Enabling quiet mode will turn off desktop notifications and only mark the channel as unread if you have been mentioned. +
+ ); + + quietSection = ( + + ); } else { - allActive = "active"; + var describe = ""; + if (this.state.quiet_mode) { + describe = "On"; + } else { + describe = "Off"; + } + + quietSection = ( + + ); } var self = this; return (