From 966da1ac584349a2b32dc02290da11dc3123f5af Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Thu, 1 Feb 2018 17:39:45 -0800 Subject: [PATCH] Revert "Replace quadratic emote list impl with Map" This reverts commit 0f9bc449259171cf67901e7bc3b531ec70c55126. The original commit was not backwards compatible with use cases that users were relying on, such as emotes being sorted in insertion order by default. I will develop a new patch which fixes the performance issue in a backwards compatible way. --- package.json | 2 +- src/channel/emotes.js | 175 ++++++++++++++++++++++++----------- templates/channel.pug | 5 + templates/channeloptions.pug | 5 + www/js/data.js | 1 + www/js/ui.js | 6 ++ www/js/util.js | 16 +++- 7 files changed, 153 insertions(+), 57 deletions(-) diff --git a/package.json b/package.json index 1afee257..091100fd 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Calvin Montgomery", "name": "CyTube", "description": "Online media synchronizer and chat", - "version": "3.53.1", + "version": "3.52.4", "repository": { "url": "http://github.com/calzoneman/sync" }, diff --git a/src/channel/emotes.js b/src/channel/emotes.js index d3c6364b..5384c857 100644 --- a/src/channel/emotes.js +++ b/src/channel/emotes.js @@ -1,42 +1,96 @@ var ChannelModule = require("./module"); var XSS = require("../xss"); -class EmoteList { - constructor(list) { - if (!list) { - list = []; - } - - this.emotes = new Map(list.map(e => [e.name, e])); +function EmoteList(defaults) { + if (!defaults) { + defaults = []; } - toJSON() { - 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; - } + this.emotes = defaults.map(validateEmote).filter(function (f) { + return f !== false; + }); } +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) { if (typeof f.name !== "string" || typeof f.image !== "string") { return false; @@ -72,19 +126,21 @@ EmoteModule.prototype = Object.create(ChannelModule.prototype); EmoteModule.prototype.load = function (data) { if ("emotes" in data) { - this.emotes = new EmoteList(data.emotes); + for (var i = 0; i < data.emotes.length; i++) { + this.emotes.updateEmote(data.emotes[i]); + } } this.dirty = false; }; EmoteModule.prototype.save = function (data) { - data.emotes = this.emotes.toJSON(); + data.emotes = this.emotes.pack(); }; EmoteModule.prototype.packInfo = function (data, isAdmin) { if (isAdmin) { - data.emoteCount = this.emotes.size(); + data.emoteCount = this.emotes.emotes.length; } }; @@ -92,12 +148,13 @@ EmoteModule.prototype.onUserPostJoin = function (user) { user.socket.on("renameEmote", this.handleRenameEmote.bind(this, user)); user.socket.on("updateEmote", this.handleUpdateEmote.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)); this.sendEmotes([user]); }; EmoteModule.prototype.sendEmotes = function (users) { - var f = this.emotes.toJSON(); + var f = this.emotes.pack(); var chan = this.channel; users.forEach(function (u) { u.socket.emit("emoteList", f); @@ -121,7 +178,7 @@ EmoteModule.prototype.handleRenameEmote = function (user, data) { return; } - var e = this.emotes.hasEmote(data.name); + var e = this.emotes.emoteExists(data); var f = validateEmote(data); if (!f || e) { var message = "Unable to rename emote '" + JSON.stringify(data) + "'. " + @@ -140,17 +197,9 @@ EmoteModule.prototype.handleRenameEmote = function (user, data) { return; } - var hadOld = this.emotes.deleteEmote(f.old); - - if (!hadOld) { - return; - } - - this.emotes.setEmote(f.name, { - name: f.name, - source: f.source, - image: f.image - }); + // See comment above + var success = this.emotes.renameEmote(Object.assign({}, f)); + if(!success){ return; } this.dirty = true; @@ -183,11 +232,7 @@ EmoteModule.prototype.handleUpdateEmote = function (user, data) { return; } - this.emotes.setEmote(f.name, { - name: f.name, - source: f.source, - image: f.image - }); + this.emotes.updateEmote(f); this.dirty = true; @@ -209,7 +254,9 @@ EmoteModule.prototype.handleImportEmotes = function (user, data) { return; } - this.emotes = new EmoteList(data.map(validateEmote).filter(f => f)); + this.emotes.importList(data.map(validateEmote).filter(function (f) { + return f !== false; + })); this.dirty = true; @@ -229,7 +276,7 @@ EmoteModule.prototype.handleRemoveEmote = function (user, data) { return; } - this.emotes.deleteEmote(data.name); + this.emotes.removeEmote(data); this.dirty = true; @@ -237,4 +284,22 @@ EmoteModule.prototype.handleRemoveEmote = function (user, 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; diff --git a/templates/channel.pug b/templates/channel.pug index ace50c58..89e38daf 100644 --- a/templates/channel.pug +++ b/templates/channel.pug @@ -189,6 +189,11 @@ html(lang="en") .modal-body .pull-left input.emotelist-search.form-control(type="text", placeholder="Search") + .pull-right + .checkbox + label + input.emotelist-alphabetical(type="checkbox") + | Sort alphabetically .emotelist-paginator-container table.emotelist-table tbody diff --git a/templates/channeloptions.pug b/templates/channeloptions.pug index c350e80c..047f442d 100644 --- a/templates/channeloptions.pug +++ b/templates/channeloptions.pug @@ -197,6 +197,11 @@ mixin emotes form.form-inline .form-group input.emotelist-search.form-control(type="text", placeholder="Search") + .form-group + .checkbox + label + input.emotelist-alphabetical(type="checkbox") + | Sort alphabetically .emotelist-paginator-container table.emotelist-table.table.table-striped.table-condensed thead diff --git a/www/js/data.js b/www/js/data.js index 3a0524be..e71bfeb3 100644 --- a/www/js/data.js +++ b/www/js/data.js @@ -127,6 +127,7 @@ var USEROPTS = { default_quality : getOrDefault("default_quality", "auto"), boop : getOrDefault("boop", "never"), show_shadowchat : getOrDefault("show_shadowchat", false), + emotelist_sort : getOrDefault("emotelist_sort", true), no_emotes : getOrDefault("no_emotes", false), strip_image : getOrDefault("strip_image", false), chat_tab_method : getOrDefault("chat_tab_method", "Cycle options") diff --git a/www/js/ui.js b/www/js/ui.js index 27eb72c8..52ba5b91 100644 --- a/www/js/ui.js +++ b/www/js/ui.js @@ -846,6 +846,12 @@ $("#emotelistbtn").click(function () { 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 () { var elem = document.querySelector("#videowrap .embed-responsive"); // this shit is why frontend web development sucks diff --git a/www/js/util.js b/www/js/util.js index 51f05b44..ff47cf1b 100644 --- a/www/js/util.js +++ b/www/js/util.js @@ -2915,8 +2915,8 @@ function formatScriptAccessPrefs() { function EmoteList(selector, emoteClickCallback) { this.elem = $(selector); - this.sortAlphabetical = true; this.initSearch(); + this.initSortOption(); this.table = this.elem.find(".emotelist-table")[0]; this.paginatorContainer = this.elem.find(".emotelist-paginator-container"); this.cols = 5; @@ -2944,6 +2944,18 @@ 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 () { this.emotes = CHANNEL.emotes.slice(); if (this.sortAlphabetical) { @@ -3027,6 +3039,7 @@ function onEmoteClicked(emote) { } window.EMOTELIST = new EmoteList("#emotelist", onEmoteClicked); +window.EMOTELIST.sortAlphabetical = USEROPTS.emotelist_sort; function CSEmoteList(selector) { EmoteList.call(this, selector); @@ -3179,6 +3192,7 @@ CSEmoteList.prototype.loadPage = function (page) { }; window.CSEMOTELIST = new CSEmoteList("#cs-emotes"); +window.CSEMOTELIST.sortAlphabetical = USEROPTS.emotelist_sort; function showChannelSettings() { $("#channeloptions").modal();