Merge branches 'develop' and 't3chguy/roomColor' of github.com:matrix-org/matrix-react-sdk into t3chguy/roomColor
This commit is contained in:
commit
2f42e69b57
12 changed files with 219 additions and 182 deletions
|
@ -3,7 +3,10 @@ dist: trusty
|
||||||
|
|
||||||
# we don't need sudo, so can run in a container, which makes startup much
|
# we don't need sudo, so can run in a container, which makes startup much
|
||||||
# quicker.
|
# quicker.
|
||||||
sudo: false
|
#
|
||||||
|
# unfortunately we do temporarily require sudo as a workaround for
|
||||||
|
# https://github.com/travis-ci/travis-ci/issues/8836
|
||||||
|
sudo: required
|
||||||
|
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
|
|
|
@ -77,6 +77,7 @@
|
||||||
"querystring": "^0.2.0",
|
"querystring": "^0.2.0",
|
||||||
"react": "^15.4.0",
|
"react": "^15.4.0",
|
||||||
"react-addons-css-transition-group": "15.3.2",
|
"react-addons-css-transition-group": "15.3.2",
|
||||||
|
"react-beautiful-dnd": "^4.0.0",
|
||||||
"react-dnd": "^2.1.4",
|
"react-dnd": "^2.1.4",
|
||||||
"react-dnd-html5-backend": "^2.1.2",
|
"react-dnd-html5-backend": "^2.1.2",
|
||||||
"react-dom": "^15.4.0",
|
"react-dom": "^15.4.0",
|
||||||
|
|
|
@ -82,6 +82,17 @@ export function formatDate(date, showTwelveHour=false) {
|
||||||
return formatFullDate(date, showTwelveHour);
|
return formatFullDate(date, showTwelveHour);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatFullDateNoTime(date) {
|
||||||
|
const days = getDaysArray();
|
||||||
|
const months = getMonthsArray();
|
||||||
|
return _t('%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s', {
|
||||||
|
weekDayName: days[date.getDay()],
|
||||||
|
monthName: months[date.getMonth()],
|
||||||
|
day: date.getDate(),
|
||||||
|
fullYear: date.getFullYear(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function formatFullDate(date, showTwelveHour=false) {
|
export function formatFullDate(date, showTwelveHour=false) {
|
||||||
const days = getDaysArray();
|
const days = getDaysArray();
|
||||||
const months = getMonthsArray();
|
const months = getMonthsArray();
|
||||||
|
|
|
@ -22,25 +22,37 @@ const TagOrderActions = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an action thunk that will do an asynchronous request to
|
* Creates an action thunk that will do an asynchronous request to
|
||||||
* commit TagOrderStore.getOrderedTags() to account data and dispatch
|
* move a tag in TagOrderStore to destinationIx.
|
||||||
* actions to indicate the status of the request.
|
|
||||||
*
|
*
|
||||||
* @param {MatrixClient} matrixClient the matrix client to set the
|
* @param {MatrixClient} matrixClient the matrix client to set the
|
||||||
* account data on.
|
* account data on.
|
||||||
|
* @param {string} tag the tag to move.
|
||||||
|
* @param {number} destinationIx the new position of the tag.
|
||||||
* @returns {function} an action thunk that will dispatch actions
|
* @returns {function} an action thunk that will dispatch actions
|
||||||
* indicating the status of the request.
|
* indicating the status of the request.
|
||||||
* @see asyncAction
|
* @see asyncAction
|
||||||
*/
|
*/
|
||||||
TagOrderActions.commitTagOrdering = function(matrixClient) {
|
TagOrderActions.moveTag = function(matrixClient, tag, destinationIx) {
|
||||||
return asyncAction('TagOrderActions.commitTagOrdering', () => {
|
// Only commit tags if the state is ready, i.e. not null
|
||||||
// Only commit tags if the state is ready, i.e. not null
|
let tags = TagOrderStore.getOrderedTags();
|
||||||
const tags = TagOrderStore.getOrderedTags();
|
if (!tags) {
|
||||||
if (!tags) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
tags = tags.filter((t) => t !== tag);
|
||||||
|
tags = [...tags.slice(0, destinationIx), tag, ...tags.slice(destinationIx)];
|
||||||
|
|
||||||
|
const storeId = TagOrderStore.getStoreId();
|
||||||
|
|
||||||
|
return asyncAction('TagOrderActions.moveTag', () => {
|
||||||
Analytics.trackEvent('TagOrderActions', 'commitTagOrdering');
|
Analytics.trackEvent('TagOrderActions', 'commitTagOrdering');
|
||||||
return matrixClient.setAccountData('im.vector.web.tag_ordering', {tags});
|
return matrixClient.setAccountData(
|
||||||
|
'im.vector.web.tag_ordering',
|
||||||
|
{tags, _storeId: storeId},
|
||||||
|
);
|
||||||
|
}, () => {
|
||||||
|
// For an optimistic update
|
||||||
|
return {tags};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,9 @@ limitations under the License.
|
||||||
* suffix determining whether it is pending, successful or
|
* suffix determining whether it is pending, successful or
|
||||||
* a failure.
|
* a failure.
|
||||||
* @param {function} fn a function that returns a Promise.
|
* @param {function} fn a function that returns a Promise.
|
||||||
|
* @param {function?} pendingFn a function that returns an object to assign
|
||||||
|
* to the `request` key of the ${id}.pending
|
||||||
|
* payload.
|
||||||
* @returns {function} an action thunk - a function that uses its single
|
* @returns {function} an action thunk - a function that uses its single
|
||||||
* argument as a dispatch function to dispatch the
|
* argument as a dispatch function to dispatch the
|
||||||
* following actions:
|
* following actions:
|
||||||
|
@ -29,9 +32,13 @@ limitations under the License.
|
||||||
* `${id}.success` or
|
* `${id}.success` or
|
||||||
* `${id}.failure`.
|
* `${id}.failure`.
|
||||||
*/
|
*/
|
||||||
export function asyncAction(id, fn) {
|
export function asyncAction(id, fn, pendingFn) {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
dispatch({action: id + '.pending'});
|
dispatch({
|
||||||
|
action: id + '.pending',
|
||||||
|
request:
|
||||||
|
typeof pendingFn === 'function' ? pendingFn() : undefined,
|
||||||
|
});
|
||||||
fn().then((result) => {
|
fn().then((result) => {
|
||||||
dispatch({action: id + '.success', result});
|
dispatch({action: id + '.success', result});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
|
|
|
@ -325,7 +325,7 @@ module.exports = React.createClass({
|
||||||
const key = "membereventlistsummary-" + (prevEvent ? mxEv.getId() : "initial");
|
const key = "membereventlistsummary-" + (prevEvent ? mxEv.getId() : "initial");
|
||||||
|
|
||||||
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
|
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
|
||||||
const dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1} showTwelveHour={this.props.isTwelveHour} /></li>;
|
const dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1} /></li>;
|
||||||
ret.push(dateSeparator);
|
ret.push(dateSeparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -479,7 +479,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// do we need a date separator since the last event?
|
// do we need a date separator since the last event?
|
||||||
if (this._wantsDateSeparator(prevEvent, eventDate)) {
|
if (this._wantsDateSeparator(prevEvent, eventDate)) {
|
||||||
const dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1} showTwelveHour={this.props.isTwelveHour} /></li>;
|
const dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1} /></li>;
|
||||||
ret.push(dateSeparator);
|
ret.push(dateSeparator);
|
||||||
continuation = false;
|
continuation = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ import TagOrderActions from '../../actions/TagOrderActions';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
|
|
||||||
|
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||||
|
|
||||||
const TagPanel = React.createClass({
|
const TagPanel = React.createClass({
|
||||||
displayName: 'TagPanel',
|
displayName: 'TagPanel',
|
||||||
|
|
||||||
|
@ -69,7 +71,9 @@ const TagPanel = React.createClass({
|
||||||
dis.dispatch(GroupActions.fetchJoinedGroups(this.context.matrixClient));
|
dis.dispatch(GroupActions.fetchJoinedGroups(this.context.matrixClient));
|
||||||
},
|
},
|
||||||
|
|
||||||
onClick() {
|
onClick(e) {
|
||||||
|
// Ignore clicks on children
|
||||||
|
if (e.target !== e.currentTarget) return;
|
||||||
dis.dispatch({action: 'deselect_tags'});
|
dis.dispatch({action: 'deselect_tags'});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -78,8 +82,20 @@ const TagPanel = React.createClass({
|
||||||
dis.dispatch({action: 'view_create_group'});
|
dis.dispatch({action: 'view_create_group'});
|
||||||
},
|
},
|
||||||
|
|
||||||
onTagTileEndDrag() {
|
onTagTileEndDrag(result) {
|
||||||
dis.dispatch(TagOrderActions.commitTagOrdering(this.context.matrixClient));
|
// Dragged to an invalid destination, not onto a droppable
|
||||||
|
if (!result.destination) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch synchronously so that the TagPanel receives an
|
||||||
|
// optimistic update from TagOrderStore before the previous
|
||||||
|
// state is shown.
|
||||||
|
dis.dispatch(TagOrderActions.moveTag(
|
||||||
|
this.context.matrixClient,
|
||||||
|
result.draggableId,
|
||||||
|
result.destination.index,
|
||||||
|
), true);
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -89,16 +105,31 @@ const TagPanel = React.createClass({
|
||||||
|
|
||||||
const tags = this.state.orderedTags.map((tag, index) => {
|
const tags = this.state.orderedTags.map((tag, index) => {
|
||||||
return <DNDTagTile
|
return <DNDTagTile
|
||||||
key={tag + '_' + index}
|
key={tag}
|
||||||
tag={tag}
|
tag={tag}
|
||||||
|
index={index}
|
||||||
selected={this.state.selectedTags.includes(tag)}
|
selected={this.state.selectedTags.includes(tag)}
|
||||||
onEndDrag={this.onTagTileEndDrag}
|
|
||||||
/>;
|
/>;
|
||||||
});
|
});
|
||||||
return <div className="mx_TagPanel" onClick={this.onClick}>
|
return <div className="mx_TagPanel">
|
||||||
<div className="mx_TagPanel_tagTileContainer">
|
<DragDropContext onDragEnd={this.onTagTileEndDrag}>
|
||||||
{ tags }
|
<Droppable droppableId="tag-panel-droppable">
|
||||||
</div>
|
{ (provided, snapshot) => (
|
||||||
|
<div
|
||||||
|
className="mx_TagPanel_tagTileContainer"
|
||||||
|
ref={provided.innerRef}
|
||||||
|
// react-beautiful-dnd has a bug that emits a click to the parent
|
||||||
|
// of draggables upon dropping
|
||||||
|
// https://github.com/atlassian/react-beautiful-dnd/issues/273
|
||||||
|
// so we use onMouseDown here as a workaround.
|
||||||
|
onMouseDown={this.onClick}
|
||||||
|
>
|
||||||
|
{ tags }
|
||||||
|
{ provided.placeholder }
|
||||||
|
</div>
|
||||||
|
) }
|
||||||
|
</Droppable>
|
||||||
|
</DragDropContext>
|
||||||
<AccessibleButton className="mx_TagPanel_createGroupButton" onClick={this.onCreateGroupClick}>
|
<AccessibleButton className="mx_TagPanel_createGroupButton" onClick={this.onCreateGroupClick}>
|
||||||
<TintableSvg src="img/icons-create-room.svg" width="25" height="25" />
|
<TintableSvg src="img/icons-create-room.svg" width="25" height="25" />
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
|
|
|
@ -15,71 +15,29 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DragSource, DropTarget } from 'react-dnd';
|
|
||||||
|
|
||||||
import TagTile from './TagTile';
|
import TagTile from './TagTile';
|
||||||
import dis from '../../../dispatcher';
|
|
||||||
import { findDOMNode } from 'react-dom';
|
|
||||||
|
|
||||||
const tagTileSource = {
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
canDrag: function(props, monitor) {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
beginDrag: function(props) {
|
export default function DNDTagTile(props) {
|
||||||
// Return the data describing the dragged item
|
return <div>
|
||||||
return {
|
<Draggable
|
||||||
tag: props.tag,
|
key={props.tag}
|
||||||
};
|
draggableId={props.tag}
|
||||||
},
|
index={props.index}
|
||||||
|
>
|
||||||
endDrag: function(props, monitor, component) {
|
{ (provided, snapshot) => (
|
||||||
const dropResult = monitor.getDropResult();
|
<div>
|
||||||
if (!monitor.didDrop() || !dropResult) {
|
<div
|
||||||
return;
|
ref={provided.innerRef}
|
||||||
}
|
{...provided.draggableProps}
|
||||||
props.onEndDrag();
|
{...provided.dragHandleProps}
|
||||||
},
|
>
|
||||||
};
|
<TagTile {...props} />
|
||||||
|
</div>
|
||||||
const tagTileTarget = {
|
{ provided.placeholder }
|
||||||
canDrop(props, monitor) {
|
</div>
|
||||||
return true;
|
) }
|
||||||
},
|
</Draggable>
|
||||||
|
</div>;
|
||||||
hover(props, monitor, component) {
|
}
|
||||||
if (!monitor.canDrop()) return;
|
|
||||||
const draggedY = monitor.getClientOffset().y;
|
|
||||||
const {top, bottom} = findDOMNode(component).getBoundingClientRect();
|
|
||||||
const targetY = (top + bottom) / 2;
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'order_tag',
|
|
||||||
tag: monitor.getItem().tag,
|
|
||||||
targetTag: props.tag,
|
|
||||||
// Note: we indicate that the tag should be after the target when
|
|
||||||
// it's being dragged over the top half of the target.
|
|
||||||
after: draggedY < targetY,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
drop(props) {
|
|
||||||
// Return the data to be returned by getDropResult
|
|
||||||
return {
|
|
||||||
tag: props.tag,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default
|
|
||||||
DropTarget('TagTile', tagTileTarget, (connect, monitor) => ({
|
|
||||||
connectDropTarget: connect.dropTarget(),
|
|
||||||
}))(DragSource('TagTile', tagTileSource, (connect, monitor) => ({
|
|
||||||
connectDragSource: connect.dragSource(),
|
|
||||||
}))((props) => {
|
|
||||||
const { connectDropTarget, connectDragSource, ...otherProps } = props;
|
|
||||||
return connectDropTarget(connectDragSource(
|
|
||||||
<div>
|
|
||||||
<TagTile {...otherProps} />
|
|
||||||
</div>,
|
|
||||||
));
|
|
||||||
}));
|
|
||||||
|
|
|
@ -324,12 +324,7 @@ module.exports = React.createClass({
|
||||||
// Show all rooms
|
// Show all rooms
|
||||||
this._visibleRooms = MatrixClientPeg.get().getRooms();
|
this._visibleRooms = MatrixClientPeg.get().getRooms();
|
||||||
}
|
}
|
||||||
|
this._delayedRefreshRoomList();
|
||||||
this.setState({
|
|
||||||
selectedTags,
|
|
||||||
}, () => {
|
|
||||||
this.refreshRoomList();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
refreshRoomList: function() {
|
refreshRoomList: function() {
|
||||||
|
@ -345,6 +340,9 @@ module.exports = React.createClass({
|
||||||
this.setState({
|
this.setState({
|
||||||
lists: this.getRoomLists(),
|
lists: this.getRoomLists(),
|
||||||
totalRoomCount: totalRooms,
|
totalRoomCount: totalRooms,
|
||||||
|
// Do this here so as to not render every time the selected tags
|
||||||
|
// themselves change.
|
||||||
|
selectedTags: TagOrderStore.getSelectedTags(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// this._lastRefreshRoomListTs = Date.now();
|
// this._lastRefreshRoomListTs = Date.now();
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
"AM": "AM",
|
"AM": "AM",
|
||||||
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
|
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
|
||||||
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s",
|
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s",
|
||||||
|
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s",
|
||||||
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s",
|
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s",
|
||||||
"Who would you like to add to this community?": "Who would you like to add to this community?",
|
"Who would you like to add to this community?": "Who would you like to add to this community?",
|
||||||
"Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID",
|
"Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID",
|
||||||
|
|
|
@ -19,6 +19,14 @@ import { groupMemberFromApiObject, groupRoomFromApiObject } from '../groups';
|
||||||
import FlairStore from './FlairStore';
|
import FlairStore from './FlairStore';
|
||||||
import MatrixClientPeg from '../MatrixClientPeg';
|
import MatrixClientPeg from '../MatrixClientPeg';
|
||||||
|
|
||||||
|
function parseMembersResponse(response) {
|
||||||
|
return response.chunk.map((apiMember) => groupMemberFromApiObject(apiMember));
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRoomsResponse(response) {
|
||||||
|
return response.chunk.map((apiRoom) => groupRoomFromApiObject(apiRoom));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the group summary for a room and provides an API to change it and
|
* Stores the group summary for a room and provides an API to change it and
|
||||||
* other useful group APIs that may have an effect on the group summary.
|
* other useful group APIs that may have an effect on the group summary.
|
||||||
|
@ -38,65 +46,68 @@ export default class GroupStore extends EventEmitter {
|
||||||
throw new Error('GroupStore needs a valid groupId to be created');
|
throw new Error('GroupStore needs a valid groupId to be created');
|
||||||
}
|
}
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
this._summary = {};
|
this._state = {};
|
||||||
this._rooms = [];
|
this._state[GroupStore.STATE_KEY.Summary] = {};
|
||||||
this._members = [];
|
this._state[GroupStore.STATE_KEY.GroupRooms] = [];
|
||||||
this._invitedMembers = [];
|
this._state[GroupStore.STATE_KEY.GroupMembers] = [];
|
||||||
|
this._state[GroupStore.STATE_KEY.GroupInvitedMembers] = [];
|
||||||
this._ready = {};
|
this._ready = {};
|
||||||
|
|
||||||
|
this._fetchResourcePromise = {};
|
||||||
|
this._resourceFetcher = {
|
||||||
|
[GroupStore.STATE_KEY.Summary]: () => {
|
||||||
|
return MatrixClientPeg.get()
|
||||||
|
.getGroupSummary(this.groupId);
|
||||||
|
},
|
||||||
|
[GroupStore.STATE_KEY.GroupRooms]: () => {
|
||||||
|
return MatrixClientPeg.get()
|
||||||
|
.getGroupRooms(this.groupId)
|
||||||
|
.then(parseRoomsResponse);
|
||||||
|
},
|
||||||
|
[GroupStore.STATE_KEY.GroupMembers]: () => {
|
||||||
|
return MatrixClientPeg.get()
|
||||||
|
.getGroupUsers(this.groupId)
|
||||||
|
.then(parseMembersResponse);
|
||||||
|
},
|
||||||
|
[GroupStore.STATE_KEY.GroupInvitedMembers]: () => {
|
||||||
|
return MatrixClientPeg.get()
|
||||||
|
.getGroupInvitedUsers(this.groupId)
|
||||||
|
.then(parseMembersResponse);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
this.on('error', (err) => {
|
this.on('error', (err) => {
|
||||||
console.error(`GroupStore for ${this.groupId} encountered error`, err);
|
console.error(`GroupStore for ${this.groupId} encountered error`, err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_fetchMembers() {
|
_fetchResource(stateKey) {
|
||||||
MatrixClientPeg.get().getGroupUsers(this.groupId).then((result) => {
|
// Ongoing request, ignore
|
||||||
this._members = result.chunk.map((apiMember) => {
|
if (this._fetchResourcePromise[stateKey]) return;
|
||||||
return groupMemberFromApiObject(apiMember);
|
|
||||||
});
|
|
||||||
this._ready[GroupStore.STATE_KEY.GroupMembers] = true;
|
|
||||||
this._notifyListeners();
|
|
||||||
}).catch((err) => {
|
|
||||||
console.error("Failed to get group member list: " + err);
|
|
||||||
this.emit('error', err);
|
|
||||||
});
|
|
||||||
|
|
||||||
MatrixClientPeg.get().getGroupInvitedUsers(this.groupId).then((result) => {
|
const clientPromise = this._resourceFetcher[stateKey]();
|
||||||
this._invitedMembers = result.chunk.map((apiMember) => {
|
|
||||||
return groupMemberFromApiObject(apiMember);
|
// Indicate ongoing request
|
||||||
});
|
this._fetchResourcePromise[stateKey] = clientPromise;
|
||||||
this._ready[GroupStore.STATE_KEY.GroupInvitedMembers] = true;
|
|
||||||
|
clientPromise.then((result) => {
|
||||||
|
this._state[stateKey] = result;
|
||||||
|
this._ready[stateKey] = true;
|
||||||
this._notifyListeners();
|
this._notifyListeners();
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
// Invited users not visible to non-members
|
// Invited users not visible to non-members
|
||||||
if (err.httpStatus === 403) {
|
if (stateKey === GroupStore.STATE_KEY.GroupInvitedMembers && err.httpStatus === 403) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.error("Failed to get group invited member list: " + err);
|
|
||||||
this.emit('error', err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_fetchSummary() {
|
console.error("Failed to get resource " + stateKey + ":" + err);
|
||||||
MatrixClientPeg.get().getGroupSummary(this.groupId).then((resp) => {
|
|
||||||
this._summary = resp;
|
|
||||||
this._ready[GroupStore.STATE_KEY.Summary] = true;
|
|
||||||
this._notifyListeners();
|
|
||||||
}).catch((err) => {
|
|
||||||
this.emit('error', err);
|
this.emit('error', err);
|
||||||
|
}).finally(() => {
|
||||||
|
// Indicate finished request, allow for future fetches
|
||||||
|
delete this._fetchResourcePromise[stateKey];
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
_fetchRooms() {
|
return clientPromise;
|
||||||
MatrixClientPeg.get().getGroupRooms(this.groupId).then((resp) => {
|
|
||||||
this._rooms = resp.chunk.map((apiRoom) => {
|
|
||||||
return groupRoomFromApiObject(apiRoom);
|
|
||||||
});
|
|
||||||
this._ready[GroupStore.STATE_KEY.GroupRooms] = true;
|
|
||||||
this._notifyListeners();
|
|
||||||
}).catch((err) => {
|
|
||||||
this.emit('error', err);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_notifyListeners() {
|
_notifyListeners() {
|
||||||
|
@ -108,10 +119,9 @@ export default class GroupStore extends EventEmitter {
|
||||||
* immediately triggers an update to send the current state of the
|
* immediately triggers an update to send the current state of the
|
||||||
* store (which could be the initial state).
|
* store (which could be the initial state).
|
||||||
*
|
*
|
||||||
* XXX: This also causes a fetch of all group data, which effectively
|
* This also causes a fetch of all group data, which might cause
|
||||||
* causes 4 separate HTTP requests. This is bad, we should at least
|
* 4 separate HTTP requests, but only said requests aren't already
|
||||||
* deduplicate these in order to fix:
|
* ongoing.
|
||||||
* https://github.com/vector-im/riot-web/issues/5901
|
|
||||||
*
|
*
|
||||||
* @param {function} fn the function to call when the store updates.
|
* @param {function} fn the function to call when the store updates.
|
||||||
* @return {Object} tok a registration "token" with a single
|
* @return {Object} tok a registration "token" with a single
|
||||||
|
@ -123,9 +133,11 @@ export default class GroupStore extends EventEmitter {
|
||||||
this.on('update', fn);
|
this.on('update', fn);
|
||||||
// Call to set initial state (before fetching starts)
|
// Call to set initial state (before fetching starts)
|
||||||
this.emit('update');
|
this.emit('update');
|
||||||
this._fetchSummary();
|
|
||||||
this._fetchRooms();
|
this._fetchResource(GroupStore.STATE_KEY.Summary);
|
||||||
this._fetchMembers();
|
this._fetchResource(GroupStore.STATE_KEY.GroupRooms);
|
||||||
|
this._fetchResource(GroupStore.STATE_KEY.GroupMembers);
|
||||||
|
this._fetchResource(GroupStore.STATE_KEY.GroupInvitedMembers);
|
||||||
|
|
||||||
// Similar to the Store of flux/utils, we return a "token" that
|
// Similar to the Store of flux/utils, we return a "token" that
|
||||||
// can be used to unregister the listener.
|
// can be used to unregister the listener.
|
||||||
|
@ -145,90 +157,94 @@ export default class GroupStore extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
getSummary() {
|
getSummary() {
|
||||||
return this._summary;
|
return this._state[GroupStore.STATE_KEY.Summary];
|
||||||
}
|
}
|
||||||
|
|
||||||
getGroupRooms() {
|
getGroupRooms() {
|
||||||
return this._rooms;
|
return this._state[GroupStore.STATE_KEY.GroupRooms];
|
||||||
}
|
}
|
||||||
|
|
||||||
getGroupMembers( ) {
|
getGroupMembers() {
|
||||||
return this._members;
|
return this._state[GroupStore.STATE_KEY.GroupMembers];
|
||||||
}
|
}
|
||||||
|
|
||||||
getGroupInvitedMembers( ) {
|
getGroupInvitedMembers() {
|
||||||
return this._invitedMembers;
|
return this._state[GroupStore.STATE_KEY.GroupInvitedMembers];
|
||||||
}
|
}
|
||||||
|
|
||||||
getGroupPublicity() {
|
getGroupPublicity() {
|
||||||
return this._summary.user ? this._summary.user.is_publicised : null;
|
return this._state[GroupStore.STATE_KEY.Summary].user ?
|
||||||
|
this._state[GroupStore.STATE_KEY.Summary].user.is_publicised : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
isUserPrivileged() {
|
isUserPrivileged() {
|
||||||
return this._summary.user ? this._summary.user.is_privileged : null;
|
return this._state[GroupStore.STATE_KEY.Summary].user ?
|
||||||
|
this._state[GroupStore.STATE_KEY.Summary].user.is_privileged : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
addRoomToGroup(roomId, isPublic) {
|
addRoomToGroup(roomId, isPublic) {
|
||||||
return MatrixClientPeg.get()
|
return MatrixClientPeg.get()
|
||||||
.addRoomToGroup(this.groupId, roomId, isPublic)
|
.addRoomToGroup(this.groupId, roomId, isPublic)
|
||||||
.then(this._fetchRooms.bind(this));
|
.then(this._fetchResource.bind(this, GroupStore.STATE_KEY.GroupRooms));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGroupRoomVisibility(roomId, isPublic) {
|
updateGroupRoomVisibility(roomId, isPublic) {
|
||||||
return MatrixClientPeg.get()
|
return MatrixClientPeg.get()
|
||||||
.updateGroupRoomVisibility(this.groupId, roomId, isPublic)
|
.updateGroupRoomVisibility(this.groupId, roomId, isPublic)
|
||||||
.then(this._fetchRooms.bind(this));
|
.then(this._fetchResource.bind(this, GroupStore.STATE_KEY.GroupRooms));
|
||||||
}
|
}
|
||||||
|
|
||||||
removeRoomFromGroup(roomId) {
|
removeRoomFromGroup(roomId) {
|
||||||
return MatrixClientPeg.get()
|
return MatrixClientPeg.get()
|
||||||
.removeRoomFromGroup(this.groupId, roomId)
|
.removeRoomFromGroup(this.groupId, roomId)
|
||||||
// Room might be in the summary, refresh just in case
|
// Room might be in the summary, refresh just in case
|
||||||
.then(this._fetchSummary.bind(this))
|
.then(this._fetchResource.bind(this, GroupStore.STATE_KEY.Summary))
|
||||||
.then(this._fetchRooms.bind(this));
|
.then(this._fetchResource.bind(this, GroupStore.STATE_KEY.GroupRooms));
|
||||||
}
|
}
|
||||||
|
|
||||||
inviteUserToGroup(userId) {
|
inviteUserToGroup(userId) {
|
||||||
return MatrixClientPeg.get().inviteUserToGroup(this.groupId, userId)
|
return MatrixClientPeg.get().inviteUserToGroup(this.groupId, userId)
|
||||||
.then(this._fetchMembers.bind(this));
|
.then(this._fetchResource.bind(this, GroupStore.STATE_KEY.GroupInvitedMembers));
|
||||||
}
|
}
|
||||||
|
|
||||||
acceptGroupInvite() {
|
acceptGroupInvite() {
|
||||||
return MatrixClientPeg.get().acceptGroupInvite(this.groupId)
|
return MatrixClientPeg.get().acceptGroupInvite(this.groupId)
|
||||||
// The user might be able to see more rooms now
|
// The user might be able to see more rooms now
|
||||||
.then(this._fetchRooms.bind(this))
|
.then(this._fetchResource.bind(this, GroupStore.STATE_KEY.GroupRooms))
|
||||||
// The user should now appear as a member
|
// The user should now appear as a member
|
||||||
.then(this._fetchMembers.bind(this));
|
.then(this._fetchResource.bind(this, GroupStore.STATE_KEY.GroupMembers))
|
||||||
|
// The user should now not appear as an invited member
|
||||||
|
.then(this._fetchResource.bind(this, GroupStore.STATE_KEY.GroupInvitedMembers));
|
||||||
}
|
}
|
||||||
|
|
||||||
addRoomToGroupSummary(roomId, categoryId) {
|
addRoomToGroupSummary(roomId, categoryId) {
|
||||||
return MatrixClientPeg.get()
|
return MatrixClientPeg.get()
|
||||||
.addRoomToGroupSummary(this.groupId, roomId, categoryId)
|
.addRoomToGroupSummary(this.groupId, roomId, categoryId)
|
||||||
.then(this._fetchSummary.bind(this));
|
.then(this._fetchResource.bind(this, GroupStore.STATE_KEY.Summary));
|
||||||
}
|
}
|
||||||
|
|
||||||
addUserToGroupSummary(userId, roleId) {
|
addUserToGroupSummary(userId, roleId) {
|
||||||
return MatrixClientPeg.get()
|
return MatrixClientPeg.get()
|
||||||
.addUserToGroupSummary(this.groupId, userId, roleId)
|
.addUserToGroupSummary(this.groupId, userId, roleId)
|
||||||
.then(this._fetchSummary.bind(this));
|
.then(this._fetchResource.bind(this, GroupStore.STATE_KEY.Summary));
|
||||||
}
|
}
|
||||||
|
|
||||||
removeRoomFromGroupSummary(roomId) {
|
removeRoomFromGroupSummary(roomId) {
|
||||||
return MatrixClientPeg.get()
|
return MatrixClientPeg.get()
|
||||||
.removeRoomFromGroupSummary(this.groupId, roomId)
|
.removeRoomFromGroupSummary(this.groupId, roomId)
|
||||||
.then(this._fetchSummary.bind(this));
|
.then(this._fetchResource.bind(this, GroupStore.STATE_KEY.Summary));
|
||||||
}
|
}
|
||||||
|
|
||||||
removeUserFromGroupSummary(userId) {
|
removeUserFromGroupSummary(userId) {
|
||||||
return MatrixClientPeg.get()
|
return MatrixClientPeg.get()
|
||||||
.removeUserFromGroupSummary(this.groupId, userId)
|
.removeUserFromGroupSummary(this.groupId, userId)
|
||||||
.then(this._fetchSummary.bind(this));
|
.then(this._fetchResource.bind(this, GroupStore.STATE_KEY.Summary));
|
||||||
}
|
}
|
||||||
|
|
||||||
setGroupPublicity(isPublished) {
|
setGroupPublicity(isPublished) {
|
||||||
return MatrixClientPeg.get()
|
return MatrixClientPeg.get()
|
||||||
.setGroupPublicity(this.groupId, isPublished)
|
.setGroupPublicity(this.groupId, isPublished)
|
||||||
.then(() => { FlairStore.invalidatePublicisedGroups(MatrixClientPeg.get().credentials.userId); })
|
.then(() => { FlairStore.invalidatePublicisedGroups(MatrixClientPeg.get().credentials.userId); })
|
||||||
.then(this._fetchSummary.bind(this));
|
.then(this._fetchResource.bind(this, GroupStore.STATE_KEY.Summary));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,11 @@ class TagOrderStore extends Store {
|
||||||
// Get ordering from account data
|
// Get ordering from account data
|
||||||
case 'MatrixActions.accountData': {
|
case 'MatrixActions.accountData': {
|
||||||
if (payload.event_type !== 'im.vector.web.tag_ordering') break;
|
if (payload.event_type !== 'im.vector.web.tag_ordering') break;
|
||||||
|
|
||||||
|
// Ignore remote echos caused by this store so as to avoid setting
|
||||||
|
// state back to old state.
|
||||||
|
if (payload.event_content._storeId === this.getStoreId()) break;
|
||||||
|
|
||||||
this._setState({
|
this._setState({
|
||||||
orderedTagsAccountData: payload.event_content ? payload.event_content.tags : null,
|
orderedTagsAccountData: payload.event_content ? payload.event_content.tags : null,
|
||||||
});
|
});
|
||||||
|
@ -78,24 +83,11 @@ class TagOrderStore extends Store {
|
||||||
this._updateOrderedTags();
|
this._updateOrderedTags();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Puts payload.tag at payload.targetTag, placing the targetTag before or after the tag
|
case 'TagOrderActions.moveTag.pending': {
|
||||||
case 'order_tag': {
|
// Optimistic update of a moved tag
|
||||||
if (!this._state.orderedTags ||
|
this._setState({
|
||||||
!payload.tag ||
|
orderedTags: payload.request.tags,
|
||||||
!payload.targetTag ||
|
});
|
||||||
payload.tag === payload.targetTag
|
|
||||||
) return;
|
|
||||||
|
|
||||||
const tags = this._state.orderedTags;
|
|
||||||
|
|
||||||
let orderedTags = tags.filter((t) => t !== payload.tag);
|
|
||||||
const newIndex = orderedTags.indexOf(payload.targetTag) + (payload.after ? 1 : 0);
|
|
||||||
orderedTags = [
|
|
||||||
...orderedTags.slice(0, newIndex),
|
|
||||||
payload.tag,
|
|
||||||
...orderedTags.slice(newIndex),
|
|
||||||
];
|
|
||||||
this._setState({orderedTags});
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'select_tag': {
|
case 'select_tag': {
|
||||||
|
@ -189,6 +181,13 @@ class TagOrderStore extends Store {
|
||||||
return this._state.orderedTags;
|
return this._state.orderedTags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStoreId() {
|
||||||
|
// Generate a random ID to prevent this store from clobbering its
|
||||||
|
// state with redundant remote echos.
|
||||||
|
if (!this._id) this._id = Math.random().toString(16).slice(2, 10);
|
||||||
|
return this._id;
|
||||||
|
}
|
||||||
|
|
||||||
getSelectedTags() {
|
getSelectedTags() {
|
||||||
return this._state.selectedTags;
|
return this._state.selectedTags;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue