diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js new file mode 100644 index 0000000000..76724d88fd --- /dev/null +++ b/src/components/views/rooms/RoomHeader.js @@ -0,0 +1,273 @@ +/* +Copyright 2015 OpenMarket Ltd + +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. +*/ + +'use strict'; + +/* + * State vars: + * this.state.call_state = the UI state of the call (see CallHandler) + */ + +var React = require('react'); +var sdk = require('../../../index'); +var dis = require("../../../dispatcher"); +var CallHandler = require("../../../CallHandler"); +var MatrixClientPeg = require('../../../MatrixClientPeg'); + +module.exports = React.createClass({ + displayName: 'RoomHeader', + + propTypes: { + room: React.PropTypes.object, + editing: React.PropTypes.bool, + onSettingsClick: React.PropTypes.func, + onSaveClick: React.PropTypes.func, + }, + + getDefaultProps: function() { + return { + editing: false, + onSettingsClick: function() {}, + onSaveClick: function() {}, + }; + }, + + componentDidMount: function() { + this.dispatcherRef = dis.register(this.onAction); + if (this.props.room) { + var call = CallHandler.getCallForRoom(this.props.room.roomId); + var callState = call ? call.call_state : "ended"; + this.setState({ + call_state: callState + }); + } + }, + + componentWillUnmount: function() { + dis.unregister(this.dispatcherRef); + }, + + onAction: function(payload) { + // don't filter out payloads for room IDs other than props.room because + // we may be interested in the conf 1:1 room + if (payload.action !== 'call_state' || !payload.room_id) { + return; + } + var call = CallHandler.getCallForRoom(payload.room_id); + var callState = call ? call.call_state : "ended"; + this.setState({ + call_state: callState + }); + }, + + onVideoClick: function(e) { + dis.dispatch({ + action: 'place_call', + type: e.shiftKey ? "screensharing" : "video", + room_id: this.props.room.roomId + }); + }, + onVoiceClick: function() { + dis.dispatch({ + action: 'place_call', + type: "voice", + room_id: this.props.room.roomId + }); + }, + onHangupClick: function() { + var call = CallHandler.getCallForRoom(this.props.room.roomId); + if (!call) { return; } + dis.dispatch({ + action: 'hangup', + // hangup the call for this room, which may not be the room in props + // (e.g. conferences which will hangup the 1:1 room instead) + room_id: call.roomId + }); + }, + onMuteAudioClick: function() { + var call = CallHandler.getCallForRoom(this.props.room.roomId); + if (!call) { + return; + } + var newState = !call.isMicrophoneMuted(); + call.setMicrophoneMuted(newState); + this.setState({ + audioMuted: newState + }); + }, + onMuteVideoClick: function() { + var call = CallHandler.getCallForRoom(this.props.room.roomId); + if (!call) { + return; + } + var newState = !call.isLocalVideoMuted(); + call.setLocalVideoMuted(newState); + this.setState({ + videoMuted: newState + }); + }, + + onNameChange: function(new_name) { + if (this.props.room.name != new_name && new_name) { + MatrixClientPeg.get().setRoomName(this.props.room.roomId, new_name); + } + }, + + getRoomName: function() { + return this.refs.name_edit.value; + }, + + onFullscreenClick: function() { + dis.dispatch({action: 'video_fullscreen', fullscreen: true}, true); + }, + + render: function() { + var EditableText = sdk.getComponent("elements.EditableText"); + var RoomAvatar = sdk.getComponent('avatars.RoomAvatar'); + + var header; + if (this.props.simpleHeader) { + header = + <div className="mx_RoomHeader_wrapper"> + <div className="mx_RoomHeader_simpleHeader"> + { this.props.simpleHeader } + </div> + </div> + } + else { + var topic = this.props.room.currentState.getStateEvents('m.room.topic', ''); + + var call_buttons; + if (this.state && this.state.call_state != 'ended') { + //var muteVideoButton; + var activeCall = ( + CallHandler.getCallForRoom(this.props.room.roomId) + ); +/* + if (activeCall && activeCall.type === "video") { + muteVideoButton = ( + <div className="mx_RoomHeader_textButton mx_RoomHeader_voipButton" + onClick={this.onMuteVideoClick}> + { + (activeCall.isLocalVideoMuted() ? + "Unmute" : "Mute") + " video" + } + </div> + ); + } + {muteVideoButton} + <div className="mx_RoomHeader_textButton mx_RoomHeader_voipButton" + onClick={this.onMuteAudioClick}> + { + (activeCall && activeCall.isMicrophoneMuted() ? + "Unmute" : "Mute") + " audio" + } + </div> +*/ + + call_buttons = ( + <div className="mx_RoomHeader_textButton" + onClick={this.onHangupClick}> + End call + </div> + ); + } + + var name = null; + var topic_el = null; + var cancel_button = null; + var save_button = null; + var settings_button = null; + var actual_name = this.props.room.currentState.getStateEvents('m.room.name', ''); + if (actual_name) actual_name = actual_name.getContent().name; + if (this.props.editing) { + name = + <div className="mx_RoomHeader_nameEditing"> + <input className="mx_RoomHeader_nameInput" type="text" defaultValue={actual_name} placeholder="Name" ref="name_edit"/> + </div> + // if (topic) topic_el = <div className="mx_RoomHeader_topic"><textarea>{ topic.getContent().topic }</textarea></div> + cancel_button = <div className="mx_RoomHeader_textButton" onClick={this.props.onCancelClick}>Cancel</div> + save_button = <div className="mx_RoomHeader_textButton" onClick={this.props.onSaveClick}>Save Changes</div> + } else { + // <EditableText label={this.props.room.name} initialValue={actual_name} placeHolder="Name" onValueChanged={this.onNameChange} /> + name = + <div className="mx_RoomHeader_name" onClick={this.props.onSettingsClick}> + <div className="mx_RoomHeader_nametext">{ this.props.room.name }</div> + <div className="mx_RoomHeader_settingsButton"> + <img src="img/settings.png" width="12" height="12"/> + </div> + </div> + if (topic) topic_el = <div className="mx_RoomHeader_topic" title={topic.getContent().topic}>{ topic.getContent().topic }</div>; + } + + var roomAvatar = null; + if (this.props.room) { + roomAvatar = ( + <RoomAvatar room={this.props.room} width="48" height="48" /> + ); + } + + var zoom_button, video_button, voice_button; + if (activeCall) { + if (activeCall.type == "video") { + zoom_button = ( + <div className="mx_RoomHeader_button" onClick={this.onFullscreenClick}> + <img src="img/zoom.png" title="Fullscreen" alt="Fullscreen" width="32" height="32" style={{ 'marginTop': '-5px' }}/> + </div> + ); + } + video_button = + <div className="mx_RoomHeader_button mx_RoomHeader_video" onClick={activeCall && activeCall.type === "video" ? this.onMuteVideoClick : this.onVideoClick}> + <img src="img/video.png" title="Video call" alt="Video call" width="32" height="32" style={{ 'marginTop': '-8px' }}/> + </div>; + voice_button = + <div className="mx_RoomHeader_button mx_RoomHeader_voice" onClick={activeCall ? this.onMuteAudioClick : this.onVoiceClick}> + <img src="img/voip.png" title="VoIP call" alt="VoIP call" width="32" height="32" style={{ 'marginTop': '-8px' }}/> + </div>; + } + + header = + <div className="mx_RoomHeader_wrapper"> + <div className="mx_RoomHeader_leftRow"> + <div className="mx_RoomHeader_avatar"> + { roomAvatar } + </div> + <div className="mx_RoomHeader_info"> + { name } + { topic_el } + </div> + </div> + {call_buttons} + {cancel_button} + {save_button} + <div className="mx_RoomHeader_rightRow"> + { video_button } + { voice_button } + { zoom_button } + <div className="mx_RoomHeader_button"> + <img src="img/search.png" title="Search" alt="Search" width="21" height="19" onClick={this.props.onSearchClick}/> + </div> + </div> + </div> + } + + return ( + <div className="mx_RoomHeader"> + { header } + </div> + ); + }, +}); diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js new file mode 100644 index 0000000000..eb9bfd90c8 --- /dev/null +++ b/src/components/views/rooms/RoomSettings.js @@ -0,0 +1,237 @@ +/* +Copyright 2015 OpenMarket Ltd + +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 React = require('react'); +var MatrixClientPeg = require('../../../MatrixClientPeg'); +var sdk = require('../../../index'); + +module.exports = React.createClass({ + displayName: 'RoomSettings', + + propTypes: { + room: React.PropTypes.object.isRequired, + }, + + getInitialState: function() { + return { + power_levels_changed: false + }; + }, + + getTopic: function() { + return this.refs.topic.value; + }, + + getJoinRules: function() { + return this.refs.is_private.checked ? "invite" : "public"; + }, + + getHistoryVisibility: function() { + return this.refs.share_history.checked ? "shared" : "invited"; + }, + + getPowerLevels: function() { + 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.value), + kick: parseInt(this.refs.kick.value), + redact: parseInt(this.refs.redact.value), + invite: parseInt(this.refs.invite.value), + events_default: parseInt(this.refs.events_default.value), + state_default: parseInt(this.refs.state_default.value), + users_default: parseInt(this.refs.users_default.value), + users: power_levels.users, + events: power_levels.events, + }; + + return new_power_levels; + }, + + onPowerLevelsChanged: function() { + this.setState({ + power_levels_changed: true + }); + }, + + render: function() { + var ChangeAvatar = sdk.getComponent('settings.ChangeAvatar'); + + var topic = this.props.room.currentState.getStateEvents('m.room.topic', ''); + if (topic) topic = topic.getContent().topic; + + var join_rule = this.props.room.currentState.getStateEvents('m.room.join_rules', ''); + if (join_rule) join_rule = join_rule.getContent().join_rule; + + var history_visibility = this.props.room.currentState.getStateEvents('m.room.history_visibility', ''); + if (history_visibility) history_visibility = history_visibility.getContent().history_visibility; + + var power_levels = this.props.room.currentState.getStateEvents('m.room.power_levels', ''); + + var events_levels = power_levels.events || {}; + + 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 user_id = MatrixClientPeg.get().credentials.userId; + + 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 room_avatar_level = parseInt(power_levels.state_default || 0); + if (events_levels['m.room.avatar'] !== undefined) { + room_avatar_level = events_levels['m.room.avatar']; + } + var can_set_room_avatar = current_user_level >= room_avatar_level; + + var change_avatar; + if (can_set_room_avatar) { + change_avatar = <div> + <h3>Room Icon</h3> + <ChangeAvatar room={this.props.room} /> + </div>; + } + + var banned = this.props.room.getMembersWithMembership("ban"); + + return ( + <div className="mx_RoomSettings"> + <textarea className="mx_RoomSettings_description" placeholder="Topic" defaultValue={topic} ref="topic"/> <br/> + <label><input type="checkbox" ref="is_private" defaultChecked={join_rule != "public"}/> Make this room private</label> <br/> + <label><input type="checkbox" ref="share_history" defaultChecked={history_visibility == "shared"}/> Share message history with new users</label> <br/> + <label className="mx_RoomSettings_encrypt"><input type="checkbox" /> Encrypt room</label> <br/> + + <h3>Power levels</h3> + <div className="mx_RoomSettings_power_levels mx_RoomSettings_settings"> + <div> + <label htmlFor="mx_RoomSettings_ban_level">Ban level</label> + <input type="text" defaultValue={ban_level} size="3" ref="ban" id="mx_RoomSettings_ban_level" + disabled={!can_change_levels || current_user_level < ban_level} onChange={this.onPowerLevelsChanged}/> + </div> + <div> + <label htmlFor="mx_RoomSettings_kick_level">Kick level</label> + <input type="text" defaultValue={kick_level} size="3" ref="kick" id="mx_RoomSettings_kick_level" + disabled={!can_change_levels || current_user_level < kick_level} onChange={this.onPowerLevelsChanged}/> + </div> + <div> + <label htmlFor="mx_RoomSettings_redact_level">Redact level</label> + <input type="text" defaultValue={redact_level} size="3" ref="redact" id="mx_RoomSettings_redact_level" + disabled={!can_change_levels || current_user_level < redact_level} onChange={this.onPowerLevelsChanged}/> + </div> + <div> + <label htmlFor="mx_RoomSettings_invite_level">Invite level</label> + <input type="text" defaultValue={invite_level} size="3" ref="invite" id="mx_RoomSettings_invite_level" + disabled={!can_change_levels || current_user_level < invite_level} onChange={this.onPowerLevelsChanged}/> + </div> + <div> + <label htmlFor="mx_RoomSettings_event_level">Send event level</label> + <input type="text" defaultValue={send_level} size="3" ref="events_default" id="mx_RoomSettings_event_level" + disabled={!can_change_levels || current_user_level < send_level} onChange={this.onPowerLevelsChanged}/> + </div> + <div> + <label htmlFor="mx_RoomSettings_state_level">Set state level</label> + <input type="text" defaultValue={state_level} size="3" ref="state_default" id="mx_RoomSettings_state_level" + disabled={!can_change_levels || current_user_level < state_level} onChange={this.onPowerLevelsChanged}/> + </div> + <div> + <label htmlFor="mx_RoomSettings_user_level">Default user level</label> + <input type="text" defaultValue={default_user_level} size="3" ref="users_default" + id="mx_RoomSettings_user_level" disabled={!can_change_levels || current_user_level < default_user_level} + onChange={this.onPowerLevelsChanged}/> + </div> + </div> + + <h3>User levels</h3> + <div className="mx_RoomSettings_user_levels mx_RoomSettings_settings"> + {Object.keys(user_levels).map(function(user, i) { + return ( + <div key={user}> + <label htmlFor={"mx_RoomSettings_user_"+i}>{user}</label> + <input type="text" defaultValue={user_levels[user]} size="3" id={"mx_RoomSettings_user_"+i} disabled/> + </div> + ); + })} + </div> + + <h3>Event levels</h3> + <div className="mx_RoomSettings_event_lvels mx_RoomSettings_settings"> + {Object.keys(events_levels).map(function(event_type, i) { + return ( + <div key={event_type}> + <label htmlFor={"mx_RoomSettings_event_"+i}>{event_type}</label> + <input type="text" defaultValue={events_levels[event_type]} size="3" id={"mx_RoomSettings_event_"+i} disabled/> + </div> + ); + })} + </div> + + <h3>Banned users</h3> + <div className="mx_RoomSettings_banned"> + {banned.map(function(member, i) { + return ( + <div key={i}> + {member.userId} + </div> + ); + })} + </div> + {change_avatar} + </div> + ); + } +}); diff --git a/src/controllers/molecules/RoomHeader.js b/src/controllers/molecules/RoomHeader.js deleted file mode 100644 index 2e61f0608c..0000000000 --- a/src/controllers/molecules/RoomHeader.js +++ /dev/null @@ -1,118 +0,0 @@ -/* -Copyright 2015 OpenMarket Ltd - -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. -*/ - -'use strict'; - -/* - * State vars: - * this.state.call_state = the UI state of the call (see CallHandler) - */ - -var React = require('react'); -var dis = require("../../dispatcher"); -var CallHandler = require("../../CallHandler"); - -module.exports = { - propTypes: { - room: React.PropTypes.object, - editing: React.PropTypes.bool, - onSettingsClick: React.PropTypes.func, - onSaveClick: React.PropTypes.func, - }, - - getDefaultProps: function() { - return { - editing: false, - onSettingsClick: function() {}, - onSaveClick: function() {}, - }; - }, - - componentDidMount: function() { - this.dispatcherRef = dis.register(this.onAction); - if (this.props.room) { - var call = CallHandler.getCallForRoom(this.props.room.roomId); - var callState = call ? call.call_state : "ended"; - this.setState({ - call_state: callState - }); - } - }, - - componentWillUnmount: function() { - dis.unregister(this.dispatcherRef); - }, - - onAction: function(payload) { - // don't filter out payloads for room IDs other than props.room because - // we may be interested in the conf 1:1 room - if (payload.action !== 'call_state' || !payload.room_id) { - return; - } - var call = CallHandler.getCallForRoom(payload.room_id); - var callState = call ? call.call_state : "ended"; - this.setState({ - call_state: callState - }); - }, - - onVideoClick: function(e) { - dis.dispatch({ - action: 'place_call', - type: e.shiftKey ? "screensharing" : "video", - room_id: this.props.room.roomId - }); - }, - onVoiceClick: function() { - dis.dispatch({ - action: 'place_call', - type: "voice", - room_id: this.props.room.roomId - }); - }, - onHangupClick: function() { - var call = CallHandler.getCallForRoom(this.props.room.roomId); - if (!call) { return; } - dis.dispatch({ - action: 'hangup', - // hangup the call for this room, which may not be the room in props - // (e.g. conferences which will hangup the 1:1 room instead) - room_id: call.roomId - }); - }, - onMuteAudioClick: function() { - var call = CallHandler.getCallForRoom(this.props.room.roomId); - if (!call) { - return; - } - var newState = !call.isMicrophoneMuted(); - call.setMicrophoneMuted(newState); - this.setState({ - audioMuted: newState - }); - }, - onMuteVideoClick: function() { - var call = CallHandler.getCallForRoom(this.props.room.roomId); - if (!call) { - return; - } - var newState = !call.isLocalVideoMuted(); - call.setLocalVideoMuted(newState); - this.setState({ - videoMuted: newState - }); - } -}; diff --git a/src/controllers/molecules/RoomSettings.js b/src/controllers/molecules/RoomSettings.js deleted file mode 100644 index 3c0682d09a..0000000000 --- a/src/controllers/molecules/RoomSettings.js +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2015 OpenMarket Ltd - -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 React = require('react'); - -module.exports = { - propTypes: { - room: React.PropTypes.object.isRequired, - }, - - getInitialState: function() { - return { - power_levels_changed: false - }; - } -};