Files
ThreadNet-Web/src/components/views/rooms/RoomSettings.js
T

494 lines
20 KiB
JavaScript
Raw Normal View History

2015-11-27 10:42:03 +00:00
/*
2016-01-07 04:06:39 +00:00
Copyright 2015, 2016 OpenMarket Ltd
2015-11-27 10:42:03 +00:00
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var q = require("q");
2015-11-27 10:42:03 +00:00
var React = require('react');
var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require('../../../index');
var Modal = require('../../../Modal');
2016-02-05 11:59:19 +00:00
var ObjectUtils = require("../../../ObjectUtils");
2015-11-27 10:42:03 +00:00
module.exports = React.createClass({
displayName: 'RoomSettings',
propTypes: {
room: React.PropTypes.object.isRequired,
onSaveClick: React.PropTypes.func,
onCancelClick: React.PropTypes.func,
2015-11-27 10:42:03 +00:00
},
getInitialState: function() {
2016-01-17 05:13:16 +00:00
var tags = {};
Object.keys(this.props.room.tags).forEach(function(tagName) {
tags[tagName] = {};
});
2016-02-04 15:26:12 +00:00
var are_notifications_muted = false;
var roomPushRule = MatrixClientPeg.get().getRoomPushRule("global", this.props.room.roomId);
if (roomPushRule) {
if (0 <= roomPushRule.actions.indexOf("dont_notify")) {
are_notifications_muted = true;
}
}
2015-11-27 10:42:03 +00:00
return {
2016-02-04 15:26:12 +00:00
name: this._yankValueFromEvent("m.room.name", "name"),
topic: this._yankValueFromEvent("m.room.topic", "topic"),
join_rule: this._yankValueFromEvent("m.room.join_rules", "join_rule"),
history_visibility: this._yankValueFromEvent("m.room.history_visibility", "history_visibility"),
guest_access: this._yankValueFromEvent("m.room.guest_access", "guest_access"),
power_levels_changed: false,
2016-01-17 05:13:16 +00:00
tags_changed: false,
tags: tags,
2016-02-04 15:26:12 +00:00
areNotifsMuted: are_notifications_muted
2015-11-27 10:42:03 +00:00
};
},
2016-02-04 15:26:12 +00:00
setName: function(name) {
this.setState({
name: name
});
},
setTopic: function(topic) {
this.setState({
topic: topic
});
},
save: function() {
var stateWasSetDefer = q.defer();
// the caller may have JUST called setState on stuff, so we need to re-render before saving
// else we won't use the latest values of things.
// We can be a bit cheeky here and set a loading flag, and listen for the callback on that
// to know when things have been set.
this.setState({ _loading: true}, () => {
stateWasSetDefer.resolve();
this.setState({ _loading: false});
});
return stateWasSetDefer.promise.then(() => {
return this._save();
});
},
_save: function() {
const roomId = this.props.room.roomId;
var promises = this.saveAliases(); // returns Promise[]
var originalState = this.getInitialState();
2016-02-05 11:59:19 +00:00
2016-02-04 15:26:12 +00:00
// diff between original state and this.state to work out what has been changed
console.log("Original: %s", JSON.stringify(originalState));
console.log("New: %s", JSON.stringify(this.state));
2016-02-05 11:59:19 +00:00
// name and topic
if (this._hasDiff(this.state.name, originalState.name)) {
2016-02-04 15:26:12 +00:00
promises.push(MatrixClientPeg.get().setRoomName(roomId, this.state.name));
}
if (this._hasDiff(this.state.topic, originalState.topic)) {
2016-02-04 15:26:12 +00:00
promises.push(MatrixClientPeg.get().setRoomTopic(roomId, this.state.topic));
}
2016-02-05 11:59:19 +00:00
2016-02-04 15:26:12 +00:00
// TODO:
// this.state.join_rule
// this.state.history_visibility
// this.state.guest_access
2016-02-05 11:59:19 +00:00
2016-02-04 15:26:12 +00:00
// setRoomMutePushRule
2016-02-05 11:59:19 +00:00
if (this.state.areNotifsMuted !== originalState.areNotifsMuted) {
promises.push(MatrixClientPeg.get().setRoomMutePushRule(
"global", roomId, this.state.areNotifsMuted
));
}
2016-02-04 15:26:12 +00:00
// power levels
2016-02-05 11:59:19 +00:00
var powerLevels = this._getPowerLevels();
if (powerLevels) {
promises.push(MatrixClientPeg.get().sendStateEvent(
roomId, "m.room.power_levels", powerLevels, ""
));
}
2016-02-04 15:26:12 +00:00
// tags
2016-02-05 11:59:19 +00:00
if (this.state.tags_changed) {
var tagDiffs = ObjectUtils.getKeyValueArrayDiffs(originalState.tags, this.state.tags);
// [ {place: add, key: "m.favourite", val: "yep"} ]
tagDiffs.forEach(function(diff) {
switch (diff.place) {
case "add":
promises.push(
MatrixClientPeg.get().setRoomTag(roomId, diff.key, {})
);
break;
case "del":
promises.push(
MatrixClientPeg.get().deleteRoomTag(roomId, diff.key)
);
break;
default:
console.error("Unknown tag operation: %s", diff.place);
break;
}
});
}
2016-02-04 15:26:12 +00:00
// color scheme
promises.push(this.saveColor());
2016-02-04 15:26:12 +00:00
return q.allSettled(promises);
},
2015-11-27 10:42:03 +00:00
saveAliases: function() {
if (!this.refs.alias_settings) { return [q()]; }
return this.refs.alias_settings.saveSettings();
},
saveColor: function() {
if (!this.refs.color_settings) { return q(); }
return this.refs.color_settings.saveSettings();
},
_hasDiff: function(strA, strB) {
// treat undefined as an empty string because other components may blindly
// call setName("") when there has been no diff made to the name!
strA = strA || "";
strB = strB || "";
return strA !== strB;
},
2016-02-05 11:59:19 +00:00
_getPowerLevels: function() {
2015-11-27 10:42:03 +00:00
if (!this.state.power_levels_changed) return undefined;
var power_levels = this.props.room.currentState.getStateEvents('m.room.power_levels', '');
power_levels = power_levels.getContent();
var new_power_levels = {
ban: parseInt(this.refs.ban.getValue()),
kick: parseInt(this.refs.kick.getValue()),
redact: parseInt(this.refs.redact.getValue()),
invite: parseInt(this.refs.invite.getValue()),
events_default: parseInt(this.refs.events_default.getValue()),
state_default: parseInt(this.refs.state_default.getValue()),
users_default: parseInt(this.refs.users_default.getValue()),
2015-11-27 10:42:03 +00:00
users: power_levels.users,
events: power_levels.events,
};
return new_power_levels;
},
onPowerLevelsChanged: function() {
this.setState({
power_levels_changed: true
});
},
2016-02-04 15:26:12 +00:00
_yankValueFromEvent: function(stateEventType, keyName, defaultValue) {
// E.g.("m.room.name","name") would yank the "name" content key from "m.room.name"
var event = this.props.room.currentState.getStateEvents(stateEventType, '');
if (!event) {
return defaultValue;
}
return event.getContent()[keyName] || defaultValue;
},
2016-02-04 16:18:59 +00:00
_onToggle: function(keyName, checkedValue, uncheckedValue, ev) {
2016-02-04 15:26:12 +00:00
console.log("Checkbox toggle: %s %s", keyName, ev.target.checked);
var state = {};
2016-02-04 16:18:59 +00:00
state[keyName] = ev.target.checked ? checkedValue : uncheckedValue;
2016-02-04 15:26:12 +00:00
this.setState(state);
},
2016-02-05 11:59:19 +00:00
_onTagChange: function(tagName, event) {
2016-01-17 05:13:16 +00:00
if (event.target.checked) {
if (tagName === 'm.favourite') {
delete this.state.tags['m.lowpriority'];
}
else if (tagName === 'm.lowpriority') {
delete this.state.tags['m.favourite'];
}
2016-02-05 11:59:19 +00:00
this.state.tags[tagName] = this.state.tags[tagName] || ["yep"];
2016-01-17 05:13:16 +00:00
}
else {
delete this.state.tags[tagName];
}
this.setState({
tags: this.state.tags,
tags_changed: true
});
},
2015-11-27 10:42:03 +00:00
render: function() {
2016-01-13 13:15:13 +00:00
// TODO: go through greying out things you don't have permission to change
// (or turning them into informative stuff)
var AliasSettings = sdk.getComponent("room_settings.AliasSettings");
var ColorSettings = sdk.getComponent("room_settings.ColorSettings");
2016-01-13 13:15:13 +00:00
var EditableText = sdk.getComponent('elements.EditableText');
var PowerSelector = sdk.getComponent('elements.PowerSelector');
2015-11-27 10:42:03 +00:00
var power_levels = this.props.room.currentState.getStateEvents('m.room.power_levels', '');
var events_levels = (power_levels ? power_levels.events : {}) || {};
2015-11-27 10:42:03 +00:00
2016-01-13 13:15:13 +00:00
var user_id = MatrixClientPeg.get().credentials.userId;
2015-11-27 10:42:03 +00:00
if (power_levels) {
power_levels = power_levels.getContent();
var ban_level = parseInt(power_levels.ban);
var kick_level = parseInt(power_levels.kick);
var redact_level = parseInt(power_levels.redact);
var invite_level = parseInt(power_levels.invite || 0);
var send_level = parseInt(power_levels.events_default || 0);
var state_level = parseInt(power_levels.state_default || 0);
var default_user_level = parseInt(power_levels.users_default || 0);
if (power_levels.ban == undefined) ban_level = 50;
if (power_levels.kick == undefined) kick_level = 50;
if (power_levels.redact == undefined) redact_level = 50;
var user_levels = power_levels.users || {};
var current_user_level = user_levels[user_id];
if (current_user_level == undefined) current_user_level = default_user_level;
var power_level_level = events_levels["m.room.power_levels"];
if (power_level_level == undefined) {
power_level_level = state_level;
}
var can_change_levels = current_user_level >= power_level_level;
} else {
var ban_level = 50;
var kick_level = 50;
var redact_level = 50;
var invite_level = 0;
var send_level = 0;
var state_level = 0;
var default_user_level = 0;
var user_levels = [];
var events_levels = [];
var current_user_level = 0;
var power_level_level = 0;
var can_change_levels = false;
}
var state_default = (parseInt(power_levels ? power_levels.state_default : 0) || 0);
var room_aliases_level = state_default;
if (events_levels['m.room.aliases'] !== undefined) {
room_aliases_level = events_levels['m.room.aliases'];
2015-11-27 10:42:03 +00:00
}
var can_set_room_aliases = current_user_level >= room_aliases_level;
var canonical_alias_level = state_default;
if (events_levels['m.room.canonical_alias'] !== undefined) {
canonical_alias_level = events_levels['m.room.canonical_alias'];
}
var can_set_canonical_alias = current_user_level >= canonical_alias_level;
2015-11-27 10:42:03 +00:00
2016-01-21 15:08:04 +00:00
var can_set_tag = true;
2016-01-17 05:13:16 +00:00
var self = this;
2016-01-13 13:15:13 +00:00
var user_levels_section;
2016-01-20 17:12:55 +00:00
if (Object.keys(user_levels).length) {
2016-01-13 13:15:13 +00:00
user_levels_section =
2016-01-20 22:47:42 +00:00
<div>
<h3>Privileged Users</h3>
<ul className="mx_RoomSettings_userLevels">
{Object.keys(user_levels).map(function(user, i) {
return (
<li className="mx_RoomSettings_userLevel" key={user}>
{ user } is a <PowerSelector value={ user_levels[user] } disabled={true}/>
</li>
);
})}
</ul>
</div>;
}
else {
user_levels_section = <div>No users have specific privileges in this room.</div>
}
2016-01-13 13:15:13 +00:00
var banned = this.props.room.getMembersWithMembership("ban");
var banned_users_section;
if (banned.length) {
banned_users_section =
<div>
<h3>Banned users</h3>
2016-01-20 22:47:42 +00:00
<ul className="mx_RoomSettings_banned">
{banned.map(function(member, i) {
return (
2016-01-20 22:47:42 +00:00
<li key={i}>
{member.userId}
2016-01-20 22:47:42 +00:00
</li>
);
})}
2016-01-20 22:47:42 +00:00
</ul>
</div>;
}
var create_event = this.props.room.currentState.getStateEvents('m.room.create', '');
var unfederatable_section;
if (create_event.getContent()["m.federate"] === false) {
2016-01-15 16:33:50 +00:00
unfederatable_section = <div className="mx_RoomSettings_powerLevel">Ths room is not accessible by remote Matrix servers.</div>
}
2016-01-13 13:15:13 +00:00
// TODO: support editing custom events_levels
// TODO: support editing custom user_levels
2016-01-17 05:13:16 +00:00
var tags = [
{ name: "m.favourite", label: "Favourite", ref: "tag_favourite" },
{ name: "m.lowpriority", label: "Low priority", ref: "tag_lowpriority" },
];
Object.keys(this.state.tags).sort().forEach(function(tagName) {
if (tagName !== 'm.favourite' && tagName !== 'm.lowpriority') {
tags.push({ name: tagName, label: tagName });
}
});
var tags_section =
2016-01-17 12:27:21 +00:00
<div className="mx_RoomSettings_tags">
2016-01-20 16:39:26 +00:00
Tagged as:
2016-01-17 05:13:16 +00:00
{ can_set_tag ?
tags.map(function(tag, i) {
return (<label key={ i }>
<input type="checkbox"
ref={ tag.ref }
checked={ tag.name in self.state.tags }
2016-02-05 11:59:19 +00:00
onChange={ self._onTagChange.bind(self, tag.name) }/>
2016-01-17 05:13:16 +00:00
{ tag.label }
</label>);
}) : tags.map(function(tag) { return tag.label; }).join(", ")
}
</div>
// FIXME: disable guests_read if the user hasn't turned on shared history
2015-11-27 10:42:03 +00:00
return (
<div className="mx_RoomSettings">
2016-01-17 05:13:16 +00:00
{ tags_section }
2016-01-18 19:56:38 +00:00
<div className="mx_RoomSettings_toggles">
2016-02-04 15:26:12 +00:00
<label>
2016-02-04 16:18:59 +00:00
<input type="checkbox" onChange={this._onToggle.bind(this, "areNotifsMuted", true, false)} defaultChecked={this.state.areNotifsMuted}/>
2016-02-04 15:26:12 +00:00
Mute notifications for this room
</label>
<label>
2016-02-04 16:18:59 +00:00
<input type="checkbox" onChange={this._onToggle.bind(this, "join_rule", "invite", "public")}
defaultChecked={this.state.join_rule !== "public"}/>
2016-02-04 15:26:12 +00:00
Make this room private
</label>
<label>
2016-02-04 16:18:59 +00:00
<input type="checkbox" ref="share_history"
defaultChecked={this.state.history_visibility === "shared" || this.state.history_visibility === "world_readable"}/>
2016-02-04 15:26:12 +00:00
Share message history with new participants
</label>
<label>
2016-02-04 16:18:59 +00:00
<input type="checkbox" onChange={this._onToggle.bind(this, "guest_access", "can_join", "forbidden")}
defaultChecked={this.state.guest_access === "can_join"}/>
2016-02-04 15:26:12 +00:00
Let guests join this room
</label>
<label>
2016-02-04 16:18:59 +00:00
<input type="checkbox" ref="guests_read" defaultChecked={this.state.history_visibility === "world_readable"}/>
2016-02-04 15:26:12 +00:00
Let users read message history without joining
</label>
<label className="mx_RoomSettings_encrypt">
<input type="checkbox" />
Encrypt room
</label>
2016-01-18 19:56:38 +00:00
</div>
<div>
<h3>Room Colour</h3>
<ColorSettings ref="color_settings" room={this.props.room} />
</div>
2015-11-27 10:42:03 +00:00
<AliasSettings ref="alias_settings"
roomId={this.props.room.roomId}
canSetCanonicalAlias={can_set_canonical_alias}
canSetAliases={can_set_room_aliases}
canonicalAliasEvent={this.props.room.currentState.getStateEvents('m.room.canonical_alias', '')}
aliasEvents={this.props.room.currentState.getStateEvents('m.room.aliases')} />
2015-11-27 10:42:03 +00:00
2016-01-13 13:15:13 +00:00
<h3>Permissions</h3>
<div className="mx_RoomSettings_powerLevels mx_RoomSettings_settings">
<div className="mx_RoomSettings_powerLevel">
2016-01-15 16:33:50 +00:00
<span className="mx_RoomSettings_powerLevelKey">The default role for new room members is </span>
<PowerSelector ref="users_default" value={default_user_level} disabled={!can_change_levels || current_user_level < default_user_level} onChange={this.onPowerLevelsChanged}/>
2016-01-13 13:15:13 +00:00
</div>
<div className="mx_RoomSettings_powerLevel">
2016-01-15 16:33:50 +00:00
<span className="mx_RoomSettings_powerLevelKey">To send messages, you must be a </span>
<PowerSelector ref="events_default" value={send_level} disabled={!can_change_levels || current_user_level < send_level} onChange={this.onPowerLevelsChanged}/>
2016-01-13 13:15:13 +00:00
</div>
<div className="mx_RoomSettings_powerLevel">
2016-01-15 16:33:50 +00:00
<span className="mx_RoomSettings_powerLevelKey">To invite users into the room, you must be a </span>
<PowerSelector ref="invite" value={invite_level} disabled={!can_change_levels || current_user_level < invite_level} onChange={this.onPowerLevelsChanged}/>
2016-01-13 13:15:13 +00:00
</div>
<div className="mx_RoomSettings_powerLevel">
2016-01-15 16:33:50 +00:00
<span className="mx_RoomSettings_powerLevelKey">To configure the room, you must be a </span>
<PowerSelector ref="state_default" value={state_level} disabled={!can_change_levels || current_user_level < state_level} onChange={this.onPowerLevelsChanged}/>
2016-01-13 13:15:13 +00:00
</div>
<div className="mx_RoomSettings_powerLevel">
2016-01-15 16:33:50 +00:00
<span className="mx_RoomSettings_powerLevelKey">To kick users, you must be a </span>
<PowerSelector ref="kick" value={kick_level} disabled={!can_change_levels || current_user_level < kick_level} onChange={this.onPowerLevelsChanged}/>
2016-01-13 13:15:13 +00:00
</div>
<div className="mx_RoomSettings_powerLevel">
2016-01-15 16:33:50 +00:00
<span className="mx_RoomSettings_powerLevelKey">To ban users, you must be a </span>
<PowerSelector ref="ban" value={ban_level} disabled={!can_change_levels || current_user_level < ban_level} onChange={this.onPowerLevelsChanged}/>
2016-01-13 13:15:13 +00:00
</div>
<div className="mx_RoomSettings_powerLevel">
2016-01-15 16:33:50 +00:00
<span className="mx_RoomSettings_powerLevelKey">To redact messages, you must be a </span>
<PowerSelector ref="redact" value={redact_level} disabled={!can_change_levels || current_user_level < redact_level} onChange={this.onPowerLevelsChanged}/>
2016-01-13 13:15:13 +00:00
</div>
{Object.keys(events_levels).map(function(event_type, i) {
2015-11-27 10:42:03 +00:00
return (
2016-01-13 13:15:13 +00:00
<div className="mx_RoomSettings_powerLevel" key={event_type}>
2016-01-15 16:33:50 +00:00
<span className="mx_RoomSettings_powerLevelKey">To send events of type <code>{ event_type }</code>, you must be a </span>
<PowerSelector value={ events_levels[event_type] } disabled={true} onChange={self.onPowerLevelsChanged}/>
2015-11-27 10:42:03 +00:00
</div>
);
})}
2016-01-15 16:33:50 +00:00
{ unfederatable_section }
2015-11-27 10:42:03 +00:00
</div>
2016-01-20 22:47:42 +00:00
{ user_levels_section }
2016-01-13 13:15:13 +00:00
{ banned_users_section }
2016-01-13 13:15:13 +00:00
<h3>Advanced</h3>
<div className="mx_RoomSettings_settings">
This room's internal ID is <code>{ this.props.room.roomId }</code>
</div>
2015-11-27 10:42:03 +00:00
</div>
);
}
});