make roomsettings actually work, including alias editing. (using staged UX rather than direct manipulation). loads of tweaks and fixes across the board

This commit is contained in:
Matthew Hodgson 2016-01-17 02:48:55 +00:00
parent 90184235bb
commit 82a159d651
4 changed files with 369 additions and 88 deletions

View file

@ -66,7 +66,7 @@ function textForMemberEvent(ev) {
function textForTopicEvent(ev) { function textForTopicEvent(ev) {
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
return senderDisplayName + ' changed the topic to, "' + ev.getContent().topic + '"'; return senderDisplayName + ' changed the topic to "' + ev.getContent().topic + '"';
}; };
function textForRoomNameEvent(ev) { function textForRoomNameEvent(ev) {

View file

@ -887,6 +887,24 @@ module.exports = React.createClass({
old_history_visibility = "shared"; old_history_visibility = "shared";
} }
var old_guest_read = (old_history_visibility === "world_readable");
var old_guest_join = this.state.room.currentState.getStateEvents('m.room.guest_access', '');
if (old_guest_join) {
old_guest_join = (old_guest_join.getContent().guest_access === "can_join");
}
else {
old_guest_join = (old_guest_join.getContent().guest_access === "forbidden");
}
var old_canonical_alias = this.state.room.currentState.getStateEvents('m.room.canonical_alias', '');
if (old_canonical_alias) {
old_canonical_alias = old_canonical_alias.getContent().alias;
}
else {
old_canonical_alias = "";
}
var deferreds = []; var deferreds = [];
if (old_name != newVals.name && newVals.name != undefined) { if (old_name != newVals.name && newVals.name != undefined) {
@ -930,6 +948,49 @@ module.exports = React.createClass({
); );
} }
if (newVals.alias_operations) {
var oplist = [];
for (var i = 0; i < newVals.alias_operations.length; i++) {
var alias_operation = newVals.alias_operations[i];
switch (alias_operation.type) {
case 'put':
oplist.push(
MatrixClientPeg.get().createAlias(
alias_operation.alias, this.state.room.roomId
)
);
break;
case 'delete':
oplist.push(
MatrixClientPeg.get().deleteAlias(
alias_operation.alias
)
);
break;
default:
console.log("Unknown alias operation, ignoring: " + alias_operation.type);
}
}
if (oplist.length) {
var deferred = oplist[0];
oplist.splice(1).forEach(function (f) {
deferred = deferred.then(f);
});
deferreds.push(deferred);
}
}
if (old_canonical_alias !== newVals.canonical_alias) {
deferreds.push(
MatrixClientPeg.get().sendStateEvent(
this.state.room.roomId, "m.room.canonical_alias", {
alias: newVals.canonical_alias
}, ""
)
);
}
if (newVals.color_scheme) { if (newVals.color_scheme) {
deferreds.push( deferreds.push(
MatrixClientPeg.get().setRoomAccountData( MatrixClientPeg.get().setRoomAccountData(
@ -938,21 +999,38 @@ module.exports = React.createClass({
); );
} }
if (old_guest_read != newVals.guest_read ||
old_guest_join != newVals.guest_join)
{
deferreds.push( deferreds.push(
MatrixClientPeg.get().setGuestAccess(this.state.room.roomId, { MatrixClientPeg.get().setGuestAccess(this.state.room.roomId, {
allowRead: newVals.guest_read, allowRead: newVals.guest_read,
allowJoin: newVals.guest_join allowJoin: newVals.guest_join
}) })
); );
}
if (deferreds.length) { if (deferreds.length) {
var self = this; var self = this;
q.all(deferreds).fail(function(err) { q.allSettled(deferreds).then(
function(results) {
var fails = results.filter(function(result) { return result.state !== "fulfilled" });
if (fails.length) {
fails.forEach(function(result) {
console.error(result.reason);
});
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Failed to set state", title: "Failed to set state",
description: err.toString() description: fails.map(function(result) { return result.reason }).join("\n"),
}); });
self.refs.room_settings.resetState();
}
else {
self.setState({
editingRoomSettings: false
});
}
}).finally(function() { }).finally(function() {
self.setState({ self.setState({
uploadingRoomSettings: false, uploadingRoomSettings: false,
@ -1025,16 +1103,17 @@ module.exports = React.createClass({
onSaveClick: function() { onSaveClick: function() {
this.setState({ this.setState({
editingRoomSettings: false,
uploadingRoomSettings: true, uploadingRoomSettings: true,
}); });
this.uploadNewState({ this.uploadNewState({
name: this.refs.header.getRoomName(), name: this.refs.header.getRoomName(),
topic: this.refs.room_settings.getTopic(), topic: this.refs.header.getTopic(),
join_rule: this.refs.room_settings.getJoinRules(), join_rule: this.refs.room_settings.getJoinRules(),
history_visibility: this.refs.room_settings.getHistoryVisibility(), history_visibility: this.refs.room_settings.getHistoryVisibility(),
power_levels: this.refs.room_settings.getPowerLevels(), power_levels: this.refs.room_settings.getPowerLevels(),
alias_operations: this.refs.room_settings.getAliasOperations(),
canonical_alias: this.refs.room_settings.getCanonicalAlias(),
guest_join: this.refs.room_settings.canGuestsJoin(), guest_join: this.refs.room_settings.canGuestsJoin(),
guest_read: this.refs.room_settings.canGuestsRead(), guest_read: this.refs.room_settings.canGuestsRead(),
color_scheme: this.refs.room_settings.getColorScheme(), color_scheme: this.refs.room_settings.getColorScheme(),

View file

@ -71,6 +71,13 @@ module.exports = React.createClass({
} }
}, },
componentDidUpdate: function(newProps) {
this.value = this.props.initialValue;
if (this.refs.editable_div) {
this.showPlaceholder(!this.value);
}
},
showPlaceholder: function(show) { showPlaceholder: function(show) {
if (show) { if (show) {
this.refs.editable_div.textContent = this.props.placeholder; this.refs.editable_div.textContent = this.props.placeholder;
@ -89,6 +96,11 @@ module.exports = React.createClass({
return this.value; return this.value;
}, },
setValue: function(value) {
this.value = value;
this.showPlaceholder(!this.value);
},
edit: function() { edit: function() {
this.setState({ this.setState({
phase: this.Phases.Edit, phase: this.Phases.Edit,
@ -99,6 +111,8 @@ module.exports = React.createClass({
this.setState({ this.setState({
phase: this.Phases.Display, phase: this.Phases.Display,
}); });
this.value = this.props.initialValue;
this.showPlaceholder(!this.value);
this.onValueChanged(false); this.onValueChanged(false);
}, },
@ -110,10 +124,8 @@ module.exports = React.createClass({
// console.log("keyDown: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder); // console.log("keyDown: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
if (this.placeholder) { if (this.placeholder) {
if (ev.keyCode !== KEY_SHIFT && !ev.metaKey && !ev.ctrlKey && !ev.altKey && ev.keyCode !== KEY_WINDOWS && ev.keyCode !== KEY_TAB) {
this.showPlaceholder(false); this.showPlaceholder(false);
} }
}
if (ev.key == "Enter") { if (ev.key == "Enter") {
ev.stopPropagation(); ev.stopPropagation();
@ -182,7 +194,9 @@ module.exports = React.createClass({
if (this.props.blurToCancel) if (this.props.blurToCancel)
this.cancelEdit(); this.cancelEdit();
else else
this.onFinish(ev) this.onFinish(ev);
this.showPlaceholder(!this.value);
}, },
render: function() { render: function() {

View file

@ -18,6 +18,7 @@ var React = require('react');
var MatrixClientPeg = require('../../../MatrixClientPeg'); var MatrixClientPeg = require('../../../MatrixClientPeg');
var Tinter = require('../../../Tinter'); var Tinter = require('../../../Tinter');
var sdk = require('../../../index'); var sdk = require('../../../index');
var Modal = require('../../../Modal');
var room_colors = [ var room_colors = [
// magic room default values courtesy of Ribot // magic room default values courtesy of Ribot
@ -40,6 +41,9 @@ module.exports = React.createClass({
room: React.PropTypes.object.isRequired, room: React.PropTypes.object.isRequired,
}, },
componentDidMount: function() {
},
getInitialState: function() { getInitialState: function() {
// work out the initial color index // work out the initial color index
var room_color_index = undefined; var room_color_index = undefined;
@ -68,15 +72,28 @@ module.exports = React.createClass({
room_color_index = 0; room_color_index = 0;
} }
// get the aliases
var aliases = {};
var domain = MatrixClientPeg.get().getDomain();
var alias_events = this.props.room.currentState.getStateEvents('m.room.aliases');
for (var i = 0; i < alias_events.length; i++) {
aliases[alias_events[i].getStateKey()] = alias_events[i].getContent().aliases.slice(); // shallow copy
}
aliases[domain] = aliases[domain] || [];
return { return {
power_levels_changed: false, power_levels_changed: false,
color_scheme_changed: false, color_scheme_changed: false,
color_scheme_index: room_color_index, color_scheme_index: room_color_index,
aliases_changed: false, aliases_changed: false,
aliases: [], aliases: aliases,
}; };
}, },
resetState: function() {
this.set.state(this.getInitialState());
},
canGuestsJoin: function() { canGuestsJoin: function() {
return this.refs.guests_join.checked; return this.refs.guests_join.checked;
}, },
@ -104,13 +121,13 @@ module.exports = React.createClass({
power_levels = power_levels.getContent(); power_levels = power_levels.getContent();
var new_power_levels = { var new_power_levels = {
ban: parseInt(this.refs.ban.value), ban: parseInt(this.refs.ban.getValue()),
kick: parseInt(this.refs.kick.value), kick: parseInt(this.refs.kick.getValue()),
redact: parseInt(this.refs.redact.value), redact: parseInt(this.refs.redact.getValue()),
invite: parseInt(this.refs.invite.value), invite: parseInt(this.refs.invite.getValue()),
events_default: parseInt(this.refs.events_default.value), events_default: parseInt(this.refs.events_default.getValue()),
state_default: parseInt(this.refs.state_default.value), state_default: parseInt(this.refs.state_default.getValue()),
users_default: parseInt(this.refs.users_default.value), users_default: parseInt(this.refs.users_default.getValue()),
users: power_levels.users, users: power_levels.users,
events: power_levels.events, events: power_levels.events,
}; };
@ -118,6 +135,75 @@ module.exports = React.createClass({
return new_power_levels; return new_power_levels;
}, },
getCanonicalAlias: function() {
return this.refs.canonical_alias ? this.refs.canonical_alias.value : "";
},
getAliasOperations: function() {
if (!this.state.aliases_changed) return undefined;
// work out the delta from room state to UI state
var ops = [];
// calculate original ("old") aliases
var oldAliases = {};
var aliases = this.state.aliases;
var alias_events = this.props.room.currentState.getStateEvents('m.room.aliases');
for (var i = 0; i < alias_events.length; i++) {
var domain = alias_events[i].getStateKey();
oldAliases[domain] = alias_events[i].getContent().aliases.slice(); // shallow copy
}
// work out whether any domains have entirely disappeared or appeared
var domainDelta = {}
Object.keys(oldAliases).forEach(function(domain) {
domainDelta[domain] = domainDelta[domain] || 0;
domainDelta[domain]--;
});
Object.keys(aliases).forEach(function(domain) {
domainDelta[domain] = domainDelta[domain] || 0;
domainDelta[domain]++;
});
Object.keys(domainDelta).forEach(function(domain) {
switch (domainDelta[domain]) {
case 1: // entirely new domain
aliases[domain].forEach(function(alias) {
ops.push({ type: "put", alias : alias });
});
break;
case -1: // entirely removed domain
oldAliases[domain].forEach(function(alias) {
ops.push({ type: "delete", alias : alias });
});
break;
case 0: // mix of aliases in this domain.
// compare old & new aliases for this domain
var delta = {};
oldAliases[domain].forEach(function(item) {
delta[item] = delta[item] || 0;
delta[item]--;
});
aliases[domain].forEach(function(item) {
delta[item] = delta[item] || 0;
delta[item]++;
});
Object.keys(delta).forEach(function(alias) {
if (delta[alias] == 1) {
ops.push({ type: "put", alias: alias });
} else if (delta[alias] == -1) {
ops.push({ type: "delete", alias: alias });
}
});
break;
}
});
return ops;
},
onPowerLevelsChanged: function() { onPowerLevelsChanged: function() {
this.setState({ this.setState({
power_levels_changed: true power_levels_changed: true
@ -143,16 +229,66 @@ module.exports = React.createClass({
}); });
}, },
onAliasChanged: function(i, j) { onAliasChanged: function(domain, index, alias) {
if (alias === "") return; // hit the delete button to delete please
var oldAlias;
if (this.isAliasValid(alias)) {
oldAlias = this.state.aliases[domain][index];
this.state.aliases[domain][index] = alias;
this.setState({ aliases_changed : true });
}
else {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Invalid alias format",
description: "'" + alias + "' is not a valid format for an alias",
});
}
}, },
onAliasDeleted: function(i, j) { onAliasDeleted: function(domain, index) {
// XXX: can we edit state directly and then set, or should we copy it first?
var alias = this.state.aliases[domain].splice(index, 1);
this.setState({
aliases: this.state.aliases
});
this.setState({ aliases_changed : true });
}, },
onAliasAdded: function(i, j) { onAliasAdded: function(alias) {
if (alias === "") return; // ignore attempts to create blank aliases
if (alias === undefined) {
alias = this.refs.add_alias ? this.refs.add_alias.getValue() : undefined;
if (alias === undefined || alias === "") return;
}
if (this.isAliasValid(alias)) {
var domain = alias.replace(/^.*?:/, '');
// XXX: do we need to deep copy aliases before editing it?
this.state.aliases[domain] = this.state.aliases[domain] || [];
this.state.aliases[domain].push(alias);
this.setState({
aliases: this.state.aliases
});
// reset the add field
this.refs.add_alias.setValue('');
this.setState({ aliases_changed : true });
}
else {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Invalid alias format",
description: "'" + alias + "' is not a valid format for an alias",
});
}
},
isAliasValid: function(alias) {
// XXX: FIXME SPEC-1
return (alias.match(/^#([^\/:,]+?):(.+)$/) && encodeURI(alias) === alias);
}, },
render: function() { render: function() {
@ -223,73 +359,117 @@ module.exports = React.createClass({
var can_change_levels = false; var can_change_levels = false;
} }
var room_avatar_level = parseInt(power_levels.state_default || 0); var room_aliases_level = parseInt(power_levels.state_default || 0);
if (events_levels['m.room.avatar'] !== undefined) { if (events_levels['m.room.aliases'] !== undefined) {
room_avatar_level = events_levels['m.room.avatar']; room_avatar_level = events_levels['m.room.aliases'];
} }
var can_set_room_avatar = current_user_level >= room_avatar_level; var can_set_room_aliases = current_user_level >= room_aliases_level;
var canonical_alias_level = parseInt(power_levels.state_default || 0);
if (events_levels['m.room.canonical_alias'] !== undefined) {
room_avatar_level = events_levels['m.room.canonical_alias'];
}
var can_set_canonical_alias = current_user_level >= canonical_alias_level;
var self = this; var self = this;
var alias_events = this.props.room.currentState.getStateEvents('m.room.aliases');
var canonical_alias_event = this.props.room.currentState.getStateEvents('m.room.canonical_alias', ''); var canonical_alias_event = this.props.room.currentState.getStateEvents('m.room.canonical_alias', '');
var canonical_alias = canonical_alias_event ? canonical_alias_event.getContent().alias : ""; var canonical_alias = canonical_alias_event ? canonical_alias_event.getContent().alias : "";
var domain = MatrixClientPeg.get().getDomain(); var domain = MatrixClientPeg.get().getDomain();
var aliases_section = var remote_domains = Object.keys(this.state.aliases).filter(function(alias) { return alias !== domain });
var remote_aliases_section;
if (remote_domains.length) {
remote_aliases_section =
<div> <div>
<h3>Directory</h3>
<div className="mx_RoomSettings_aliasLabel"> <div className="mx_RoomSettings_aliasLabel">
{ alias_events.length ? "This room is accessible via:" : "This room has no aliases." } This room can be found elsewhere as:
</div> </div>
<div className="mx_RoomSettings_aliasesTable"> <div className="mx_RoomSettings_aliasesTable">
{ alias_events.map(function(alias_event, i) { { remote_domains.map(function(state_key, i) {
return alias_event.getContent().aliases.map(function(alias, j) { self.state.aliases[state_key].map(function(alias, j) {
var deleteButton;
if (alias_event && alias_event.getStateKey() === domain) {
deleteButton = <img src="img/cancel-small.svg" width="14" height="14" alt="Delete" onClick={ self.onAliasDeleted.bind(self, i, j) }/>;
}
return ( return (
<div className="mx_RoomSettings_aliasesTableRow" key={ i + "_" + j }> <div className="mx_RoomSettings_aliasesTableRow" key={ i + "_" + j }>
<EditableText <EditableText
className="mx_RoomSettings_alias mx_RoomSettings_editable" className="mx_RoomSettings_alias mx_RoomSettings_editable"
placeholderClassName="mx_RoomSettings_aliasPlaceholder"
placeholder={ "New alias (e.g. #foo:" + domain + ")" }
blurToCancel={ false } blurToCancel={ false }
onValueChanged={ self.onAliasChanged.bind(self, i, j) } editable={ false }
editable={ alias_event && alias_event.getStateKey() === domain } initialValue={ alias } />
initialValue={ alias }
/>
<div className="mx_RoomSettings_deleteAlias"> <div className="mx_RoomSettings_deleteAlias">
{ deleteButton }
</div> </div>
</div> </div>
); );
}); });
})} })}
</div>
</div>
}
var canonical_alias_section;
if (can_set_canonical_alias) {
canonical_alias_section =
<select ref="canonical_alias" defaultValue={ canonical_alias }>
{ Object.keys(self.state.aliases).map(function(stateKey, i) {
return self.state.aliases[stateKey].map(function(alias, j) {
return <option value={ alias } key={ i + "_" + j }>{ alias }</option>
});
})}
<option value="" key="unset">not set</option>
</select>
}
else {
canonical_alias_section = <b>{ canonical_alias || "not set" }</b>;
}
var aliases_section =
<div>
<h3>Directory</h3>
<div className="mx_RoomSettings_aliasLabel">
{ this.state.aliases[domain].length
? "This room can be found on " + domain + " as:"
: "This room is not findable on " + domain }
</div>
<div className="mx_RoomSettings_aliasesTable">
{ this.state.aliases[domain].map(function(alias, i) {
var deleteButton;
if (can_set_room_aliases) {
deleteButton = <img src="img/cancel-small.svg" width="14" height="14" alt="Delete" onClick={ self.onAliasDeleted.bind(self, domain, i) }/>;
}
return (
<div className="mx_RoomSettings_aliasesTableRow" key={ i }>
<EditableText
className="mx_RoomSettings_alias mx_RoomSettings_editable"
placeholderClassName="mx_RoomSettings_aliasPlaceholder"
placeholder={ "New alias (e.g. #foo:" + domain + ")" }
blurToCancel={ false }
onValueChanged={ self.onAliasChanged.bind(self, domain, i) }
editable={ can_set_room_aliases }
initialValue={ alias } />
<div className="mx_RoomSettings_deleteAlias">
{ deleteButton }
</div>
</div>
);
})}
<div className="mx_RoomSettings_aliasesTableRow" key="new"> <div className="mx_RoomSettings_aliasesTableRow" key="new">
<EditableText <EditableText
ref="add_alias"
className="mx_RoomSettings_alias mx_RoomSettings_editable" className="mx_RoomSettings_alias mx_RoomSettings_editable"
placeholderClassName="mx_RoomSettings_aliasPlaceholder" placeholderClassName="mx_RoomSettings_aliasPlaceholder"
placeholder={ "New alias (e.g. #foo:" + domain + ")" } placeholder={ "New alias (e.g. #foo:" + domain + ")" }
blurToCancel={ false } blurToCancel={ false }
onValueChanged={ self.onAliasAdded } /> onValueChanged={ self.onAliasAdded } />
<div className="mx_RoomSettings_addAlias"> <div className="mx_RoomSettings_addAlias">
<img src="img/plus.svg" width="14" height="14" alt="Add" onClick={ self.onAliasAdded }/> <img src="img/plus.svg" width="14" height="14" alt="Add" onClick={ self.onAliasAdded.bind(self, undefined) }/>
</div> </div>
</div> </div>
</div> </div>
<div className="mx_RoomSettings_aliasLabel">The canonical entry is&nbsp;
<select defaultValue={ canonical_alias }> { remote_aliases_section }
{ alias_events.map(function(alias_event, i) {
return alias_event.getContent().aliases.map(function(alias, j) { <div className="mx_RoomSettings_aliasLabel">The official way to refer to this room is: { canonical_alias_section }</div>
return <option value={ alias } key={ i + "_" + j }>{ alias }</option>
});
})}
<option value="" key="unset">not set</option>
</select>
</div>
</div>; </div>;
var room_colors_section = var room_colors_section =
@ -337,6 +517,9 @@ module.exports = React.createClass({
</div> </div>
</div>; </div>;
} }
else {
user_levels_section = <div>No users have specific privileges in this room.</div>
}
var banned = this.props.room.getMembersWithMembership("ban"); var banned = this.props.room.getMembersWithMembership("ban");
var banned_users_section; var banned_users_section;
@ -381,38 +564,38 @@ module.exports = React.createClass({
<div className="mx_RoomSettings_powerLevels mx_RoomSettings_settings"> <div className="mx_RoomSettings_powerLevels mx_RoomSettings_settings">
<div className="mx_RoomSettings_powerLevel"> <div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">The default role for new room members is </span> <span className="mx_RoomSettings_powerLevelKey">The default role for new room members is </span>
<PowerSelector value={default_user_level} disabled={!can_change_levels || current_user_level < default_user_level} onChange={this.onPowerLevelsChanged}/> <PowerSelector ref="users_default" value={default_user_level} disabled={!can_change_levels || current_user_level < default_user_level} onChange={this.onPowerLevelsChanged}/>
</div> </div>
<div className="mx_RoomSettings_powerLevel"> <div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">To send messages, you must be a </span> <span className="mx_RoomSettings_powerLevelKey">To send messages, you must be a </span>
<PowerSelector value={send_level} disabled={!can_change_levels || current_user_level < send_level} onChange={this.onPowerLevelsChanged}/> <PowerSelector ref="events_default" value={send_level} disabled={!can_change_levels || current_user_level < send_level} onChange={this.onPowerLevelsChanged}/>
</div> </div>
<div className="mx_RoomSettings_powerLevel"> <div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">To invite users into the room, you must be a </span> <span className="mx_RoomSettings_powerLevelKey">To invite users into the room, you must be a </span>
<PowerSelector value={invite_level} disabled={!can_change_levels || current_user_level < invite_level} onChange={this.onPowerLevelsChanged}/> <PowerSelector ref="invite" value={invite_level} disabled={!can_change_levels || current_user_level < invite_level} onChange={this.onPowerLevelsChanged}/>
</div> </div>
<div className="mx_RoomSettings_powerLevel"> <div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">To configure the room, you must be a </span> <span className="mx_RoomSettings_powerLevelKey">To configure the room, you must be a </span>
<PowerSelector value={state_level} disabled={!can_change_levels || current_user_level < state_level} onChange={this.onPowerLevelsChanged}/> <PowerSelector ref="state_default" value={state_level} disabled={!can_change_levels || current_user_level < state_level} onChange={this.onPowerLevelsChanged}/>
</div> </div>
<div className="mx_RoomSettings_powerLevel"> <div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">To kick users, you must be a </span> <span className="mx_RoomSettings_powerLevelKey">To kick users, you must be a </span>
<PowerSelector value={kick_level} disabled={!can_change_levels || current_user_level < kick_level} onChange={this.onPowerLevelsChanged}/> <PowerSelector ref="kick" value={kick_level} disabled={!can_change_levels || current_user_level < kick_level} onChange={this.onPowerLevelsChanged}/>
</div> </div>
<div className="mx_RoomSettings_powerLevel"> <div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">To ban users, you must be a </span> <span className="mx_RoomSettings_powerLevelKey">To ban users, you must be a </span>
<PowerSelector value={ban_level} disabled={!can_change_levels || current_user_level < ban_level} onChange={this.onPowerLevelsChanged}/> <PowerSelector ref="ban" value={ban_level} disabled={!can_change_levels || current_user_level < ban_level} onChange={this.onPowerLevelsChanged}/>
</div> </div>
<div className="mx_RoomSettings_powerLevel"> <div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">To redact messages, you must be a </span> <span className="mx_RoomSettings_powerLevelKey">To redact messages, you must be a </span>
<PowerSelector value={redact_level} disabled={!can_change_levels || current_user_level < redact_level} onChange={this.onPowerLevelsChanged}/> <PowerSelector ref="redact" value={redact_level} disabled={!can_change_levels || current_user_level < redact_level} onChange={this.onPowerLevelsChanged}/>
</div> </div>
{Object.keys(events_levels).map(function(event_type, i) { {Object.keys(events_levels).map(function(event_type, i) {
return ( return (
<div className="mx_RoomSettings_powerLevel" key={event_type}> <div className="mx_RoomSettings_powerLevel" key={event_type}>
<span className="mx_RoomSettings_powerLevelKey">To send events of type <code>{ event_type }</code>, you must be a </span> <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={this.onPowerLevelsChanged}/> <PowerSelector value={ events_levels[event_type] } disabled={true} onChange={self.onPowerLevelsChanged}/>
</div> </div>
); );
})} })}
@ -431,6 +614,11 @@ module.exports = React.createClass({
{ banned_users_section } { banned_users_section }
<h3>Advanced</h3>
<div className="mx_RoomSettings_settings">
This room's internal ID is <code>{ this.props.room.roomId }</code>
</div>
</div> </div>
); );
} }