Rename base skin to vector

This commit is contained in:
David Baker 2015-09-22 18:05:55 +01:00
parent 682392d02a
commit 148dbc23ed
146 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,76 @@
/*
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';
var React = require('react');
var EditableTextController = require("../../../../src/controllers/atoms/EditableText");
module.exports = React.createClass({
displayName: 'EditableText',
mixins: [EditableTextController],
onKeyUp: function(ev) {
if (ev.key == "Enter") {
this.onFinish(ev);
} else if (ev.key == "Escape") {
this.cancelEdit();
}
},
onClickDiv: function() {
this.setState({
phase: this.Phases.Edit,
})
},
onFocus: function(ev) {
ev.target.setSelectionRange(0, ev.target.value.length);
},
onFinish: function(ev) {
if (ev.target.value) {
this.setValue(ev.target.value, ev.key === "Enter");
} else {
this.cancelEdit();
}
},
render: function() {
var editable_el;
if (this.state.phase == this.Phases.Display) {
if (this.state.value) {
editable_el = <div ref="display_div" onClick={this.onClickDiv}>{this.state.value}</div>;
} else {
editable_el = <div ref="display_div" onClick={this.onClickDiv}>{this.props.label}</div>;
}
} else if (this.state.phase == this.Phases.Edit) {
editable_el = (
<div>
<input type="text" defaultValue={this.state.value} onKeyUp={this.onKeyUp} onFocus={this.onFocus} onBlur={this.onFinish} placeholder={this.props.placeHolder} autoFocus/>
</div>
);
}
return (
<div className="mx_EditableText">
{editable_el}
</div>
);
}
});

View file

@ -0,0 +1,38 @@
/*
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';
var React = require('react');
var EnableNotificationsButtonController = require("../../../../src/controllers/atoms/EnableNotificationsButton");
module.exports = React.createClass({
displayName: 'EnableNotificationsButton',
mixins: [EnableNotificationsButtonController],
render: function() {
if (this.enabled()) {
return (
<button className="mx_EnableNotificationsButton" onClick={this.onClick}>Disable Notifications</button>
);
} else {
return (
<button className="mx_EnableNotificationsButton" onClick={this.onClick}>Enable Notifications</button>
);
}
}
});

View file

@ -0,0 +1,76 @@
/*
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';
var React = require('react');
var ImageViewController = require("../../../../src/controllers/atoms/ImageView");
module.exports = React.createClass({
displayName: 'ImageView',
mixins: [ImageViewController],
// XXX: keyboard shortcuts for managing dialogs should be done by the modal dialog base class omehow, surely...
componentDidMount: function() {
document.addEventListener("keydown", this.onKeyDown);
},
componentWillUnmount: function() {
document.removeEventListener("keydown", this.onKeyDown);
},
onKeyDown: function(ev) {
if (ev.keyCode == 27) { // escape
ev.stopPropagation();
ev.preventDefault();
this.props.onFinished();
}
},
render: function() {
// XXX: can't we just do max-width: 80%, max-height: 80% on the CSS?
var width = this.props.width || 500;
var height = this.props.height || 500;
var maxWidth = document.documentElement.clientWidth * 0.8;
var maxHeight = document.documentElement.clientHeight * 0.8;
var widthFrac = width / maxWidth;
var heightFrac = height / maxHeight;
var displayWidth;
var displayHeight;
if (widthFrac > heightFrac) {
displayWidth = Math.min(width, maxWidth);
displayHeight = (displayWidth / width) * height;
} else {
displayHeight = Math.min(height, maxHeight);
displayWidth = (displayHeight / height) * width;
}
var style = {
width: displayWidth,
height: displayHeight
};
return (
<img className="mx_ImageView" src={this.props.src} style={style} />
);
}
});

View file

@ -0,0 +1,32 @@
/*
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';
var React = require('react');
var LogoutButtonController = require("../../../../src/controllers/atoms/LogoutButton");
module.exports = React.createClass({
displayName: 'LogoutButton',
mixins: [LogoutButtonController],
render: function() {
return (
<button className="mx_LogoutButton" onClick={this.onClick}>Sign out</button>
);
}
});

View file

@ -0,0 +1,34 @@
/*
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';
var React = require('react');
var MemberAvatarController = require("../../../../src/controllers/atoms/MemberAvatar");
module.exports = React.createClass({
displayName: 'MemberAvatar',
mixins: [MemberAvatarController],
render: function() {
return (
<img className="mx_MemberAvatar" src={this.state.imageUrl}
onError={this.onError}
width={this.props.width} height={this.props.height} />
);
}
});

View file

@ -0,0 +1,36 @@
/*
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';
var React = require('react');
var MessageTimestampController = require("../../../../src/controllers/atoms/MessageTimestamp");
module.exports = React.createClass({
displayName: 'MessageTimestamp',
mixins: [MessageTimestampController],
render: function() {
var date = new Date(this.props.ts);
return (
<span className="mx_MessageTimestamp">
{date.toLocaleTimeString()}
</span>
);
},
});

View file

@ -0,0 +1,34 @@
/*
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';
var React = require('react');
var RoomAvatarController = require("../../../../src/controllers/atoms/RoomAvatar");
module.exports = React.createClass({
displayName: 'RoomAvatar',
mixins: [RoomAvatarController],
render: function() {
return (
<img className="mx_RoomAvatar" src={this.state.imageUrl} onError={this.onError}
width={this.props.width} height={this.props.height}
/>
);
}
});

View file

@ -0,0 +1,32 @@
/*
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';
var React = require('react');
var CreateRoomButtonController = require("../../../../../src/controllers/atoms/create_room/CreateRoomButton");
module.exports = React.createClass({
displayName: 'CreateRoomButton',
mixins: [CreateRoomButtonController],
render: function() {
return (
<button className="mx_CreateRoomButton" onClick={this.onClick}>Create Room</button>
);
}
});

View file

@ -0,0 +1,40 @@
/*
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';
var React = require('react');
var PresetsController = require("../../../../../src/controllers/atoms/create_room/Presets");
module.exports = React.createClass({
displayName: 'CreateRoomPresets',
mixins: [PresetsController],
onValueChanged: function(ev) {
this.props.onChange(ev.target.value)
},
render: function() {
return (
<select className="mx_Presets" onChange={this.onValueChanged} value={this.props.preset}>
<option value={this.Presets.PrivateChat}>Private Chat</option>
<option value={this.Presets.PublicChat}>Public Chat</option>
<option value={this.Presets.Custom}>Custom</option>
</select>
);
}
});

View file

@ -0,0 +1,79 @@
/*
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';
var React = require('react');
var RoomAliasController = require("../../../../../src/controllers/atoms/create_room/RoomAlias");
module.exports = React.createClass({
displayName: 'RoomAlias',
mixins: [RoomAliasController],
onValueChanged: function(ev) {
this.props.onChange(ev.target.value);
},
onFocus: function(ev) {
var target = ev.target;
var curr_val = ev.target.value;
if (this.props.homeserver) {
if (curr_val == "") {
setTimeout(function() {
target.value = "#:" + this.props.homeserver;
target.setSelectionRange(1, 1);
}, 0);
} else {
var suffix = ":" + this.props.homeserver;
setTimeout(function() {
target.setSelectionRange(
curr_val.startsWith("#") ? 1 : 0,
curr_val.endsWith(suffix) ? (target.value.length - suffix.length) : target.value.length
);
}, 0);
}
}
},
onBlur: function(ev) {
var curr_val = ev.target.value;
if (this.props.homeserver) {
if (curr_val == "#:" + this.props.homeserver) {
ev.target.value = "";
return;
}
if (curr_val != "") {
var new_val = ev.target.value;
var suffix = ":" + this.props.homeserver;
if (!curr_val.startsWith("#")) new_val = "#" + new_val;
if (!curr_val.endsWith(suffix)) new_val = new_val + suffix;
ev.target.value = new_val;
}
}
},
render: function() {
return (
<input type="text" className="mx_RoomAlias" placeholder="Alias (optional)"
onChange={this.onValueChanged} onFocus={this.onFocus} onBlur={this.onBlur}
value={this.props.alias}/>
);
}
});

View file

@ -0,0 +1,34 @@
/*
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';
var React = require('react');
var VideoFeedController = require("../../../../../src/controllers/atoms/voip/VideoFeed");
module.exports = React.createClass({
displayName: 'VideoFeed',
mixins: [VideoFeedController],
render: function() {
return (
<video>
</video>
);
},
});

View file

@ -0,0 +1,67 @@
/*
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';
var React = require('react');
var classNames = require('classnames');
var dis = require("../../../../src/dispatcher");
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
module.exports = React.createClass({
displayName: 'BottomLeftMenu',
onSettingsClick: function() {
dis.dispatch({action: 'view_user_settings'});
},
onRoomDirectoryClick: function() {
dis.dispatch({action: 'view_room_directory'});
},
onCreateRoomClick: function() {
dis.dispatch({action: 'view_create_room'});
},
render: function() {
return (
<div className="mx_BottomLeftMenu">
<div className="mx_BottomLeftMenu_options">
<div className="mx_RoomTile" onClick={this.onCreateRoomClick}>
<div className="mx_RoomTile_avatar">
<img src="img/create-big.png" alt="Create new room" title="Create new room" width="42" height="42"/>
</div>
<div className="mx_RoomTile_name">Create new room</div>
</div>
<div className="mx_RoomTile" onClick={this.onRoomDirectoryClick}>
<div className="mx_RoomTile_avatar">
<img src="img/directory-big.png" alt="Directory" title="Directory" width="42" height="42"/>
</div>
<div className="mx_RoomTile_name">Directory</div>
</div>
<div className="mx_RoomTile" onClick={this.onSettingsClick}>
<div className="mx_RoomTile_avatar">
<img src="img/settings-big.png" alt="Settings" title="Settings" width="42" height="42"/>
</div>
<div className="mx_RoomTile_name">Settings</div>
</div>
</div>
</div>
);
}
});

View file

@ -0,0 +1,65 @@
/*
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';
var React = require('react');
var ChangeAvatarController = require("../../../../src/controllers/molecules/ChangeAvatar");
var Loader = require("react-loader");
module.exports = React.createClass({
displayName: 'ChangeAvatar',
mixins: [ChangeAvatarController],
onFileSelected: function(ev) {
this.setAvatarFromFile(ev.target.files[0]);
},
onError: function(error) {
this.setState({
errorText: "Failed to upload profile picture!"
});
},
render: function() {
switch (this.state.phase) {
case this.Phases.Display:
case this.Phases.Error:
return (
<div>
<div className="mx_Dialog_content">
<img src={this.state.avatarUrl}/>
</div>
<div className="mx_Dialog_content">
Upload new:
<input type="file" onChange={this.onFileSelected}/>
{this.state.errorText}
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.props.onFinished}>Cancel</button>
</div>
</div>
);
case this.Phases.Uploading:
return (
<Loader />
);
}
}
});

View file

@ -0,0 +1,85 @@
/*
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';
var React = require('react');
var ChangePasswordController = require("../../../../src/controllers/molecules/ChangePassword");
var Loader = require("react-loader");
module.exports = React.createClass({
displayName: 'ChangePassword',
mixins: [ChangePasswordController],
onClickChange: function() {
var old_password = this.refs.old_input.getDOMNode().value;
var new_password = this.refs.new_input.getDOMNode().value;
var confirm_password = this.refs.confirm_input.getDOMNode().value;
if (new_password != confirm_password) {
this.setState({
state: this.Phases.Error,
errorString: "Passwords don't match"
});
} else if (new_password == '' || old_password == '') {
this.setState({
state: this.Phases.Error,
errorString: "Passwords can't be empty"
});
} else {
this.changePassword(old_password, new_password);
}
},
render: function() {
switch (this.state.phase) {
case this.Phases.Edit:
case this.Phases.Error:
return (
<div>
<div className="mx_Dialog_content">
<div>{this.state.errorString}</div>
<div><label>Old password <input type="password" ref="old_input"/></label></div>
<div><label>New password <input type="password" ref="new_input"/></label></div>
<div><label>Confirm password <input type="password" ref="confirm_input"/></label></div>
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onClickChange}>Change Password</button>
<button onClick={this.props.onFinished}>Cancel</button>
</div>
</div>
);
case this.Phases.Uploading:
return (
<div className="mx_Dialog_content">
<Loader />
</div>
);
case this.Phases.Success:
return (
<div>
<div className="mx_Dialog_content">
Success!
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.props.onFinished}>Ok</button>
</div>
</div>
)
}
}
});

View file

@ -0,0 +1,35 @@
/*
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';
var React = require('react');
var classNames = require('classnames');
var dis = require("../../../../src/dispatcher");
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
module.exports = React.createClass({
displayName: 'ContextualMenu',
render: function() {
return (
<div className="mx_ContextualMenu">
</div>
);
}
});

View file

@ -0,0 +1,56 @@
/*
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';
var React = require('react');
var days = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
];
module.exports = React.createClass({
displayName: 'DateSeparator',
render: function() {
var date = new Date(this.props.ts);
var today = new Date();
var yesterday = new Date();
yesterday.setDate(today.getDate() - 1);
var label;
if (date.toDateString() === today.toDateString()) {
label = "Today";
}
else if (date.toDateString() === yesterday.toDateString()) {
label = "Yesterday";
}
else if (today.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) {
label = days[date.getDay()];
}
else {
label = date.toDateString();
}
return (
<h2>{ label }</h2>
);
}
});

View file

@ -0,0 +1,52 @@
/*
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';
var React = require('react');
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var EventAsTextTileController = require("../../../../src/controllers/molecules/EventAsTextTile");
var ComponentBroker = require('../../../../src/ComponentBroker');
var MessageTimestamp = ComponentBroker.get('atoms/MessageTimestamp');
var MemberAvatar = ComponentBroker.get('atoms/MemberAvatar');
var TextForEvent = require("../../../../src/TextForEvent");
module.exports = React.createClass({
displayName: 'EventAsTextTile',
mixins: [EventAsTextTileController],
render: function() {
var text = TextForEvent.textForEvent(this.props.mxEvent);
if (text == null || text.length == 0) return null;
var timestamp = this.props.last ? <MessageTimestamp ts={this.props.mxEvent.getTs()} /> : null;
var avatar = this.props.mxEvent.sender ? <MemberAvatar member={this.props.mxEvent.sender} /> : null;
return (
<div className="mx_MessageTile mx_MessageTile_notice">
<div className="mx_MessageTile_avatar">
{ avatar }
</div>
{ timestamp }
<span className="mx_SenderProfile"></span>
<span className="mx_MessageTile_content">
{TextForEvent.textForEvent(this.props.mxEvent)}
</span>
</div>
);
},
});

View file

@ -0,0 +1,38 @@
/*
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';
var React = require('react');
var MEmoteTileController = require("../../../../src/controllers/molecules/MEmoteTile");
module.exports = React.createClass({
displayName: 'MEmoteTile',
mixins: [MEmoteTileController],
render: function() {
var mxEvent = this.props.mxEvent;
var content = mxEvent.getContent();
var name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
return (
<span ref="content" className="mx_MEmoteTile mx_MessageTile_content">
* {name} {content.body}
</span>
);
},
});

View file

@ -0,0 +1,44 @@
/*
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';
var React = require('react');
var MFileTileController = require("../../../../src/controllers/molecules/MFileTile");
var MatrixClientPeg = require('../../../../src/MatrixClientPeg');
module.exports = React.createClass({
displayName: 'MFileTile',
mixins: [MFileTileController],
render: function() {
var content = this.props.mxEvent.getContent();
var cli = MatrixClientPeg.get();
return (
<span className="mx_MFileTile">
<div className="mx_MImageTile_download">
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
<img src="img/download.png" width="10" height="12"/>
Download {this.presentableTextForFile(content)}
</a>
</div>
</span>
);
},
});

View file

@ -0,0 +1,94 @@
/*
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';
var React = require('react');
var filesize = require('filesize');
var MImageTileController = require("../../../../src/controllers/molecules/MImageTile");
var MatrixClientPeg = require('../../../../src/MatrixClientPeg');
var Modal = require('../../../../src/Modal');
var ComponentBroker = require('../../../../src/ComponentBroker');
var ImageView = ComponentBroker.get("atoms/ImageView");
module.exports = React.createClass({
displayName: 'MImageTile',
mixins: [MImageTileController],
thumbHeight: function(fullWidth, fullHeight, thumbWidth, thumbHeight) {
if (!fullWidth || !fullHeight) {
// Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even
// log this because it's spammy
return undefined;
}
if (fullWidth < thumbWidth && fullHeight < thumbHeight) {
// no scaling needs to be applied
return fullHeight;
}
var widthMulti = thumbWidth / fullWidth;
var heightMulti = thumbHeight / fullHeight;
if (widthMulti < heightMulti) {
// width is the dominant dimension so scaling will be fixed on that
return Math.floor(widthMulti * fullHeight);
}
else {
// height is the dominant dimension so scaling will be fixed on that
return Math.floor(heightMulti * fullHeight);
}
},
onClick: function(ev) {
var ms = ev.getModifierState();
if (ev.button == 0 && !ev.metaKey) {
ev.preventDefault();
var content = this.props.mxEvent.getContent();
var httpUrl = MatrixClientPeg.get().mxcUrlToHttp(content.url);
Modal.createDialog(ImageView, {
src: httpUrl,
width: content.info.w,
height: content.info.h
});
}
},
render: function() {
var content = this.props.mxEvent.getContent();
var cli = MatrixClientPeg.get();
var thumbHeight = null;
if (content.info) thumbHeight = this.thumbHeight(content.info.w, content.info.h, 320, 240);
var imgStyle = {};
if (thumbHeight) imgStyle['height'] = thumbHeight;
return (
<span className="mx_MImageTile">
<a href={cli.mxcUrlToHttp(content.url)} onClick={ this.onClick }>
<img className="mx_MImageTile_thumbnail" src={cli.mxcUrlToHttp(content.url, 320, 240)} alt={content.body} style={imgStyle} />
</a>
<div className="mx_MImageTile_download">
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
<img src="img/download.png" width="10" height="12"/>
Download {content.body} ({ filesize(content.info.size) })
</a>
</div>
</span>
);
},
});

View file

@ -0,0 +1,36 @@
/*
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';
var React = require('react');
var MNoticeTileController = require("../../../../src/controllers/molecules/MNoticeTile");
module.exports = React.createClass({
displayName: 'MNoticeTile',
mixins: [MNoticeTileController],
render: function() {
var content = this.props.mxEvent.getContent();
return (
<span ref="content" className="mx_MNoticeTile mx_MessageTile_content">
{content.body}
</span>
);
},
});

View file

@ -0,0 +1,56 @@
/*
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';
var React = require('react');
var MRoomMemberTileController = require("../../../../src/controllers/molecules/MRoomMemberTile");
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var ComponentBroker = require('../../../../src/ComponentBroker');
var TextForEvent = require('../../../../src/TextForEvent');
var MessageTimestamp = ComponentBroker.get('atoms/MessageTimestamp');
var MemberAvatar = ComponentBroker.get('atoms/MemberAvatar');
module.exports = React.createClass({
displayName: 'MRoomMemberTile',
mixins: [MRoomMemberTileController],
getMemberEventText: function() {
return TextForEvent.textForEvent(this.props.mxEvent);
},
render: function() {
// XXX: for now, just cheekily borrow the css from message tile...
var timestamp = this.props.last ? <MessageTimestamp ts={this.props.mxEvent.getTs()} /> : null;
var text = this.getMemberEventText();
if (!text) return <div/>;
return (
<div className="mx_MessageTile mx_MessageTile_notice">
<div className="mx_MessageTile_avatar">
<MemberAvatar member={this.props.mxEvent.sender} />
</div>
{ timestamp }
<span className="mx_SenderProfile"></span>
<span className="mx_MessageTile_content">
{ text }
</span>
</div>
);
},
});

View file

@ -0,0 +1,36 @@
/*
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';
var React = require('react');
var MTextTileController = require("../../../../src/controllers/molecules/MTextTile");
module.exports = React.createClass({
displayName: 'MTextTile',
mixins: [MTextTileController],
render: function() {
var content = this.props.mxEvent.getContent();
return (
<span ref="content" className="mx_MTextTile mx_MessageTile_content">
{content.body}
</span>
);
},
});

View file

@ -0,0 +1,46 @@
/*
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';
var React = require('react');
var ComponentBroker = require('../../../../src/ComponentBroker');
var LogoutButton = ComponentBroker.get("atoms/LogoutButton");
var EnableNotificationsButton = ComponentBroker.get("atoms/EnableNotificationsButton");
var MatrixToolbarController = require("../../../../src/controllers/molecules/MatrixToolbar");
var Notifier = ComponentBroker.get('organisms/Notifier');
module.exports = React.createClass({
displayName: 'MatrixToolbar',
mixins: [MatrixToolbarController],
hideToolbar: function() {
Notifier.setToolbarHidden(true);
},
render: function() {
return (
<div className="mx_MatrixToolbar">
You are not receiving desktop notifications. <EnableNotificationsButton />
<div className="mx_MatrixToolbar_close"><img src="img/close-white.png" width="16" height="16" onClick={ this.hideToolbar } /></div>
</div>
);
}
});

View file

@ -0,0 +1,72 @@
/*
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';
var React = require('react');
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var MemberInfoController = require("../../../../src/controllers/molecules/MemberInfo");
var ComponentBroker = require('../../../../src/ComponentBroker');
var MemberAvatar = ComponentBroker.get('atoms/MemberAvatar');
module.exports = React.createClass({
displayName: 'MemberInfo',
mixins: [MemberInfoController],
render: function() {
var interactButton, kickButton, banButton, muteButton, giveModButton;
if (this.props.member.userId === MatrixClientPeg.get().credentials.userId) {
interactButton = <div className="mx_ContextualMenu_field" onClick={this.onLeaveClick}>Leave room</div>;
}
else {
interactButton = <div className="mx_ContextualMenu_field" onClick={this.onChatClick}>Start chat</div>;
}
if (this.state.can.kick) {
kickButton = <div className="mx_ContextualMenu_field" onClick={this.onKick}>
Kick
</div>;
}
if (this.state.can.ban) {
banButton = <div className="mx_ContextualMenu_field" onClick={this.onBan}>
Ban
</div>;
}
if (this.state.can.mute) {
var muteLabel = this.state.muted ? "Unmute" : "Mute";
muteButton = <div className="mx_ContextualMenu_field" onClick={this.onMuteToggle}>
{muteLabel}
</div>;
}
if (this.state.can.modifyLevel) {
var giveOpLabel = this.state.isTargetMod ? "Revoke Mod" : "Make Mod";
giveModButton = <div className="mx_ContextualMenu_field" onClick={this.onModToggle}>
{giveOpLabel}
</div>
}
return (
<div>
{interactButton}
{muteButton}
{kickButton}
{banButton}
{giveModButton}
</div>
);
}
});

View file

@ -0,0 +1,166 @@
/*
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';
var React = require('react');
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var ComponentBroker = require('../../../../src/ComponentBroker');
var Modal = require("../../../../src/Modal");
var ContextualMenu = require("../../../../src/ContextualMenu");
var MemberTileController = require("../../../../src/controllers/molecules/MemberTile");
var MemberInfo = ComponentBroker.get('molecules/MemberInfo');
var ErrorDialog = ComponentBroker.get("organisms/ErrorDialog");
var MemberAvatar = ComponentBroker.get('atoms/MemberAvatar');
// The Lato WOFF doesn't include sensible combining diacritics, so Chrome chokes on rendering them.
// Revert to Arial when this happens, which on OSX works at least.
var zalgo = /[\u0300-\u036f\u1ab0-\u1aff\u1dc0-\u1dff\u20d0-\u20ff\ufe20-\ufe2f]/;
module.exports = React.createClass({
displayName: 'MemberTile',
mixins: [MemberTileController],
mouseEnter: function(e) {
this.setState({ 'hover': true });
},
mouseLeave: function(e) {
this.setState({ 'hover': false });
},
onClick: function(e) {
var self = this;
self.setState({ 'menu': true });
ContextualMenu.createMenu(MemberInfo, {
member: self.props.member,
right: window.innerWidth - e.pageX,
top: e.pageY,
onFinished: function() {
self.setState({ 'menu': false });
}
});
},
getDuration: function(time) {
if (!time) return;
var t = parseInt(time / 1000);
var s = t % 60;
var m = parseInt(t / 60) % 60;
var h = parseInt(t / (60 * 60)) % 24;
var d = parseInt(t / (60 * 60 * 24));
if (t < 60) {
if (t < 0) {
return "0s";
}
return s + "s";
}
if (t < 60 * 60) {
return m + "m";
}
if (t < 24 * 60 * 60) {
return h + "h";
}
return d + "d ";
},
getPrettyPresence: function(user) {
if (!user) return "Unknown";
var presence = user.presence;
if (presence === "online") return "Online";
if (presence === "unavailable") return "Idle"; // XXX: is this actually right?
if (presence === "offline") return "Offline";
return "Unknown";
},
getPowerLabel: function() {
var label = this.props.member.userId;
if (this.state.isTargetMod) {
label += " - Mod (" + this.props.member.powerLevelNorm + "%)";
}
return label;
},
render: function() {
var isMyUser = MatrixClientPeg.get().credentials.userId == this.props.member.userId;
var power;
if (this.props.member && this.props.member.powerLevelNorm > 0) {
var img = "img/p/p" + Math.floor(20 * this.props.member.powerLevelNorm / 100) + ".png";
power = <img src={ img } className="mx_MemberTile_power" width="48" height="48" alt=""/>;
}
var presenceClass = "mx_MemberTile_offline";
var mainClassName = "mx_MemberTile ";
if (this.props.member.user) {
if (this.props.member.user.presence === "online") {
presenceClass = "mx_MemberTile_online";
}
else if (this.props.member.user.presence === "unavailable") {
presenceClass = "mx_MemberTile_unavailable";
}
}
mainClassName += presenceClass;
if (this.state.hover || this.state.menu) {
mainClassName += " mx_MemberTile_hover";
}
var name = this.props.member.name;
// if (isMyUser) name += " (me)"; // this does nothing other than introduce line wrapping and pain
var leave = isMyUser ? <img className="mx_MemberTile_leave" src="img/delete.png" width="10" height="10" onClick={this.onLeaveClick}/> : null;
var nameClass = "mx_MemberTile_name";
if (zalgo.test(name)) {
nameClass += " mx_MemberTile_zalgo";
}
var nameEl;
if (this.state.hover || this.state.menu) {
var presence;
// FIXME: make presence data update whenever User.presence changes...
var active = this.props.member.user ? (this.props.member.user.lastActiveAgo || -1) : -1;
if (active >= 0) {
presence = <div className="mx_MemberTile_presence">{ this.getPrettyPresence(this.props.member.user) } for { this.getDuration(active) }</div>;
}
else {
presence = <div className="mx_MemberTile_presence">{ this.getPrettyPresence(this.props.member.user) }</div>;
}
nameEl =
<div className="mx_MemberTile_details">
{ leave }
<div className="mx_MemberTile_userId">{ this.props.member.userId }</div>
{ presence }
</div>
}
else {
nameEl =
<div className={nameClass}>
{ name }
</div>
}
return (
<div className={mainClassName} title={ this.getPowerLabel() } onClick={ this.onClick } onMouseEnter={ this.mouseEnter } onMouseLeave={ this.mouseLeave }>
<div className="mx_MemberTile_avatar">
<MemberAvatar member={this.props.member} />
{ power }
</div>
{ nameEl }
</div>
);
}
});

View file

@ -0,0 +1,68 @@
/*
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';
var React = require('react');
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var MessageComposerController = require("../../../../src/controllers/molecules/MessageComposer");
var ContentMessages = require("../../../../src/ContentMessages");
var ComponentBroker = require('../../../../src/ComponentBroker');
var MemberAvatar = ComponentBroker.get('atoms/MemberAvatar');
module.exports = React.createClass({
displayName: 'MessageComposer',
mixins: [MessageComposerController],
onUploadClick: function(ev) {
this.refs.uploadInput.getDOMNode().click();
},
onUploadFileSelected: function(ev) {
var files = ev.target.files;
// MessageComposer shouldn't have to rely on it's parent passing in a callback to upload a file
if (files && files.length > 0) {
this.props.uploadFile(files[0]);
}
this.refs.uploadInput.getDOMNode().value = null;
},
render: function() {
var me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
var uploadInputStyle = {display: 'none'};
return (
<div className="mx_MessageComposer">
<div className="mx_MessageComposer_wrapper">
<div className="mx_MessageComposer_row">
<div className="mx_MessageComposer_avatar">
<MemberAvatar member={me} />
</div>
<div className="mx_MessageComposer_input">
<textarea ref="textarea" onKeyDown={this.onKeyDown} placeholder="Type a message" />
</div>
<div className="mx_MessageComposer_upload" onClick={this.onUploadClick}>
<img src="img/upload.png" width="32" height="32"/>
<input type="file" style={uploadInputStyle} ref="uploadInput" onChange={this.onUploadFileSelected} />
</div>
</div>
</div>
</div>
);
},
});

View file

@ -0,0 +1,96 @@
/*
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';
var React = require('react');
var classNames = require("classnames");
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var ComponentBroker = require('../../../../src/ComponentBroker');
var MessageTimestamp = ComponentBroker.get('atoms/MessageTimestamp');
var SenderProfile = ComponentBroker.get('molecules/SenderProfile');
var MemberAvatar = ComponentBroker.get('atoms/MemberAvatar');
var UnknownMessageTile = ComponentBroker.get('molecules/UnknownMessageTile');
var tileTypes = {
'm.text': ComponentBroker.get('molecules/MTextTile'),
'm.notice': ComponentBroker.get('molecules/MNoticeTile'),
'm.emote': ComponentBroker.get('molecules/MEmoteTile'),
'm.image': ComponentBroker.get('molecules/MImageTile'),
'm.file': ComponentBroker.get('molecules/MFileTile')
};
var MessageTileController = require("../../../../src/controllers/molecules/MessageTile");
module.exports = React.createClass({
displayName: 'MessageTile',
mixins: [MessageTileController],
render: function() {
var content = this.props.mxEvent.getContent();
var msgtype = content.msgtype;
var TileType = UnknownMessageTile;
if (msgtype && tileTypes[msgtype]) {
TileType = tileTypes[msgtype];
}
var classes = classNames({
mx_MessageTile: true,
mx_MessageTile_sending: ['sending', 'queued'].indexOf(
this.props.mxEvent.status
) !== -1,
mx_MessageTile_notSent: this.props.mxEvent.status == 'not_sent',
mx_MessageTile_highlight: this.shouldHighlight(),
mx_MessageTile_continuation: this.props.continuation,
mx_MessageTile_last: this.props.last,
});
var timestamp = <MessageTimestamp ts={this.props.mxEvent.getTs()} />
var aux = null;
if (msgtype === 'm.image') aux = "sent an image";
else if (msgtype === 'm.video') aux = "sent a video";
else if (msgtype === 'm.file') aux = "uploaded a file";
var avatar, sender, resend;
if (!this.props.continuation) {
if (this.props.mxEvent.sender) {
avatar = (
<div className="mx_MessageTile_avatar">
<MemberAvatar member={this.props.mxEvent.sender} />
</div>
);
}
sender = <SenderProfile mxEvent={this.props.mxEvent} aux={aux} />;
}
if (this.props.mxEvent.status === "not_sent" && !this.state.resending) {
resend = <button className="mx_MessageTile_msgOption" onClick={this.onResend}>
Resend
</button>;
}
return (
<div className={classes}>
{ avatar }
{ timestamp }
{ resend }
{ sender }
<TileType mxEvent={this.props.mxEvent} />
</div>
);
},
});

View file

@ -0,0 +1,37 @@
/*
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';
var React = require('react');
var ProgressBarController = require("../../../../src/controllers/molecules/ProgressBar");
module.exports = React.createClass({
displayName: 'ProgressBar',
mixins: [ProgressBarController],
render: function() {
// Would use an HTML5 progress tag but if that doesn't animate if you
// use the HTML attributes rather than styles
var progressStyle = {
width: ((this.props.value / this.props.max) * 100)+"%"
};
return (
<div className="mx_ProgressBar"><div className="mx_ProgressBar_fill" style={progressStyle}></div></div>
);
}
});

View file

@ -0,0 +1,43 @@
/*
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';
var React = require('react');
var classNames = require('classnames');
//var RoomCreateController = require("../../../../src/controllers/molecules/RoomCreateController");
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
module.exports = React.createClass({
displayName: 'RoomCreate',
// mixins: [RoomCreateController],
render: function() {
return (
<div className="mx_RoomCreate">
<div className="mx_RoomCreate_table">
<div className="mx_RoomTile">
<div className="mx_RoomTile_avatar">
<img src="img/create.png" width="32" height="32"/>
</div>
<div className="mx_RoomTile_name">Create new room</div>
</div>
</div>
</div>
);
}
});

View file

@ -0,0 +1,36 @@
/*
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';
var React = require('react');
var classNames = require('classnames');
//var RoomDropTargetController = require("../../../../src/controllers/molecules/RoomDropTargetController");
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
module.exports = React.createClass({
displayName: 'RoomDropTarget',
// mixins: [RoomDropTargetController],
render: function() {
return (
<div className="mx_RoomDropTarget">
{this.props.text}
</div>
);
}
});

View file

@ -0,0 +1,139 @@
/*
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';
var React = require('react');
var ComponentBroker = require('../../../../src/ComponentBroker');
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var RoomHeaderController = require("../../../../src/controllers/molecules/RoomHeader");
var EditableText = ComponentBroker.get("atoms/EditableText");
var RoomAvatar = ComponentBroker.get('atoms/RoomAvatar');
module.exports = React.createClass({
displayName: 'RoomHeader',
mixins: [RoomHeaderController],
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.getDOMNode().value;
},
render: function() {
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 callButtons;
if (this.state) {
switch (this.state.call_state) {
case "ringback":
case "connected":
callButtons = (
<div className="mx_RoomHeader_textButton" onClick={this.onHangupClick}>
End call
</div>
);
break;
}
}
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 {
name =
<div className="mx_RoomHeader_name">
<EditableText label={this.props.room.name} initialValue={actual_name} placeHolder="Name" onValueChanged={this.onNameChange} />
</div>
if (topic) topic_el = <div className="mx_RoomHeader_topic" title={topic.getContent().topic}>{ topic.getContent().topic }</div>;
settings_button = (
<div className="mx_RoomHeader_button" onClick={this.props.onSettingsClick}>
<img src="img/settings.png" width="32" height="32"/>
</div>
);
}
var roomAvatar = null;
if (this.props.room) {
roomAvatar = (
<RoomAvatar room={this.props.room} />
);
}
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>
{callButtons}
{cancel_button}
{save_button}
<div className="mx_RoomHeader_rightRow">
{ settings_button }
<div className="mx_RoomHeader_button mx_RoomHeader_search">
<img src="img/search.png" title="Search" alt="Search" width="32" height="32"/>
</div>
<div className="mx_RoomHeader_button mx_RoomHeader_video" onClick={this.onVideoClick}>
<img src="img/video.png" title="Video call" alt="Video call" width="32" height="32"/>
</div>
<div className="mx_RoomHeader_button mx_RoomHeader_voice" onClick={this.onVoiceClick}>
<img src="img/voip.png" title="VoIP call" alt="VoIP call" width="32" height="32"/>
</div>
</div>
</div>
}
return (
<div className="mx_RoomHeader">
{ header }
</div>
);
},
});

View file

@ -0,0 +1,213 @@
/*
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';
var React = require('react');
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var RoomSettingsController = require("../../../../src/controllers/molecules/RoomSettings");
module.exports = React.createClass({
displayName: 'RoomSettings',
mixins: [RoomSettingsController],
getTopic: function() {
return this.refs.topic.getDOMNode().value;
},
getJoinRules: function() {
return this.refs.is_private.getDOMNode().checked ? "invite" : "public";
},
getHistoryVisibility: function() {
return this.refs.share_history.getDOMNode().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.getDOMNode().value),
kick: parseInt(this.refs.kick.getDOMNode().value),
redact: parseInt(this.refs.redact.getDOMNode().value),
invite: parseInt(this.refs.invite.getDOMNode().value),
events_default: parseInt(this.refs.events_default.getDOMNode().value),
state_default: parseInt(this.refs.state_default.getDOMNode().value),
users_default: parseInt(this.refs.users_default.getDOMNode().value),
users: power_levels.users,
events: power_levels.events,
};
return new_power_levels;
},
onPowerLevelsChanged: function() {
this.setState({
power_levels_changed: true
});
},
render: function() {
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', '');
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 events_levels = power_levels.events || [];
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 banned = this.props.room.getMembersWithMemership("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>
</div>
);
}
});

View file

@ -0,0 +1,71 @@
/*
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';
var React = require('react');
var classNames = require('classnames');
var RoomTileController = require("../../../../src/controllers/molecules/RoomTile");
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var ComponentBroker = require('../../../../src/ComponentBroker');
var RoomAvatar = ComponentBroker.get('atoms/RoomAvatar');
module.exports = React.createClass({
displayName: 'RoomTile',
mixins: [RoomTileController],
render: function() {
var myUserId = MatrixClientPeg.get().credentials.userId;
var classes = classNames({
'mx_RoomTile': true,
'mx_RoomTile_selected': this.props.selected,
'mx_RoomTile_unread': this.props.unread,
'mx_RoomTile_highlight': this.props.highlight,
'mx_RoomTile_invited': this.props.room.currentState.members[myUserId].membership == 'invite'
});
var name = this.props.room.name.replace(":", ":\u200b");
var badge;
if (this.props.highlight) {
badge = <div className="mx_RoomTile_badge"/>;
}
/*
if (this.props.highlight) {
badge = <div className="mx_RoomTile_badge">!</div>;
}
else if (this.props.unread) {
badge = <div className="mx_RoomTile_badge">1</div>;
}
var nameCell;
if (badge) {
nameCell = <div className="mx_RoomTile_nameBadge"><div className="mx_RoomTile_name">{name}</div><div className="mx_RoomTile_badgeCell">{badge}</div></div>;
}
else {
nameCell = <div className="mx_RoomTile_name">{name}</div>;
}
*/
return (
<div className={classes} onClick={this.onClick}>
<div className="mx_RoomTile_avatar">
<RoomAvatar room={this.props.room} />
{ badge }
</div>
<div className="mx_RoomTile_name">{name}</div>
</div>
);
}
});

