Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit fc00eaf

Browse files
author
Luke Barnard
committed
Merge branch 'develop' into luke/store-history-as-raw-content
2 parents 0cc890c + d7f4e7c commit fc00eaf

File tree

12 files changed

+554
-176
lines changed

12 files changed

+554
-176
lines changed

src/autocomplete/Autocompleter.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,9 @@ export type Completion = {
3434
component: ?Component,
3535
range: SelectionRange,
3636
command: ?string,
37-
// An entity applied during the replacement (using [email protected] Entity.create)
38-
entity: ? {
39-
type: string,
40-
mutability: string,
41-
data: ?Object,
42-
},
37+
// If provided, apply a LINK entity to the completion with the
38+
// data = { url: href }.
39+
href: ?string,
4340
};
4441

4542
const PROVIDERS = [

src/components/structures/GroupView.js

Lines changed: 190 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import dis from '../../dispatcher';
2222
import { sanitizedHtmlNode } from '../../HtmlUtils';
2323
import { _t } from '../../languageHandler';
2424
import AccessibleButton from '../views/elements/AccessibleButton';
25+
import Modal from '../../Modal';
26+
import classnames from 'classnames';
2527

2628
const RoomSummaryType = PropTypes.shape({
2729
room_id: PropTypes.string.isRequired,
@@ -179,10 +181,13 @@ export default React.createClass({
179181
summary: null,
180182
error: null,
181183
editing: false,
184+
saving: false,
185+
uploadingAvatar: false,
182186
};
183187
},
184188

185189
componentWillMount: function() {
190+
this._changeAvatarComponent = null;
186191
this._loadGroupFromServer(this.props.groupId);
187192
},
188193

@@ -211,8 +216,83 @@ export default React.createClass({
211216
});
212217
},
213218

214-
_onSettingsClick: function() {
215-
this.setState({editing: true});
219+
_onEditClick: function() {
220+
this.setState({
221+
editing: true,
222+
profileForm: Object.assign({}, this.state.summary.profile),
223+
});
224+
},
225+
226+
_onCancelClick: function() {
227+
this.setState({
228+
editing: false,
229+
profileForm: null,
230+
});
231+
},
232+
233+
_onNameChange: function(e) {
234+
const newProfileForm = Object.assign(this.state.profileForm, { name: e.target.value });
235+
this.setState({
236+
profileForm: newProfileForm,
237+
});
238+
},
239+
240+
_onShortDescChange: function(e) {
241+
const newProfileForm = Object.assign(this.state.profileForm, { short_description: e.target.value });
242+
this.setState({
243+
profileForm: newProfileForm,
244+
});
245+
},
246+
247+
_onLongDescChange: function(e) {
248+
const newProfileForm = Object.assign(this.state.profileForm, { long_description: e.target.value });
249+
this.setState({
250+
profileForm: newProfileForm,
251+
});
252+
},
253+
254+
_onAvatarSelected: function(ev) {
255+
const file = ev.target.files[0];
256+
if (!file) return;
257+
258+
this.setState({uploadingAvatar: true});
259+
MatrixClientPeg.get().uploadContent(file).then((url) => {
260+
const newProfileForm = Object.assign(this.state.profileForm, { avatar_url: url });
261+
this.setState({
262+
uploadingAvatar: false,
263+
profileForm: newProfileForm,
264+
});
265+
}).catch((e) => {
266+
this.setState({uploadingAvatar: false});
267+
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
268+
console.error("Failed to upload avatar image", e);
269+
Modal.createDialog(ErrorDialog, {
270+
title: _t('Error'),
271+
description: _t('Failed to upload image'),
272+
});
273+
}).done();
274+
},
275+
276+
_onSaveClick: function() {
277+
this.setState({saving: true});
278+
MatrixClientPeg.get().setGroupProfile(this.props.groupId, this.state.profileForm).then((result) => {
279+
this.setState({
280+
saving: false,
281+
editing: false,
282+
summary: null,
283+
});
284+
this._loadGroupFromServer(this.props.groupId);
285+
}).catch((e) => {
286+
this.setState({
287+
saving: false,
288+
});
289+
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
290+
console.error("Failed to save group profile", e);
291+
Modal.createDialog(ErrorDialog, {
292+
title: _t('Error'),
293+
description: _t('Failed to update group'),
294+
});
295+
}).done();
216296
},
217297

218298
_getFeaturedRoomsNode() {
@@ -296,60 +376,129 @@ export default React.createClass({
296376
const Loader = sdk.getComponent("elements.Spinner");
297377
const TintableSvg = sdk.getComponent("elements.TintableSvg");
298378

299-
if (this.state.summary === null && this.state.error === null) {
379+
if (this.state.summary === null && this.state.error === null || this.state.saving) {
300380
return <Loader />;
301-
} else if (this.state.editing) {
302-
return <div />;
303381
} else if (this.state.summary) {
304382
const summary = this.state.summary;
305-
let description = null;
306-
if (summary.profile && summary.profile.long_description) {
307-
description = sanitizedHtmlNode(summary.profile.long_description);
308-
}
309-
310-
const roomBody = <div>
311-
<div className="mx_GroupView_groupDesc">{description}</div>
312-
{this._getFeaturedRoomsNode()}
313-
{this._getFeaturedUsersNode()}
314-
</div>;
315383

384+
let avatarNode;
316385
let nameNode;
317-
if (summary.profile && summary.profile.name) {
318-
nameNode = <div className="mx_RoomHeader_name">
319-
<span>{summary.profile.name}</span>
320-
<span className="mx_GroupView_header_groupid">
321-
({this.props.groupId})
322-
</span>
386+
let shortDescNode;
387+
let rightButtons;
388+
let roomBody;
389+
const headerClasses = {
390+
mx_GroupView_header: true,
391+
};
392+
if (this.state.editing) {
393+
let avatarImage;
394+
if (this.state.uploadingAvatar) {
395+
avatarImage = <Loader />;
396+
} else {
397+
const GroupAvatar = sdk.getComponent('avatars.GroupAvatar');
398+
avatarImage = <GroupAvatar groupId={this.props.groupId}
399+
groupAvatarUrl={this.state.profileForm.avatar_url}
400+
width={48} height={48} resizeMethod='crop'
401+
/>;
402+
}
403+
404+
avatarNode = (
405+
<div className="mx_GroupView_avatarPicker">
406+
<label htmlFor="avatarInput" className="mx_GroupView_avatarPicker_label">
407+
{avatarImage}
408+
</label>
409+
<div className="mx_GroupView_avatarPicker_edit">
410+
<label htmlFor="avatarInput" className="mx_GroupView_avatarPicker_label">
411+
<img src="img/camera.svg"
412+
alt={ _t("Upload avatar") } title={ _t("Upload avatar") }
413+
width="17" height="15" />
414+
</label>
415+
<input id="avatarInput" className="mx_GroupView_uploadInput" type="file" onChange={this._onAvatarSelected}/>
416+
</div>
417+
</div>
418+
);
419+
nameNode = <input type="text"
420+
value={this.state.profileForm.name}
421+
onChange={this._onNameChange}
422+
placeholder={_t('Group Name')}
423+
tabIndex="1"
424+
/>;
425+
shortDescNode = <input type="text"
426+
value={this.state.profileForm.short_description}
427+
onChange={this._onShortDescChange}
428+
placeholder={_t('Description')}
429+
tabIndex="2"
430+
/>;
431+
rightButtons = <span>
432+
<AccessibleButton className="mx_GroupView_saveButton mx_RoomHeader_textButton" onClick={this._onSaveClick}>
433+
{_t('Save')}
434+
</AccessibleButton>
435+
<AccessibleButton className='mx_GroupView_cancelButton' onClick={this._onCancelClick}>
436+
<img src="img/cancel.svg" className='mx_filterFlipColor'
437+
width="18" height="18" alt={_t("Cancel")}/>
438+
</AccessibleButton>
439+
</span>;
440+
roomBody = <div>
441+
<textarea className="mx_GroupView_editLongDesc" value={this.state.profileForm.long_description}
442+
onChange={this._onLongDescChange}
443+
tabIndex="3"
444+
/>
323445
</div>;
324446
} else {
325-
nameNode = <div className="mx_RoomHeader_name">
326-
<span>{this.props.groupId}</span>
447+
const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null;
448+
avatarNode = <GroupAvatar
449+
groupId={this.props.groupId}
450+
groupAvatarUrl={groupAvatarUrl}
451+
width={48} height={48}
452+
/>;
453+
if (summary.profile && summary.profile.name) {
454+
nameNode = <div>
455+
<span>{summary.profile.name}</span>
456+
<span className="mx_GroupView_header_groupid">
457+
({this.props.groupId})
458+
</span>
459+
</div>;
460+
} else {
461+
nameNode = <span>{this.props.groupId}</span>;
462+
}
463+
shortDescNode = <span>{summary.profile.short_description}</span>;
464+
465+
let description = null;
466+
if (summary.profile && summary.profile.long_description) {
467+
description = sanitizedHtmlNode(summary.profile.long_description);
468+
}
469+
roomBody = <div>
470+
<div className="mx_GroupView_groupDesc">{description}</div>
471+
{this._getFeaturedRoomsNode()}
472+
{this._getFeaturedUsersNode()}
327473
</div>;
474+
// disabled until editing works
475+
rightButtons = <AccessibleButton className="mx_GroupHeader_button"
476+
onClick={this._onEditClick} title={_t("Edit Group")}
477+
>
478+
<TintableSvg src="img/icons-settings-room.svg" width="16" height="16"/>
479+
</AccessibleButton>;
480+
481+
headerClasses.mx_GroupView_header_view = true;
328482
}
329483

330-
const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null;
331-
332-
// settings button is display: none until settings is wired up
333484
return (
334485
<div className="mx_GroupView">
335-
<div className="mx_RoomHeader">
336-
<div className="mx_RoomHeader_wrapper">
337-
<div className="mx_RoomHeader_avatar">
338-
<GroupAvatar
339-
groupId={this.props.groupId}
340-
groupAvatarUrl={groupAvatarUrl}
341-
width={48} height={48}
342-
/>
486+
<div className={classnames(headerClasses)}>
487+
<div className="mx_GroupView_header_leftCol">
488+
<div className="mx_GroupView_header_avatar">
489+
{avatarNode}
343490
</div>
344-
<div className="mx_RoomHeader_info">
345-
{nameNode}
346-
<div className="mx_RoomHeader_topic">
347-
{summary.profile.short_description}
491+
<div className="mx_GroupView_header_info">
492+
<div className="mx_GroupView_header_name">
493+
{nameNode}
494+
</div>
495+
<div className="mx_GroupView_header_shortDesc">
496+
{shortDescNode}
348497
</div>
349498
</div>
350-
<AccessibleButton className="mx_RoomHeader_button" onClick={this._onSettingsClick} title={_t("Settings")} style={{display: 'none'}}>
351-
<TintableSvg src="img/icons-settings-room.svg" width="16" height="16"/>
352-
</AccessibleButton>
499+
</div>
500+
<div className="mx_GroupView_header_rightCol">
501+
{rightButtons}
353502
</div>
354503
</div>
355504
{roomBody}

src/components/structures/LoggedInView.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ export default React.createClass({
307307
page_element = <GroupView
308308
groupId={this.props.currentGroupId}
309309
/>;
310+
//right_panel = <RightPanel userId={this.props.viewUserId} opacity={this.props.rightOpacity} />;
310311
break;
311312
}
312313

src/components/views/elements/AppTile.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export default React.createClass({
5050
loading: false,
5151
widgetUrl: this.props.url,
5252
error: null,
53+
deleting: false,
5354
};
5455
},
5556

@@ -101,6 +102,7 @@ export default React.createClass({
101102

102103
_onDeleteClick: function() {
103104
console.log("Delete widget %s", this.props.id);
105+
this.setState({deleting: true});
104106
MatrixClientPeg.get().sendStateEvent(
105107
this.props.room.roomId,
106108
'im.vector.modular.widgets',
@@ -110,6 +112,7 @@ export default React.createClass({
110112
console.log('Deleted widget');
111113
}, (e) => {
112114
console.error('Failed to delete widget', e);
115+
this.setState({deleting: false});
113116
});
114117
},
115118

@@ -124,6 +127,12 @@ export default React.createClass({
124127

125128
render: function() {
126129
let appTileBody;
130+
131+
// Don't render widget if it is in the process of being deleted
132+
if (this.state.deleting) {
133+
return <div></div>;
134+
}
135+
127136
if (this.state.loading) {
128137
appTileBody = (
129138
<div> Loading... </div>

0 commit comments

Comments
 (0)