Replace quadratic emote list impl with Map

This commit is contained in:
Calvin Montgomery 2018-01-18 19:34:57 -08:00
parent 8399eab33f
commit 0f9bc44925
7 changed files with 57 additions and 153 deletions

View file

@ -2,7 +2,7 @@
"author": "Calvin Montgomery", "author": "Calvin Montgomery",
"name": "CyTube", "name": "CyTube",
"description": "Online media synchronizer and chat", "description": "Online media synchronizer and chat",
"version": "3.52.3", "version": "3.53.0",
"repository": { "repository": {
"url": "http://github.com/calzoneman/sync" "url": "http://github.com/calzoneman/sync"
}, },

View file

@ -1,96 +1,42 @@
var ChannelModule = require("./module"); var ChannelModule = require("./module");
var XSS = require("../xss"); var XSS = require("../xss");
function EmoteList(defaults) { class EmoteList {
if (!defaults) { constructor(list) {
defaults = []; if (!list) {
list = [];
}
this.emotes = new Map(list.map(e => [e.name, e]));
} }
this.emotes = defaults.map(validateEmote).filter(function (f) { toJSON() {
return f !== false; let list = [];
});
for (let [key, value] of this.emotes.entries()) {
list.push(value);
}
return list;
}
hasEmote(name) {
return this.emotes.has(name);
}
setEmote(name, emote) {
this.emotes.set(name, emote);
}
deleteEmote(name) {
return this.emotes.delete(name);
}
size() {
return this.emotes.size;
}
} }
EmoteList.prototype = {
pack: function () {
return Array.prototype.slice.call(this.emotes);
},
importList: function (emotes) {
this.emotes = Array.prototype.slice.call(emotes);
},
emoteExists: function (emote){
if(this.emotes.filter((item)=>{ return item.name === emote.name }).length){
return true;
}
return false;
},
renameEmote: function (emote) {
var found = false;
for (var i = 0; i < this.emotes.length; i++) {
if (this.emotes[i].name === emote.old) {
found = true;
this.emotes[i] = emote;
delete this.emotes[i].old;
break;
}
}
if(found){
return true;
}
return false;
},
updateEmote: function (emote) {
var found = false;
for (var i = 0; i < this.emotes.length; i++) {
if (this.emotes[i].name === emote.name) {
found = true;
this.emotes[i] = emote;
break;
}
}
/* If no emote was updated, add a new one */
if (!found) {
this.emotes.push(emote);
}
},
removeEmote: function (emote) {
var found = false;
for (var i = 0; i < this.emotes.length; i++) {
if (this.emotes[i].name === emote.name) {
this.emotes.splice(i, 1);
break;
}
}
},
moveEmote: function (from, to) {
if (from < 0 || to < 0 ||
from >= this.emotes.length || to >= this.emotes.length) {
return false;
}
var f = this.emotes[from];
/* Offset from/to indexes to account for the fact that removing
an element changes the position of one of them.
I could have just done a swap, but it's already implemented this way
and it works. */
to = to > from ? to + 1 : to;
from = to > from ? from : from + 1;
this.emotes.splice(to, 0, f);
this.emotes.splice(from, 1);
return true;
},
};
function validateEmote(f) { function validateEmote(f) {
if (typeof f.name !== "string" || typeof f.image !== "string") { if (typeof f.name !== "string" || typeof f.image !== "string") {
return false; return false;
@ -126,21 +72,19 @@ EmoteModule.prototype = Object.create(ChannelModule.prototype);
EmoteModule.prototype.load = function (data) { EmoteModule.prototype.load = function (data) {
if ("emotes" in data) { if ("emotes" in data) {
for (var i = 0; i < data.emotes.length; i++) { this.emotes = new EmoteList(data.emotes);
this.emotes.updateEmote(data.emotes[i]);
}
} }
this.dirty = false; this.dirty = false;
}; };
EmoteModule.prototype.save = function (data) { EmoteModule.prototype.save = function (data) {
data.emotes = this.emotes.pack(); data.emotes = this.emotes.toJSON();
}; };
EmoteModule.prototype.packInfo = function (data, isAdmin) { EmoteModule.prototype.packInfo = function (data, isAdmin) {
if (isAdmin) { if (isAdmin) {
data.emoteCount = this.emotes.emotes.length; data.emoteCount = this.emotes.size();
} }
}; };
@ -148,13 +92,12 @@ EmoteModule.prototype.onUserPostJoin = function (user) {
user.socket.on("renameEmote", this.handleRenameEmote.bind(this, user)); user.socket.on("renameEmote", this.handleRenameEmote.bind(this, user));
user.socket.on("updateEmote", this.handleUpdateEmote.bind(this, user)); user.socket.on("updateEmote", this.handleUpdateEmote.bind(this, user));
user.socket.on("importEmotes", this.handleImportEmotes.bind(this, user)); user.socket.on("importEmotes", this.handleImportEmotes.bind(this, user));
user.socket.on("moveEmote", this.handleMoveEmote.bind(this, user));
user.socket.on("removeEmote", this.handleRemoveEmote.bind(this, user)); user.socket.on("removeEmote", this.handleRemoveEmote.bind(this, user));
this.sendEmotes([user]); this.sendEmotes([user]);
}; };
EmoteModule.prototype.sendEmotes = function (users) { EmoteModule.prototype.sendEmotes = function (users) {
var f = this.emotes.pack(); var f = this.emotes.toJSON();
var chan = this.channel; var chan = this.channel;
users.forEach(function (u) { users.forEach(function (u) {
u.socket.emit("emoteList", f); u.socket.emit("emoteList", f);
@ -178,7 +121,7 @@ EmoteModule.prototype.handleRenameEmote = function (user, data) {
return; return;
} }
var e = this.emotes.emoteExists(data); var e = this.emotes.hasEmote(data.name);
var f = validateEmote(data); var f = validateEmote(data);
if (!f || e) { if (!f || e) {
var message = "Unable to rename emote '" + JSON.stringify(data) + "'. " + var message = "Unable to rename emote '" + JSON.stringify(data) + "'. " +
@ -197,9 +140,17 @@ EmoteModule.prototype.handleRenameEmote = function (user, data) {
return; return;
} }
// See comment above var hadOld = this.emotes.deleteEmote(f.old);
var success = this.emotes.renameEmote(Object.assign({}, f));
if(!success){ return; } if (!hadOld) {
return;
}
this.emotes.setEmote(f.name, {
name: f.name,
source: f.source,
image: f.image
});
this.dirty = true; this.dirty = true;
@ -232,7 +183,11 @@ EmoteModule.prototype.handleUpdateEmote = function (user, data) {
return; return;
} }
this.emotes.updateEmote(f); this.emotes.setEmote(f.name, {
name: f.name,
source: f.source,
image: f.image
});
this.dirty = true; this.dirty = true;
@ -254,9 +209,7 @@ EmoteModule.prototype.handleImportEmotes = function (user, data) {
return; return;
} }
this.emotes.importList(data.map(validateEmote).filter(function (f) { this.emotes = new EmoteList(data.map(validateEmote).filter(f => f));
return f !== false;
}));
this.dirty = true; this.dirty = true;
@ -276,7 +229,7 @@ EmoteModule.prototype.handleRemoveEmote = function (user, data) {
return; return;
} }
this.emotes.removeEmote(data); this.emotes.deleteEmote(data.name);
this.dirty = true; this.dirty = true;
@ -284,22 +237,4 @@ EmoteModule.prototype.handleRemoveEmote = function (user, data) {
this.channel.broadcastAll("removeEmote", data); this.channel.broadcastAll("removeEmote", data);
}; };
EmoteModule.prototype.handleMoveEmote = function (user, data) {
if (typeof data !== "object") {
return;
}
if (!this.channel.modules.permissions.canEditEmotes(user)) {
return;
}
if (typeof data.to !== "number" || typeof data.from !== "number") {
return;
}
this.emotes.moveEmote(data.from, data.to);
this.dirty = true;
};
module.exports = EmoteModule; module.exports = EmoteModule;

View file

@ -189,11 +189,6 @@ html(lang="en")
.modal-body .modal-body
.pull-left .pull-left
input.emotelist-search.form-control(type="text", placeholder="Search") input.emotelist-search.form-control(type="text", placeholder="Search")
.pull-right
.checkbox
label
input.emotelist-alphabetical(type="checkbox")
| Sort alphabetically
.emotelist-paginator-container .emotelist-paginator-container
table.emotelist-table table.emotelist-table
tbody tbody

View file

@ -197,11 +197,6 @@ mixin emotes
form.form-inline form.form-inline
.form-group .form-group
input.emotelist-search.form-control(type="text", placeholder="Search") input.emotelist-search.form-control(type="text", placeholder="Search")
.form-group
.checkbox
label
input.emotelist-alphabetical(type="checkbox")
| Sort alphabetically
.emotelist-paginator-container .emotelist-paginator-container
table.emotelist-table.table.table-striped.table-condensed table.emotelist-table.table.table-striped.table-condensed
thead thead

View file

@ -127,7 +127,6 @@ var USEROPTS = {
default_quality : getOrDefault("default_quality", "auto"), default_quality : getOrDefault("default_quality", "auto"),
boop : getOrDefault("boop", "never"), boop : getOrDefault("boop", "never"),
show_shadowchat : getOrDefault("show_shadowchat", false), show_shadowchat : getOrDefault("show_shadowchat", false),
emotelist_sort : getOrDefault("emotelist_sort", true),
no_emotes : getOrDefault("no_emotes", false), no_emotes : getOrDefault("no_emotes", false),
strip_image : getOrDefault("strip_image", false), strip_image : getOrDefault("strip_image", false),
chat_tab_method : getOrDefault("chat_tab_method", "Cycle options") chat_tab_method : getOrDefault("chat_tab_method", "Cycle options")

View file

@ -846,12 +846,6 @@ $("#emotelistbtn").click(function () {
EMOTELISTMODAL.modal(); EMOTELISTMODAL.modal();
}); });
EMOTELISTMODAL.find(".emotelist-alphabetical").change(function () {
USEROPTS.emotelist_sort = this.checked;
setOpt("emotelist_sort", USEROPTS.emotelist_sort);
});
EMOTELISTMODAL.find(".emotelist-alphabetical").prop("checked", USEROPTS.emotelist_sort);
$("#fullscreenbtn").click(function () { $("#fullscreenbtn").click(function () {
var elem = document.querySelector("#videowrap .embed-responsive"); var elem = document.querySelector("#videowrap .embed-responsive");
// this shit is why frontend web development sucks // this shit is why frontend web development sucks

View file

@ -2915,8 +2915,8 @@ function formatScriptAccessPrefs() {
function EmoteList(selector, emoteClickCallback) { function EmoteList(selector, emoteClickCallback) {
this.elem = $(selector); this.elem = $(selector);
this.sortAlphabetical = true;
this.initSearch(); this.initSearch();
this.initSortOption();
this.table = this.elem.find(".emotelist-table")[0]; this.table = this.elem.find(".emotelist-table")[0];
this.paginatorContainer = this.elem.find(".emotelist-paginator-container"); this.paginatorContainer = this.elem.find(".emotelist-paginator-container");
this.cols = 5; this.cols = 5;
@ -2944,18 +2944,6 @@ EmoteList.prototype.initSearch = function () {
}); });
}; };
EmoteList.prototype.initSortOption = function () {
this.sortOption = this.elem.find(".emotelist-alphabetical");
this.sortAlphabetical = false;
var self = this;
this.sortOption.change(function () {
self.sortAlphabetical = this.checked;
self.handleChange();
self.loadPage(0);
});
};
EmoteList.prototype.handleChange = function () { EmoteList.prototype.handleChange = function () {
this.emotes = CHANNEL.emotes.slice(); this.emotes = CHANNEL.emotes.slice();
if (this.sortAlphabetical) { if (this.sortAlphabetical) {
@ -3039,7 +3027,6 @@ function onEmoteClicked(emote) {
} }
window.EMOTELIST = new EmoteList("#emotelist", onEmoteClicked); window.EMOTELIST = new EmoteList("#emotelist", onEmoteClicked);
window.EMOTELIST.sortAlphabetical = USEROPTS.emotelist_sort;
function CSEmoteList(selector) { function CSEmoteList(selector) {
EmoteList.call(this, selector); EmoteList.call(this, selector);
@ -3192,7 +3179,6 @@ CSEmoteList.prototype.loadPage = function (page) {
}; };
window.CSEMOTELIST = new CSEmoteList("#cs-emotes"); window.CSEMOTELIST = new CSEmoteList("#cs-emotes");
window.CSEMOTELIST.sortAlphabetical = USEROPTS.emotelist_sort;
function showChannelSettings() { function showChannelSettings() {
$("#channeloptions").modal(); $("#channeloptions").modal();