View file

@ -0,0 +1,53 @@
/*
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';
var React = require('react');
var classNames = require("classnames");
var SenderProfileController = require("../../../../src/controllers/molecules/SenderProfile");
// The Lato WOFF doesn't include sensible combining diacritics, so Chrome chokes on rendering them.
// Revert to Arial when this happens, which on OSX works at least.
var zalgo = /[\u0300-\u036f\u1ab0-\u1aff\u1dc0-\u1dff\u20d0-\u20ff\ufe20-\ufe2f]/;
module.exports = React.createClass({
displayName: 'SenderProfile',
mixins: [SenderProfileController],
render: function() {
var mxEvent = this.props.mxEvent;
var name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
var classes = classNames({
mx_SenderProfile: true,
// taken from https://en.wikipedia.org/wiki/Combining_character
mx_SenderProfile_zalgo: zalgo.test(name),
});
var msgtype = mxEvent.getContent().msgtype;
if (msgtype && msgtype == 'm.emote') {
name = ''; // emote message must include the name so don't duplicate it
}
return (
<span className={classes}>
{name} { this.props.aux }
</span>
);
},
});

View file

@ -0,0 +1,51 @@
/*
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';
var React = require('react');
var Modal = require('../../../../src/Modal');
var ComponentBroker = require('../../../../src/ComponentBroker');
var ErrorDialog = ComponentBroker.get('organisms/ErrorDialog');
var ServerConfigController = require("../../../../src/controllers/molecules/ServerConfig");
module.exports = React.createClass({
displayName: 'ServerConfig',
mixins: [ServerConfigController],
showHelpPopup: function() {
Modal.createDialog(ErrorDialog, {
title: 'Custom Server Options',
description: "You can use the custom server options to log into other Matrix servers by specifying a different Home server URL. This allows you to use Vector with an existing Matrix account on a different Home server. You can also set a cutom Identity server but this will affect people ability to find you if you use a server in a group other than tha main Matrix.org group.",
button: "Dismiss",
focus: true
});
},
render: function() {
return (
<div className="mx_ServerConfig">
<label className="mx_Login_label mx_ServerConfig_hslabel" htmlFor="hsurl">Home server URL</label>
<input className="mx_Login_field" id="hsurl" type="text" value={this.state.hs_url} onChange={this.hsChanged} />
<label className="mx_Login_label mx_ServerConfig_islabel" htmlFor="isurl">Identity server URL</label>
<input className="mx_Login_field" type="text" value={this.state.is_url} onChange={this.isChanged} />
<a className="mx_ServerConfig_help" href="#" onClick={this.showHelpPopup}>What does this mean?</a>
</div>
);
}
});

View file

@ -0,0 +1,35 @@
/*
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';
var React = require('react');
var UnknownMessageTileController = require("../../../../src/controllers/molecules/UnknownMessageTile");
module.exports = React.createClass({
displayName: 'UnknownMessageTile',
mixins: [UnknownMessageTileController],
render: function() {
var content = this.props.mxEvent.getContent();
return (
<span className="mx_UnknownMessageTile">
{content.body}
</span>
);
},
});

View file

@ -0,0 +1,46 @@
/*
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';
var React = require('react');
var UserSelectorController = require("../../../../src/controllers/molecules/UserSelector");
module.exports = React.createClass({
displayName: 'UserSelector',
mixins: [UserSelectorController],
onAddUserId: function() {
this.addUser(this.refs.user_id_input.getDOMNode().value);
this.refs.user_id_input.getDOMNode().value = "";
},
render: function() {
var self = this;
return (
<div>
<ul className="mx_UserSelector_UserIdList" ref="list">
{this.props.selected_users.map(function(user_id, i) {
return <li key={user_id}>{user_id} - <span onClick={function() {self.removeUser(user_id);}}>X</span></li>
})}
</ul>
<input type="text" ref="user_id_input" defaultValue="" className="mx_UserSelector_userIdInput" placeholder="ex. @bob:example.com"/>
<button onClick={this.onAddUserId} className="mx_UserSelector_AddUserId">Add User</button>
</div>
);
}
});

View file

@ -0,0 +1,41 @@
/*
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';
var React = require('react');
var MatrixClientPeg = require("../../../../../src/MatrixClientPeg");
var ComponentBroker = require('../../../../../src/ComponentBroker');
var CallViewController = require(
"../../../../../src/controllers/molecules/voip/CallView"
);
var VideoView = ComponentBroker.get('molecules/voip/VideoView');
module.exports = React.createClass({
displayName: 'CallView',
mixins: [CallViewController],
getVideoView: function() {
return this.refs.video;
},
render: function(){
return (
<VideoView ref="video"/>
);
}
});

View file

@ -0,0 +1,70 @@
/*
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';
var React = require('react');
var MatrixClientPeg = require("../../../../../src/MatrixClientPeg");
var IncomingCallBoxController = require(
"../../../../../src/controllers/molecules/voip/IncomingCallBox"
);
module.exports = React.createClass({
displayName: 'IncomingCallBox',
mixins: [IncomingCallBoxController],
getRingAudio: function() {
return this.refs.ringAudio.getDOMNode();
},
render: function() {
if (!this.state.incomingCall || !this.state.incomingCall.roomId) {
return (
<div>
<audio ref="ringAudio" loop>
<source src="media/ring.ogg" type="audio/ogg" />
<source src="media/ring.mp3" type="audio/mpeg" />
</audio>
</div>
);
}
var caller = MatrixClientPeg.get().getRoom(this.state.incomingCall.roomId).name;
return (
<div className="mx_IncomingCallBox">
<img className="mx_IncomingCallBox_chevron" src="img/chevron-left.png" width="9" height="16" />
<audio ref="ringAudio" loop>
<source src="media/ring.ogg" type="audio/ogg" />
<source src="media/ring.mp3" type="audio/mpeg" />
</audio>
<div className="mx_IncomingCallBox_title">
Incoming { this.state.incomingCall ? this.state.incomingCall.type : '' } call from { caller }
</div>
<div className="mx_IncomingCallBox_buttons">
<div className="mx_IncomingCallBox_buttons_cell">
<div className="mx_IncomingCallBox_buttons_decline" onClick={this.onRejectClick}>
Decline
</div>
</div>
<div className="mx_IncomingCallBox_buttons_cell">
<div className="mx_IncomingCallBox_buttons_accept" onClick={this.onAnswerClick}>
Accept
</div>
</div>
</div>
</div>
);
}
});

View file

@ -0,0 +1,50 @@
/*
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';
var React = require('react');
var MatrixClientPeg = require("../../../../../src/MatrixClientPeg");
var ComponentBroker = require('../../../../../src/ComponentBroker');
var VideoViewController = require("../../../../../src/controllers/molecules/voip/VideoView");
var VideoFeed = ComponentBroker.get('atoms/voip/VideoFeed');
module.exports = React.createClass({
displayName: 'VideoView',
mixins: [VideoViewController],
getRemoteVideoElement: function() {
return this.refs.remote.getDOMNode();
},
getLocalVideoElement: function() {
return this.refs.local.getDOMNode();
},
render: function() {
return (
<div className="mx_VideoView">
<div className="mx_VideoView_remoteVideoFeed">
<VideoFeed ref="remote"/>
</div>
<div className="mx_VideoView_localVideoFeed">
<VideoFeed ref="local"/>
</div>
</div>
);
}
});

View file

@ -0,0 +1,170 @@
/*
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';
var React = require('react');
var CreateRoomController = require("../../../../src/controllers/organisms/CreateRoom");
var ComponentBroker = require('../../../../src/ComponentBroker');
var PresetValues = require('../../../../src/controllers/atoms/create_room/Presets').Presets;
var CreateRoomButton = ComponentBroker.get("atoms/create_room/CreateRoomButton");
var RoomAlias = ComponentBroker.get("atoms/create_room/RoomAlias");
var Presets = ComponentBroker.get("atoms/create_room/Presets");
var UserSelector = ComponentBroker.get("molecules/UserSelector");
var RoomHeader = ComponentBroker.get("molecules/RoomHeader");
var Loader = require("react-loader");
module.exports = React.createClass({
displayName: 'CreateRoom',
mixins: [CreateRoomController],
getPreset: function() {
return this.refs.presets.getPreset();
},
getName: function() {
return this.refs.name_textbox.getName();
},
getTopic: function() {
return this.refs.topic.getTopic();
},
getAliasLocalpart: function() {
return this.refs.alias.getAliasLocalpart();
},
getInvitedUsers: function() {
return this.refs.user_selector.getUserIds();
},
onPresetChanged: function(preset) {
switch (preset) {
case PresetValues.PrivateChat:
this.setState({
preset: preset,
is_private: true,
share_history: false,
});
break;
case PresetValues.PublicChat:
this.setState({
preset: preset,
is_private: false,
share_history: true,
});
break;
case PresetValues.Custom:
this.setState({
preset: preset,
});
break;
}
},
onPrivateChanged: function(ev) {
this.setState({
preset: PresetValues.Custom,
is_private: ev.target.checked,
});
},
onShareHistoryChanged: function(ev) {
this.setState({
preset: PresetValues.Custom,
share_history: ev.target.checked,
});
},
onTopicChange: function(ev) {
this.setState({
topic: ev.target.value,
});
},
onNameChange: function(ev) {
this.setState({
room_name: ev.target.value,
});
},
onInviteChanged: function(invited_users) {
this.setState({
invited_users: invited_users,
});
},
onAliasChanged: function(alias) {
this.setState({
alias: alias
})
},
onEncryptChanged: function(ev) {
this.setState({
encrypt: ev.target.checked,
});
},
render: function() {
var curr_phase = this.state.phase;
if (curr_phase == this.phases.CREATING) {
return (
<Loader/>
);
} else {
var error_box = "";
if (curr_phase == this.phases.ERROR) {
error_box = (
<div className="mx_Error">
An error occured: {this.state.error_string}
</div>
);
}
return (
<div className="mx_CreateRoom">
<RoomHeader simpleHeader="Create room" />
<div className="mx_CreateRoom_body">
<input type="text" ref="room_name" value={this.state.room_name} onChange={this.onNameChange} placeholder="Name"/> <br />
<textarea className="mx_CreateRoom_description" ref="topic" value={this.state.topic} onChange={this.onTopicChange} placeholder="Topic"/> <br />
<RoomAlias ref="alias" alias={this.state.alias} onChange={this.onAliasChanged}/> <br />
<UserSelector ref="user_selector" selected_users={this.state.invited_users} onChange={this.onInviteChanged}/> <br />
<Presets ref="presets" onChange={this.onPresetChanged} preset={this.state.preset}/> <br />
<div>
<label><input type="checkbox" ref="is_private" checked={this.state.is_private} onChange={this.onPrivateChanged}/> Make this room private</label>
</div>
<div>
<label><input type="checkbox" ref="share_history" checked={this.state.share_history} onChange={this.onShareHistoryChanged}/> Share message history with new users</label>
</div>
<div className="mx_CreateRoom_encrypt">
<label><input type="checkbox" ref="encrypt" checked={this.state.encrypt} onChange={this.onEncryptChanged}/> Encrypt room</label>
</div>
<div>
<CreateRoomButton onCreateRoom={this.onCreateRoom} /> <br />
</div>
{error_box}
</div>
</div>
);
}
}
});

View file

@ -0,0 +1,54 @@
/*
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';
/*
* Usage:
* Modal.createDialog(ErrorDialog, {
* title: "some text", (default: "Error")
* description: "some more text",
* button: "Button Text",
* onClose: someFunction,
* focus: true|false (default: true)
* });
*/
var React = require('react');
var ErrorDialogController = require("../../../../src/controllers/organisms/ErrorDialog");
module.exports = React.createClass({
displayName: 'ErrorDialog',
mixins: [ErrorDialogController],
render: function() {
return (
<div className="mx_ErrorDialog">
<div className="mx_ErrorDialogTitle">
{this.props.title}
</div>
<div className="mx_Dialog_content">
{this.props.description}
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.props.onFinished} autoFocus={this.props.focus}>
{this.props.button}
</button>
</div>
</div>
);
}
});

