-
-
Notifications
You must be signed in to change notification settings - Fork 832
Order tab complete by most recently spoke #341
Changes from all commits
0dde891
2ce521f
f1d7229
d5bed78
7d712d0
327015b
ccf8e26
5c566ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
var Entry = require("./TabCompleteEntries").Entry; | ||
|
||
import { Entry, MemberEntry, CommandEntry } from './TabCompleteEntries'; | ||
import SlashCommands from './SlashCommands'; | ||
import MatrixClientPeg from './MatrixClientPeg'; | ||
|
||
const DELAY_TIME_MS = 1000; | ||
const KEY_TAB = 9; | ||
|
@@ -45,23 +48,39 @@ class TabComplete { | |
this.isFirstWord = false; // true if you tab-complete on the first word | ||
this.enterTabCompleteTimerId = null; | ||
this.inPassiveMode = false; | ||
|
||
// Map tracking ordering of the room members. | ||
// userId: integer, highest comes first. | ||
this.memberTabOrder = {}; | ||
|
||
// monotonically increasing counter used for tracking ordering of members | ||
this.memberOrderSeq = 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this? A number to represent the member order? But how? Is it a max size? Missing explanatory comment. |
||
} | ||
|
||
/** | ||
* @param {Entry[]} completeList | ||
* Call this when a a UI element representing a tab complete entry has been clicked | ||
* @param {entry} The entry that was clicked | ||
*/ | ||
setCompletionList(completeList) { | ||
this.list = completeList; | ||
onEntryClick(entry) { | ||
if (this.opts.onClickCompletes) { | ||
// assign onClick listeners for each entry to complete the text | ||
this.list.forEach((l) => { | ||
l.onClick = () => { | ||
this.completeTo(l); | ||
} | ||
}); | ||
this.completeTo(entry); | ||
} | ||
} | ||
|
||
loadEntries(room) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So you've changed the public API of This may turn out to be fine - but I'm worried that there will be a number of components which would want to use the functionality which There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, noticed this was designed to be independent from what it was completing. It would have required a lot more fiddling to maintain this property whilst not re-doing the search every time. This means we can just pass around the TabComplete object rather than sometimes the tabcomplete object and sometimes the entries. If we ever actually want to use tabcomplete elsewhere, we can rearrange again. If we do, I'd propose adding RoomTabComplete and FooTabComplete which both use TabComplete which would be generic again. |
||
this._makeEntries(room); | ||
this._initSorting(room); | ||
this._sortEntries(); | ||
} | ||
|
||
onMemberSpoke(member) { | ||
if (this.memberTabOrder[member.userId] === undefined) { | ||
this.list.push(new MemberEntry(member)); | ||
} | ||
this.memberTabOrder[member.userId] = this.memberOrderSeq++; | ||
this._sortEntries(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we really have to go through and re-sort the entire member list when someone speaks? I thought the whole point of this was to avoid doing that, and yet here we are. |
||
} | ||
|
||
/** | ||
* @param {DOMElement} | ||
*/ | ||
|
@@ -307,6 +326,49 @@ class TabComplete { | |
this.opts.onStateChange(this.completing); | ||
} | ||
} | ||
|
||
_sortEntries() { | ||
// largest comes first | ||
const KIND_ORDER = { | ||
command: 1, | ||
member: 2, | ||
}; | ||
|
||
this.list.sort((a, b) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we know what sorting algorithm this uses? The first sort will contain completely unsorted data, but subsequent sorts are done with nearly-sorted data (only 1 element needs shifting; the person who spoke). I care because this function is called for every incoming timeline event. If we use insertion sort this can be made very quick since we're operating on nearly-sorted data sets. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't believe it's specified, although you're correct: I'm assuming that sorting a very-nearly sorted list should be fast: at worst, I think it's about the same which, for the number of things we're sorting, is probably fine. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just did a bit of digging, and indeed it is not specified in ECMAScript. Each browser implements different sorting algorithms for
Fun times. |
||
const kindOrderDifference = KIND_ORDER[b.kind] - KIND_ORDER[a.kind]; | ||
if (kindOrderDifference != 0) { | ||
return kindOrderDifference; | ||
} | ||
|
||
if (a.kind == 'member') { | ||
return this.memberTabOrder[b.member.userId] - this.memberTabOrder[a.member.userId]; | ||
} | ||
|
||
// anything else we have no ordering for | ||
return 0; | ||
}); | ||
} | ||
|
||
_makeEntries(room) { | ||
const myUserId = MatrixClientPeg.get().credentials.userId; | ||
|
||
const members = room.getJoinedMembers().filter(function(member) { | ||
if (member.userId !== myUserId) return true; | ||
}); | ||
|
||
this.list = MemberEntry.fromMemberList(members).concat( | ||
CommandEntry.fromCommands(SlashCommands.getCommandList()) | ||
); | ||
} | ||
|
||
_initSorting(room) { | ||
this.memberTabOrder = {}; | ||
this.memberOrderSeq = 0; | ||
|
||
for (const ev of room.getLiveTimeline().getEvents()) { | ||
this.memberTabOrder[ev.getSender()] = this.memberOrderSeq++; | ||
} | ||
} | ||
}; | ||
|
||
module.exports = TabComplete; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,10 +31,7 @@ var Modal = require("../../Modal"); | |
var sdk = require('../../index'); | ||
var CallHandler = require('../../CallHandler'); | ||
var TabComplete = require("../../TabComplete"); | ||
var MemberEntry = require("../../TabCompleteEntries").MemberEntry; | ||
var CommandEntry = require("../../TabCompleteEntries").CommandEntry; | ||
var Resend = require("../../Resend"); | ||
var SlashCommands = require("../../SlashCommands"); | ||
var dis = require("../../dispatcher"); | ||
var Tinter = require("../../Tinter"); | ||
var rate_limited_func = require('../../ratelimitedfunc'); | ||
|
@@ -205,8 +202,8 @@ module.exports = React.createClass({ | |
MatrixClientPeg.get().credentials.userId, 'join' | ||
); | ||
|
||
// update the tab complete list now we have a room | ||
this._updateTabCompleteList(); | ||
this._updateAutoComplete(); | ||
this.tabComplete.loadEntries(this.state.room); | ||
} | ||
|
||
if (!user_is_in_room && this.state.roomId) { | ||
|
@@ -360,6 +357,14 @@ module.exports = React.createClass({ | |
}); | ||
} | ||
} | ||
|
||
// update the tab complete list as it depends on who most recently spoke, | ||
// and that has probably just changed | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm worried about the possible performance implications of this. For every new event which appears in the room, we will do A better way to sort them would be to sort them once on |
||
if (ev.sender) { | ||
this.tabComplete.onMemberSpoke(ev.sender); | ||
// nb. we don't need to update the new autocomplete here since | ||
// its results are currently ordered purely by search score. | ||
} | ||
}, | ||
|
||
// called when state.room is first initialised (either at initial load, | ||
|
@@ -437,7 +442,8 @@ module.exports = React.createClass({ | |
} | ||
|
||
// a member state changed in this room, refresh the tab complete list | ||
this._updateTabCompleteList(); | ||
this.tabComplete.loadEntries(this.state.room); | ||
this._updateAutoComplete(); | ||
|
||
// if we are now a member of the room, where we were not before, that | ||
// means we have finished joining a room we were previously peeking | ||
|
@@ -502,8 +508,6 @@ module.exports = React.createClass({ | |
window.addEventListener('resize', this.onResize); | ||
this.onResize(); | ||
|
||
this._updateTabCompleteList(); | ||
|
||
// XXX: EVIL HACK to autofocus inviting on empty rooms. | ||
// We use the setTimeout to avoid racing with focus_composer. | ||
if (this.state.room && | ||
|
@@ -521,24 +525,6 @@ module.exports = React.createClass({ | |
} | ||
}, | ||
|
||
_updateTabCompleteList: function() { | ||
var cli = MatrixClientPeg.get(); | ||
|
||
if (!this.state.room) { | ||
return; | ||
} | ||
var members = this.state.room.getJoinedMembers().filter(function(member) { | ||
if (member.userId !== cli.credentials.userId) return true; | ||
}); | ||
|
||
UserProvider.getInstance().setUserList(members); | ||
this.tabComplete.setCompletionList( | ||
MemberEntry.fromMemberList(members).concat( | ||
CommandEntry.fromCommands(SlashCommands.getCommandList()) | ||
) | ||
); | ||
}, | ||
|
||
componentDidUpdate: function() { | ||
if (this.refs.roomView) { | ||
var roomView = ReactDOM.findDOMNode(this.refs.roomView); | ||
|
@@ -1263,6 +1249,14 @@ module.exports = React.createClass({ | |
} | ||
}, | ||
|
||
_updateAutoComplete: function() { | ||
const myUserId = MatrixClientPeg.get().credentials.userId; | ||
const members = this.state.room.getJoinedMembers().filter(function(member) { | ||
if (member.userId !== myUserId) return true; | ||
}); | ||
UserProvider.getInstance().setUserList(members); | ||
}, | ||
|
||
render: function() { | ||
var RoomHeader = sdk.getComponent('rooms.RoomHeader'); | ||
var MessageComposer = sdk.getComponent('rooms.MessageComposer'); | ||
|
@@ -1376,12 +1370,10 @@ module.exports = React.createClass({ | |
statusBar = <UploadBar room={this.state.room} /> | ||
} else if (!this.state.searchResults) { | ||
var RoomStatusBar = sdk.getComponent('structures.RoomStatusBar'); | ||
var tabEntries = this.tabComplete.isTabCompleting() ? | ||
this.tabComplete.peek(6) : null; | ||
|
||
statusBar = <RoomStatusBar | ||
room={this.state.room} | ||
tabCompleteEntries={tabEntries} | ||
tabComplete={this.tabComplete} | ||
numUnreadMessages={this.state.numUnreadMessages} | ||
hasUnsentMessages={this.state.hasUnsentMessages} | ||
atEndOfLiveTimeline={this.state.atEndOfLiveTimeline} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this? A map? What are the keys? Integers to represent the ordering? User IDs? What are the values? Comment please!