View file

@ -0,0 +1,41 @@
/*
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';
var React = require('react');
var ComponentBroker = require('../../../../src/ComponentBroker');
var RoomList = ComponentBroker.get('organisms/RoomList');
var BottomLeftMenu = ComponentBroker.get('molecules/BottomLeftMenu');
var IncomingCallBox = ComponentBroker.get('molecules/voip/IncomingCallBox');
var RoomCreate = ComponentBroker.get('molecules/RoomCreate');
module.exports = React.createClass({
displayName: 'LeftPanel',
render: function() {
return (
<aside className="mx_LeftPanel">
<img className="mx_LeftPanel_hideButton" src="img/hide.png" width="32" height="32" alt="<"/>
<IncomingCallBox />
<RoomList selectedRoom={this.props.selectedRoom} />
<BottomLeftMenu />
</aside>
);
}
});

View file

@ -0,0 +1,41 @@
/*
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';
var React = require('react');
var LogoutPromptController = require("../../../../src/controllers/organisms/LogoutPrompt");
module.exports = React.createClass({
displayName: 'LogoutPrompt',
mixins: [LogoutPromptController],
render: function() {
return (
<div>
<div className="mx_Dialog_content">
Sign out?
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.logOut}>Sign Out</button>
<button onClick={this.cancelPrompt}>Cancel</button>
</div>
</div>
);
},
});

View file

@ -0,0 +1,111 @@
/*
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';
var React = require('react');
var classNames = require('classnames');
var MemberListController = require("../../../../src/controllers/organisms/MemberList");
var ComponentBroker = require('../../../../src/ComponentBroker');
var MemberTile = ComponentBroker.get("molecules/MemberTile");
var EditableText = ComponentBroker.get("atoms/EditableText");
module.exports = React.createClass({
displayName: 'MemberList',
mixins: [MemberListController],
getInitialState: function() {
return { editing: false };
},
// FIXME: combine this more nicely with the MemberInfo positioning stuff...
onMemberListScroll: function(ev) {
if (this.refs.memberListScroll) {
var memberListScroll = this.refs.memberListScroll.getDOMNode();
// offset the current MemberInfo bubble
var memberInfo = document.getElementsByClassName("mx_MemberInfo")[0];
if (memberInfo) {
memberInfo.style.top = (memberInfo.parentElement.offsetTop - memberListScroll.scrollTop) + "px";
}
}
},
makeMemberTiles: function() {
var self = this;
return Object.keys(self.state.memberDict).map(function(userId) {
var m = self.state.memberDict[userId];
return (
<MemberTile key={userId} member={m} ref={userId} />
);
});
},
onPopulateInvite: function(inputText, shouldSubmit) {
// reset back to placeholder
this.refs.invite.setValue("Invite", false, true);
this.setState({ editing: false });
if (!shouldSubmit) {
return; // enter key wasn't pressed
}
this.onInvite(inputText);
},
onClickInvite: function(ev) {
this.setState({ editing: true });
this.refs.invite.onClickDiv();
ev.stopPropagation();
ev.preventDefault();
},
inviteTile: function() {
var classes = classNames({
mx_MemberTile: true,
mx_MemberTile_inviteTile: true,
mx_MemberTile_inviteEditing: this.state.editing,
});
return (
<div className={ classes } onClick={ this.onClickInvite } >
<div className="mx_MemberTile_avatar"><img src="img/create-big.png" width="40" height="40" alt=""/></div>
<div className="mx_MemberTile_name">
<EditableText ref="invite" label="Invite" placeHolder="@user:domain.com" initialValue="" onValueChanged={this.onPopulateInvite}/>
</div>
</div>
);
},
render: function() {
return (
<div className="mx_MemberList">
<div className="mx_MemberList_chevron">
<img src="img/chevron.png" width="24" height="13"/>
</div>
<div className="mx_MemberList_border" ref="memberListScroll" onScroll={ this.onMemberListScroll }>
<h2>Members</h2>
<div className="mx_MemberList_wrapper">
{this.makeMemberTiles()}
{this.inviteTile()}
</div>
</div>
</div>
);
}
});

View file

@ -0,0 +1,100 @@
/*
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';
var NotifierController = require("../../../../src/controllers/organisms/Notifier");
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var TextForEvent = require("../../../../src/TextForEvent");
var extend = require("../../../../src/extend");
var dis = require("../../../../src/dispatcher");
var Avatar = require("../../../../src/Avatar");
var NotifierView = {
notificationMessageForEvent: function(ev) {
return TextForEvent.textForEvent(ev);
},
displayNotification: function(ev, room) {
if (!global.Notification || global.Notification.permission != 'granted') {
return;
}
if (global.document.hasFocus()) {
return;
}
var msg = this.notificationMessageForEvent(ev);
if (!msg) return;
var title;
if (!ev.sender || room.name == ev.sender.name) {
title = room.name;
// notificationMessageForEvent includes sender,
// but we already have the sender here
if (ev.getContent().body) msg = ev.getContent().body;
} else if (ev.getType() == 'm.room.member') {
// context is all in the message here, we don't need
// to display sender info
title = room.name;
} else if (ev.sender) {
title = ev.sender.name + " (" + room.name + ")";
// notificationMessageForEvent includes sender,
// but we've just out sender in the title
if (ev.getContent().body) msg = ev.getContent().body;
}
var avatarUrl = Avatar.avatarUrlForMember(
ev.sender, 40, 40, 'crop'
);
var notification = new global.Notification(
title,
{
"body": msg,
"icon": avatarUrl
}
);
notification.onclick = function() {
dis.dispatch({
action: 'view_room',
room_id: room.roomId
});
global.focus();
};
/*var audioClip;
if (audioNotification) {
audioClip = playAudio(audioNotification);
}*/
global.setTimeout(function() {
notification.close();
}, 5 * 1000);
}
};
var NotifierClass = function() {};
extend(NotifierClass.prototype, NotifierController);
extend(NotifierClass.prototype, NotifierView);
module.exports = new NotifierClass();

View file

@ -0,0 +1,66 @@
/*
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';
/*
* Usage:
* Modal.createDialog(ErrorDialog, {
* title: "some text", (default: "Error")
* description: "some more text",
* button: "Button Text",
* onClose: someFunction,
* focus: true|false (default: true)
* });
*/
var React = require('react');
var QuestionDialogController = require("../../../../src/controllers/organisms/QuestionDialog");
module.exports = React.createClass({
displayName: 'QuestionDialog',
mixins: [QuestionDialogController],
onOk: function() {
this.props.onFinished(true);
},
onCancel: function() {
this.props.onFinished(false);
},
render: function() {
return (
<div className="mx_QuestionDialog">
<div className="mx_QuestionDialogTitle">
{this.props.title}
</div>
<div className="mx_Dialog_content">
{this.props.description}
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onOk} autoFocus={this.props.focus}>
{this.props.button}
</button>
<button onClick={this.onCancel}>
Cancel
</button>
</div>
</div>
);
}
});

View file

@ -0,0 +1,78 @@
/*
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';
var React = require('react');
var ComponentBroker = require('../../../../src/ComponentBroker');
var MemberList = ComponentBroker.get('organisms/MemberList');
module.exports = React.createClass({
displayName: 'RightPanel',
Phase : {
Blank: 'Blank',
None: 'None',
MemberList: 'MemberList',
FileList: 'FileList',
},
getInitialState: function() {
return {
phase : this.Phase.MemberList
}
},
onMemberListButtonClick: function() {
if (this.state.phase == this.Phase.None) {
this.setState({ phase: this.Phase.MemberList });
}
else {
this.setState({ phase: this.Phase.None });
}
},
render: function() {
var buttonGroup;
var panel;
if (this.props.roomId) {
buttonGroup =
<div className="mx_RightPanel_headerButtonGroup">
<div className="mx_RightPanel_headerButton mx_RightPanel_filebutton">
<img src="img/file.png" width="32" height="32" title="Files" alt="Files"/>
</div>
<div className="mx_RightPanel_headerButton" onClick={ this.onMemberListButtonClick }>
<img src="img/members.png" width="32" height="32" title="Members" alt="Members"/>
</div>
</div>;
if (this.state.phase == this.Phase.MemberList) {
panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} />
}
}
return (
<aside className="mx_RightPanel">
<div className="mx_RightPanel_header">
{ buttonGroup }
</div>
{ panel }
</aside>
);
}
});

View file

@ -0,0 +1,147 @@
/*
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';
var React = require('react');
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var Modal = require("../../../../src/Modal");
var ComponentBroker = require('../../../../src/ComponentBroker');
var ErrorDialog = ComponentBroker.get("organisms/ErrorDialog");
var RoomHeader = ComponentBroker.get('molecules/RoomHeader');
var dis = require("../../../../src/dispatcher");
var Loader = require("react-loader");
module.exports = React.createClass({
displayName: 'RoomDirectory',
getInitialState: function() {
return {
publicRooms: [],
roomAlias: '',
loading: true,
}
},
componentDidMount: function() {
var self = this;
MatrixClientPeg.get().publicRooms(function (err, data) {
if (err) {
self.setState({ loading: false });
console.error("Failed to get publicRooms: %s", JSON.stringify(err));
Modal.createDialog(ErrorDialog, {
title: "Failed to get public room list",
description: err.message
});
}
else {
self.setState({
publicRooms: data.chunk,
loading: false,
});
self.forceUpdate();
}
});
},
joinRoom: function(roomId) {
var self = this;
self.setState({ loading: true });
// XXX: check that JS SDK suppresses duplicate attempts to join the same room
MatrixClientPeg.get().joinRoom(roomId).done(function() {
dis.dispatch({
action: 'view_room',
room_id: roomId
});
}, function(err) {
console.error("Failed to join room: %s", JSON.stringify(err));
Modal.createDialog(ErrorDialog, {
title: "Failed to join room",
description: err.message
});
});
},
getRows: function(filter) {
if (!this.state.publicRooms) return [];
var rooms = this.state.publicRooms.filter(function(a) {
// FIXME: if incrementally typing, keep narrowing down the search set
// incrementally rather than starting over each time.
return (a.aliases[0].search(filter) >= 0 && a.num_joined_members > 0);
}).sort(function(a,b) {
return a.num_joined_members - b.num_joined_members;
});
var rows = [];
var self = this;
for (var i = 0; i < rooms.length; i++) {
var name = rooms[i].name || rooms[i].aliases[0];
// <img src={ MatrixClientPeg.get().getAvatarUrlForRoom(rooms[i].room_id, 40, 40, "crop") } width="40" height="40" alt=""/>
rows.unshift(
<tbody key={ rooms[i].room_id }>
<tr onClick={self.joinRoom.bind(null, rooms[i].room_id)}>
<td className="mx_RoomDirectory_name">{ name }</td>
<td>{ rooms[i].aliases[0] }</td>
<td>{ rooms[i].num_joined_members }</td>
</tr>
<tr>
<td className="mx_RoomDirectory_topic" colSpan="3">{ rooms[i].topic }</td>
</tr>
</tbody>
);
}
return rows;
},
onKeyUp: function(ev) {
this.forceUpdate();
this.setState({ roomAlias : this.refs.roomAlias.getDOMNode().value })
if (ev.key == "Enter") {
this.joinRoom(this.refs.roomAlias.getDOMNode().value);
}
if (ev.key == "Down") {
}
},
render: function() {
if (this.state.loading) {
return (
<div className="mx_RoomDirectory">
<Loader />
</div>
);
}
return (
<div className="mx_RoomDirectory">
<RoomHeader simpleHeader="Public Rooms" />
<div className="mx_RoomDirectory_list">
<input ref="roomAlias" placeholder="Join a room (e.g. #foo:domain.com)" className="mx_RoomDirectory_input" size="64" onKeyUp={ this.onKeyUp }/>
<div className="mx_RoomDirectory_tableWrapper">
<table className="mx_RoomDirectory_table">
<tr><th width="45%">Room</th><th width="45%">Alias</th><th width="10%">Members</th></tr>
{ this.getRows(this.state.roomAlias) }
</table>
</div>
</div>
</div>
);
}
});

View file

@ -0,0 +1,53 @@
/*
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';
var React = require('react');
var ComponentBroker = require('../../../../src/ComponentBroker');
var CallView = ComponentBroker.get('molecules/voip/CallView');
var RoomDropTarget = ComponentBroker.get('molecules/RoomDropTarget');
var RoomListController = require("../../../../src/controllers/organisms/RoomList");
module.exports = React.createClass({
displayName: 'RoomList',
mixins: [RoomListController],
render: function() {
var callElement;
if (this.state.show_call_element) {
callElement = <CallView className="mx_MatrixChat_callView"/>
}
return (
<div className="mx_RoomList">
{callElement}
<h2 className="mx_RoomList_favourites_label">Favourites</h2>
<RoomDropTarget text="Drop here to favourite"/>
<h2 className="mx_RoomList_recents_label">Recents</h2>
<div className="mx_RoomList_recents">
{this.makeRoomTiles()}
</div>
<h2 className="mx_RoomList_archive_label">Archive</h2>
<RoomDropTarget text="Drop here to archive"/>
</div>
);
}
});

View file

@ -0,0 +1,227 @@
/*
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';
var React = require('react');
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var ComponentBroker = require('../../../../src/ComponentBroker');
var classNames = require("classnames");
var filesize = require('filesize');
var q = require('q');
var MessageTile = ComponentBroker.get('molecules/MessageTile');
var RoomHeader = ComponentBroker.get('molecules/RoomHeader');
var MessageComposer = ComponentBroker.get('molecules/MessageComposer');
var CallView = ComponentBroker.get("molecules/voip/CallView");
var RoomSettings = ComponentBroker.get("molecules/RoomSettings");
var RoomViewController = require("../../../../src/controllers/organisms/RoomView");
var Loader = require("react-loader");
module.exports = React.createClass({
displayName: 'RoomView',
mixins: [RoomViewController],
onSettingsClick: function() {
this.setState({editingRoomSettings: true});
},
onSaveClick: function() {
this.setState({
editingRoomSettings: false,
uploadingRoomSettings: true,
});
var new_name = this.refs.header.getRoomName();
var new_topic = this.refs.room_settings.getTopic();
var new_join_rule = this.refs.room_settings.getJoinRules();
var new_history_visibility = this.refs.room_settings.getHistoryVisibility();
var new_power_levels = this.refs.room_settings.getPowerLevels();
this.uploadNewState(
new_name,
new_topic,
new_join_rule,
new_history_visibility,
new_power_levels
);
},
onCancelClick: function() {
this.setState(this.getInitialState());
},
getUnreadMessagesString: function() {
if (!this.state.numUnreadMessages) {
return "";
}
return this.state.numUnreadMessages + " new message" + (this.state.numUnreadMessages > 1 ? "s" : "");
},
scrollToBottom: function() {
if (!this.refs.messageWrapper) return;
var messageWrapper = this.refs.messageWrapper.getDOMNode();
messageWrapper.scrollTop = messageWrapper.scrollHeight;
},
render: function() {
if (!this.state.room) {
if (this.props.roomId) {
return (
<div>
<button onClick={this.onJoinButtonClicked}>Join Room</button>
</div>
);
} else {
return (
<div />
);
}
}
var myUserId = MatrixClientPeg.get().credentials.userId;
if (this.state.room.currentState.members[myUserId].membership == 'invite') {
if (this.state.joining) {
return (
<div className="mx_RoomView">
<Loader />
</div>
);
} else {
var inviteEvent = this.state.room.currentState.members[myUserId].events.member.event;
// XXX: Leaving this intentionally basic for now because invites are about to change totally
var joinErrorText = this.state.joinError ? "Failed to join room!" : "";
return (
<div className="mx_RoomView">
<RoomHeader ref="header" room={this.state.room} simpleHeader="Room invite"/>
<div className="mx_RoomView_invitePrompt">
<div>{inviteEvent.user_id} has invited you to a room</div>
<br/>
<button ref="joinButton" onClick={this.onJoinButtonClicked}>Join</button>
<div className="error">{joinErrorText}</div>
</div>
</div>
);
}
} else {
var scrollheader_classes = classNames({
mx_RoomView_scrollheader: true,
loading: this.state.paginating
});
var statusBar = (
<div />
);
if (this.state.upload) {
var innerProgressStyle = {
width: ((this.state.upload.uploadedBytes / this.state.upload.totalBytes) * 100) + '%'
};
statusBar = (
<div className="mx_RoomView_uploadBar">
<span className="mx_RoomView_uploadFilename">Uploading {this.state.upload.fileName}</span>
<span className="mx_RoomView_uploadBytes">
{filesize(this.state.upload.uploadedBytes)} / {filesize(this.state.upload.totalBytes)}
</span>
<div className="mx_RoomView_uploadProgressOuter">
<div className="mx_RoomView_uploadProgressInner" style={innerProgressStyle}></div>
</div>
</div>
);
} else {
var typingString = this.getWhoIsTypingString();
var unreadMsgs = this.getUnreadMessagesString();
// unread count trumps who is typing since the unread count is only
// set when you've scrolled up
if (unreadMsgs) {
statusBar = (
<div className="mx_RoomView_unreadMessagesBar" onClick={ this.scrollToBottom }>
<img src="img/newmessages.png" width="10" height="12" alt=""/>
{unreadMsgs}
</div>
);
}
else if (typingString) {
statusBar = (
<div className="mx_RoomView_typingBar">
<img src="img/typing.png" width="40" height="40" alt=""/>
{typingString}
</div>
);
}
}
var roomEdit = null;
if (this.state.editingRoomSettings) {
roomEdit = <RoomSettings ref="room_settings" onSaveClick={this.onSaveClick} room={this.state.room} />;
}
if (this.state.uploadingRoomSettings) {
roomEdit = <Loader/>;
}
var conferenceCallNotification = null;
if (this.state.displayConfCallNotification) {
conferenceCallNotification = (
<div className="mx_RoomView_ongoingConfCallNotification" onClick={this.onConferenceNotificationClick}>
Ongoing conference call
</div>
);
}
var fileDropTarget = null;
if (this.state.draggingFile) {
fileDropTarget = <div className="mx_RoomView_fileDropTarget">
<div className="mx_RoomView_fileDropTargetLabel">
<img src="img/upload-big.png" width="46" height="61" alt="Drop File Here"/><br/>
Drop File Here
</div>
</div>;
}
return (
<div className="mx_RoomView">
<RoomHeader ref="header" room={this.state.room} editing={this.state.editingRoomSettings}
onSettingsClick={this.onSettingsClick} onSaveClick={this.onSaveClick} onCancelClick={this.onCancelClick} />
<div className="mx_RoomView_auxPanel">
<CallView room={this.state.room}/>
{ conferenceCallNotification }
{ roomEdit }
</div>
<div ref="messageWrapper" className="mx_RoomView_messagePanel" onScroll={ this.onMessageListScroll }>
<div className="mx_RoomView_messageListWrapper">
{ fileDropTarget }
<ol className="mx_RoomView_MessageList" aria-live="polite">
<li className={scrollheader_classes}>
</li>
{this.getEventTiles()}
</ol>
</div>
</div>
<div className="mx_RoomView_statusArea">
<div className="mx_RoomView_statusAreaBox">
{statusBar}
</div>
</div>
<MessageComposer room={this.state.room} uploadFile={this.uploadFile} />
</div>
);
}
},
});

View file

@ -0,0 +1,113 @@
/*
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';
var React = require('react');
var ComponentBroker = require('../../../../src/ComponentBroker');
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var UserSettingsController = require("../../../../src/controllers/organisms/UserSettings");
var EditableText = ComponentBroker.get('atoms/EditableText');
var EnableNotificationsButton = ComponentBroker.get('atoms/EnableNotificationsButton');
var ChangeAvatar = ComponentBroker.get('molecules/ChangeAvatar');
var ChangePassword = ComponentBroker.get('molecules/ChangePassword');
var LogoutPrompt = ComponentBroker.get('organisms/LogoutPrompt');
var Loader = require("react-loader");
var Modal = require("../../../../src/Modal");
module.exports = React.createClass({
displayName: 'UserSettings',
mixins: [UserSettingsController],
editAvatar: function() {
var url = MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl);
Modal.createDialog(ChangeAvatar, {initialAvatarUrl: url});
},
addEmail: function() {
},
editDisplayName: function() {
this.refs.displayname.edit();
},
changePassword: function() {
Modal.createDialog(ChangePassword);
},
onLogoutClicked: function(ev) {
this.logoutModal = Modal.createDialog(LogoutPrompt, {onCancel: this.onLogoutPromptCancel});
},
onLogoutPromptCancel: function() {
this.logoutModal.closeDialog();
},
render: function() {
switch (this.state.phase) {
case this.Phases.Loading:
return <Loader />
case this.Phases.Display:
return (
<div className="mx_UserSettings">
<div className="mx_UserSettings_User">
<h1>User Settings</h1>
<hr/>
<div className="mx_UserSettings_User_Inner">
<div className="mx_UserSettings_Avatar">
<div className="mx_UserSettings_Avatar_Text">Profile Photo</div>
<div className="mx_UserSettings_Avatar_Edit" onClick={this.editAvatar}>Edit</div>
</div>
<div className="mx_UserSettings_DisplayName">
<EditableText ref="displayname" initialValue={this.state.displayName} label="Click to set display name." onValueChanged={this.changeDisplayname}/>
<div className="mx_UserSettings_DisplayName_Edit" onClick={this.editDisplayName}>Edit</div>
</div>
<div className="mx_UserSettings_3pids">
{this.state.threepids.map(function(val) {
return <div>{val.address}</div>;
})}
</div>
<div className="mx_UserSettings_Add3pid" onClick={this.addEmail}>Add email</div>
</div>
</div>
<div className="mx_UserSettings_Global">
<h1>Global Settings</h1>
<hr/>
<div className="mx_UserSettings_Global_Inner">
<div className="mx_UserSettings_ChangePassword" onClick={this.changePassword}>
Change Password
</div>
<div className="mx_UserSettings_ClientVersion">
Version {this.state.clientVersion}
</div>
<div className="mx_UserSettings_EnableNotifications">
<EnableNotificationsButton />
</div>
<div className="mx_UserSettings_Logout">
<button onClick={this.onLogoutClicked}>Sign Out</button>
</div>
</div>
</div>
</div>
);
}
}
});

View file

@ -0,0 +1,122 @@
/*
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';
var React = require('react');
var ComponentBroker = require('../../../../src/ComponentBroker');
var LeftPanel = ComponentBroker.get('organisms/LeftPanel');
var RoomView = ComponentBroker.get('organisms/RoomView');
var RightPanel = ComponentBroker.get('organisms/RightPanel');
var Login = ComponentBroker.get('templates/Login');
var UserSettings = ComponentBroker.get('organisms/UserSettings');
var Register = ComponentBroker.get('templates/Register');
var CreateRoom = ComponentBroker.get('organisms/CreateRoom');
var RoomDirectory = ComponentBroker.get('organisms/RoomDirectory');
var MatrixToolbar = ComponentBroker.get('molecules/MatrixToolbar');
var Notifier = ComponentBroker.get('organisms/Notifier');
var MatrixChatController = require('../../../../src/controllers/pages/MatrixChat');
// should be atomised
var Loader = require("react-loader");
var classNames = require("classnames");
var dis = require("../../../../src/dispatcher");
module.exports = React.createClass({
displayName: 'MatrixChat',
mixins: [MatrixChatController],
onRoomCreated: function(room_id) {
dis.dispatch({
action: "view_room",
room_id: room_id,
});
},
render: function() {
if (this.state.logged_in && this.state.ready) {
var page_element;
var right_panel = "";
switch (this.state.page_type) {
case this.PageTypes.RoomView:
page_element = <RoomView roomId={this.state.currentRoom} key={this.state.currentRoom} />
right_panel = <RightPanel roomId={this.state.currentRoom} />
break;
case this.PageTypes.UserSettings:
page_element = <UserSettings />
right_panel = <RightPanel/>
break;
case this.PageTypes.CreateRoom:
page_element = <CreateRoom onRoomCreated={this.onRoomCreated}/>
right_panel = <RightPanel/>
break;
case this.PageTypes.RoomDirectory:
page_element = <RoomDirectory />
right_panel = <RightPanel/>
break;
}
// TODO: Fix duplication here and do conditionals like we do above
if (Notifier.supportsDesktopNotifications() && !Notifier.isEnabled() && !Notifier.isToolbarHidden()) {
return (
<div className="mx_MatrixChat_wrapper">
<MatrixToolbar />
<div className="mx_MatrixChat mx_MatrixChat_toolbarShowing">
<LeftPanel selectedRoom={this.state.currentRoom} />
<main className="mx_MatrixChat_middlePanel">
{page_element}
</main>
{right_panel}
</div>
</div>
);
}
else {
return (
<div className="mx_MatrixChat">
<LeftPanel selectedRoom={this.state.currentRoom} />
<main className="mx_MatrixChat_middlePanel">
{page_element}
</main>
{right_panel}
</div>
);
}
} else if (this.state.logged_in) {
return (
<Loader />
);
} else if (this.state.screen == 'register') {
return (
<Register onLoggedIn={this.onLoggedIn} clientSecret={this.state.register_client_secret}
sessionId={this.state.register_session_id} idSid={this.state.register_id_sid}
hsUrl={this.state.register_hs_url} isUrl={this.state.register_is_url}
registrationUrl={this.props.registrationUrl}
/>
);
} else {
return (
<Login onLoggedIn={this.onLoggedIn} />
);
}
}
});

View file

@ -0,0 +1,191 @@
/*
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';
var React = require('react');
var ComponentBroker = require("../../../../src/ComponentBroker");
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var ProgressBar = ComponentBroker.get("molecules/ProgressBar");
var Loader = require("react-loader");
var LoginController = require("../../../../src/controllers/templates/Login");
var ServerConfig = ComponentBroker.get("molecules/ServerConfig");
module.exports = React.createClass({
DEFAULT_HS_URL: 'https://matrix.org',
DEFAULT_IS_URL: 'https://vector.im',
displayName: 'Login',
mixins: [LoginController],
getInitialState: function() {
return {
serverConfigVisible: false
};
},
componentWillMount: function() {
this.onHSChosen();
this.customHsUrl = this.DEFAULT_HS_URL;
this.customIsUrl = this.DEFAULT_IS_URL;
},
getHsUrl: function() {
if (this.state.serverConfigVisible) {
return this.customHsUrl;
} else {
return this.DEFAULT_HS_URL;
}
},
getIsUrl: function() {
if (this.state.serverConfigVisible) {
return this.customIsUrl;
} else {
return this.DEFAULT_IS_URL;
}
},
onServerConfigVisibleChange: function(ev) {
this.setState({
serverConfigVisible: ev.target.checked
}, this.onHsUrlChanged);
},
/**
* Gets the form field values for the current login stage
*/
getFormVals: function() {
return {
'username': this.refs.user.getDOMNode().value.trim(),
'password': this.refs.pass.getDOMNode().value.trim()
};
},
onHsUrlChanged: function() {
var newHsUrl = this.refs.serverConfig.getHsUrl().trim();
var newIsUrl = this.refs.serverConfig.getIsUrl().trim();
if (newHsUrl == this.customHsUrl &&
newIsUrl == this.customIsUrl)
{
return;
}
else {
this.customHsUrl = newHsUrl;
this.customIsUrl = newIsUrl;
}
MatrixClientPeg.replaceUsingUrls(
this.getHsUrl(),
this.getIsUrl()
);
this.setState({
hs_url: this.getHsUrl(),
is_url: this.getIsUrl()
});
// XXX: HSes do not have to offer password auth, so we
// need to update and maybe show a different component
// when a new HS is entered.
if (this.updateHsTimeout) {
clearTimeout(this.updateHsTimeout);
}
var self = this;
this.updateHsTimeout = setTimeout(function() {
self.onHSChosen();
}, 1000);
},
componentForStep: function(step) {
switch (step) {
case 'choose_hs':
case 'fetch_stages':
var serverConfigStyle = {};
serverConfigStyle.display = this.state.serverConfigVisible ? 'block' : 'none';
return (
<div>
<input className="mx_Login_checkbox" id="advanced" type="checkbox" checked={this.state.serverConfigVisible} onChange={this.onServerConfigVisibleChange} />
<label className="mx_Login_label" htmlFor="advanced">Use custom server options (advanced)</label>
<div style={serverConfigStyle}>
<ServerConfig ref="serverConfig"
defaultHsUrl={this.customHsUrl} defaultIsUrl={this.customIsUrl}
onHsUrlChanged={this.onHsUrlChanged}
/>
</div>
</div>
);
// XXX: clearly these should be separate organisms
case 'stage_m.login.password':
return (
<div>
<form onSubmit={this.onUserPassEntered}>
<input className="mx_Login_field" ref="user" type="text" value={this.state.username} onChange={this.onUsernameChanged} placeholder="Email or user name" /><br />
<input className="mx_Login_field" ref="pass" type="password" value={this.state.password} onChange={this.onPasswordChanged} placeholder="Password" /><br />
{ this.componentForStep('choose_hs') }
<input className="mx_Login_submit" type="submit" value="Log in" />
</form>
</div>
);
}
},
onUsernameChanged: function(ev) {
this.setState({username: ev.target.value});
},
onPasswordChanged: function(ev) {
this.setState({password: ev.target.value});
},
loginContent: function() {
var loader = this.state.busy ? <div className="mx_Login_loader"><Loader /></div> : null;
return (
<div>
<h2>Sign in</h2>
{this.componentForStep(this.state.step)}
<div className="mx_Login_error">
{ loader }
{this.state.errorText}
</div>
<a className="mx_Login_create" onClick={this.showRegister} href="#">Create a new account</a>
<br/>
<div className="mx_Login_links">
<a href="https://medium.com/@Vector">blog</a>&nbsp;&nbsp;&middot;&nbsp;&nbsp;
<a href="https://twitter.com/@VectorCo">twitter</a>&nbsp;&nbsp;&middot;&nbsp;&nbsp;
<a href="https://github.com/vector-im/vector-web">github</a>&nbsp;&nbsp;&middot;&nbsp;&nbsp;
<a href="https://matrix.org">powered by Matrix</a>
</div>
</div>
);
},
render: function() {
return (
<div className="mx_Login">
<div className="mx_Login_box">
<div className="mx_Login_logo">
<img src="img/logo.png" width="249" height="78" alt="vector"/>
</div>
{this.loginContent()}
</div>
</div>
);
}
});

View file

@ -0,0 +1,192 @@
/*
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';
var React = require('react');
var ComponentBroker = require("../../../../src/ComponentBroker");
var Loader = require("react-loader");
var RegisterController = require("../../../../src/controllers/templates/Register");
var ServerConfig = ComponentBroker.get("molecules/ServerConfig");
module.exports = React.createClass({
DEFAULT_HS_URL: 'https://matrix.org',
DEFAULT_IS_URL: 'https://vector.im',
displayName: 'Register',
mixins: [RegisterController],
getInitialState: function() {
return {
serverConfigVisible: false
};
},
componentWillMount: function() {
this.customHsUrl = this.DEFAULT_HS_URL;
this.customIsUrl = this.DEFAULT_IS_URL;
},
getRegFormVals: function() {
return {
email: this.refs.email.getDOMNode().value.trim(),
username: this.refs.username.getDOMNode().value.trim(),
password: this.refs.password.getDOMNode().value.trim(),
confirmPassword: this.refs.confirmPassword.getDOMNode().value.trim()
};
},
getHsUrl: function() {
if (this.state.serverConfigVisible) {
return this.customHsUrl;
} else {
return this.DEFAULT_HS_URL;
}
},
getIsUrl: function() {
if (this.state.serverConfigVisible) {
return this.customIsUrl;
} else {
return this.DEFAULT_IS_URL;
}
},
onServerConfigVisibleChange: function(ev) {
this.setState({
serverConfigVisible: ev.target.checked
});
},
getUserIdSuffix: function() {
return '';
var actualHsUrl = document.createElement('a');
actualHsUrl.href = this.getHsUrl();
var defaultHsUrl = document.createElement('a');
defaultHsUrl.href = this.DEFAULT_HS_URL;
if (actualHsUrl.host == defaultHsUrl.host) {
return ':matrix.org';
}
return '';
},
onServerUrlChanged: function(newUrl) {
this.customHsUrl = this.refs.serverConfig.getHsUrl();
this.customIsUrl = this.refs.serverConfig.getIsUrl();
this.forceUpdate();
},
componentForStep: function(step) {
switch (step) {
case 'initial':
var serverConfigStyle = {};
serverConfigStyle.display = this.state.serverConfigVisible ? 'block' : 'none';
return (
<div>
<form onSubmit={this.onInitialStageSubmit}>
<input className="mx_Login_field" type="text" ref="email" placeholder="Email address" defaultValue={this.savedParams.email} /><br />
<input className="mx_Login_field" type="text" ref="username" placeholder="User name" defaultValue={this.savedParams.username} />{this.getUserIdSuffix()}<br />
<input className="mx_Login_field" type="password" ref="password" placeholder="Password" defaultValue={this.savedParams.password} /><br />
<input className="mx_Login_field" type="password" ref="confirmPassword" placeholder="Confirm password" defaultValue={this.savedParams.confirmPassword} /><br />
<input className="mx_Login_checkbox" id="advanced" type="checkbox" value={this.state.serverConfigVisible} onChange={this.onServerConfigVisibleChange} />
<label htmlFor="advanced">Use custom server options (advanced)</label>
<div style={serverConfigStyle}>
<ServerConfig ref="serverConfig"
defaultHsUrl={this.customHsUrl} defaultIsUrl={this.customIsUrl}
onHsUrlChanged={this.onServerUrlChanged} onIsUrlChanged={this.onServerUrlChanged} />
</div>
<br />
<input className="mx_Login_submit" type="submit" value="Register" />
</form>
</div>
);
// XXX: clearly these should be separate organisms
case 'stage_m.login.email.identity':
return (
<div>
Please check your email to continue registration.
</div>
);
case 'stage_m.login.recaptcha':
return (
<div ref="recaptchaContainer">
This Home Server would like to make sure you are not a robot
<div id="mx_recaptcha"></div>
</div>
);
}
},
registerContent: function() {
if (this.state.busy) {
return (
<Loader />
);
} else {
return (
<div>
<h2>Create an account</h2>
{this.componentForStep(this.state.step)}
<div className="mx_Login_error">{this.state.errorText}</div>
<a className="mx_Login_create" onClick={this.showLogin} href="#">I already have an account</a>
</div>
);
}
},
onBadFields: function(bad) {
var keys = Object.keys(bad);
var strings = [];
for (var i = 0; i < keys.length; ++i) {
switch (bad[keys[i]]) {
case this.FieldErrors.PasswordMismatch:
strings.push("Passwords don't match");
break;
case this.FieldErrors.Missing:
strings.push("Missing "+keys[i]);
break;
case this.FieldErrors.TooShort:
strings.push(keys[i]+" is too short");
break;
case this.FieldErrors.InUse:
strings.push(keys[i]+" is already taken");
break;
}
}
var errtxt = strings.join(', ');
this.setState({
errorText: errtxt
});
},
render: function() {
return (
<div className="mx_Login">
<div className="mx_Login_box">
<div className="mx_Login_logo">
<img src="img/logo.png" width="249" height="78" alt="vector"/>
</div>
{this.registerContent()}
</div>
</div>
);
}
});