diff --git a/.prettierignore b/.prettierignore index b4e6fd6e264..2d6a8da1ef7 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,16 +4,16 @@ data font-awesome jquery juke +**/build +**/dist +**/node_modules ## Tgui node_modules .yarn -# Avoid running on any bundles. -tgui/public -# Running it on tgui.html is fine, however. -!/tgui/public/tgui.html # Specific files package-lock.json # File names / types *.min.* *.pnp.* +*.bundle.* diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 58fb173b1cf..2c7a5b97865 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -109,13 +109,6 @@ #define WAIT_ARRIVE 3 #define WAIT_FINISH 4 -// Setting this much higher than 1024 could allow spammers to DOS the server easily. -#define MAX_MESSAGE_LEN 1024 -#define MAX_PAPER_MESSAGE_LEN 3072 -#define MAX_BOOK_MESSAGE_LEN 9216 -#define MAX_LNAME_LEN 64 -#define MAX_NAME_LEN 63 - // Event defines. #define EVENT_LEVEL_MUNDANE 1 #define EVENT_LEVEL_MODERATE 2 diff --git a/code/__DEFINES/text.dm b/code/__DEFINES/text.dm index c31e45f3128..63bafbaa4c4 100644 --- a/code/__DEFINES/text.dm +++ b/code/__DEFINES/text.dm @@ -49,6 +49,14 @@ /// Max width of chat message in pixels #define CHAT_MESSAGE_WIDTH 112 +// Setting this much higher than 1024 could allow spammers to DOS the server easily. +#define MAX_MESSAGE_LEN 1024 +#define MAX_PAPER_MESSAGE_LEN 3072 +#define MAX_BOOK_MESSAGE_LEN 9216 +#define MAX_LNAME_LEN 64 +#define MAX_NAME_LEN 63 +#define MAX_MESSAGE_CHUNKS 20 + //All < and > characters GLOBAL_DATUM_INIT(angular_brackets, /regex, regex(@"[<>]", "g")) diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 12334fafcaf..4158a27a974 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -406,7 +406,7 @@ GLOBAL_LIST_INIT(localhost_addresses, list( send_resources() if(!winexists(src, "asset_cache_browser")) // The client is using a custom skin, tell them. - to_chat(src, SPAN_WARNING("Unable to access asset cache browser, if you are using a custom skin file, please allow DS to download the updated version, if you are not, then make a bug report. This is not a critical issue but can cause issues with resource downloading, as it is impossible to know when extra resources arrived to you.")) + to_chat_immediate(src, SPAN_WARNING("Unable to access asset cache browser, if you are using a custom skin file, please allow DS to download the updated version, if you are not, then make a bug report. This is not a critical issue but can cause issues with resource downloading, as it is impossible to know when extra resources arrived to you.")) Master.UpdateTickRate() fully_created = TRUE diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index d1dbc302688..62b40969958 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -40,6 +40,9 @@ /// Are byond mouse events beyond the window passed in to the ui var/mouse_hooked = FALSE + /// Any partial packets that we have received from TGUI, waiting to be sent + var/partial_packets + /** * public * @@ -326,6 +329,31 @@ // Pass act type messages to ui_act if(type && copytext(type, 1, 5) == "act/") var/act_type = copytext(type, 5) + + var/id = href_list["packetId"] + if(!isnull(id)) + id = text2num(id) + + var/total = text2num(href_list["totalPackets"]) + + if(total > MAX_MESSAGE_CHUNKS) + return + + if(id == 1) + partial_packets = new /list(total) + + partial_packets[id] = href_list["packet"] + + if(id != total) + return + + var/assembled_payload = "" + for(var/packet in partial_packets) + assembled_payload += packet + + payload = json_decode(assembled_payload) + partial_packets = null + log_tgui(user, "Action: [act_type] [href_list["payload"]]", window = window, src_object = src_object) diff --git a/html/changelogs/johnwildkins-powertotheplayers.yml b/html/changelogs/johnwildkins-powertotheplayers.yml new file mode 100644 index 00000000000..6df6d1dd195 --- /dev/null +++ b/html/changelogs/johnwildkins-powertotheplayers.yml @@ -0,0 +1,14 @@ +# Your name. +author: JohnWildkins + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit. +# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog. +changes: + - refactor: "Updates TGUI backend to hopefully reduce instability / failures to load." + - bugfix: "Fixes TGchat scrolling issues." diff --git a/interface/skin.dmf b/interface/skin.dmf index 73fb059bafd..9607bf37064 100644 --- a/interface/skin.dmf +++ b/interface/skin.dmf @@ -1281,6 +1281,7 @@ window "output_browser" size = 640x456 anchor1 = 0,0 anchor2 = 100,100 + background-color = none saved-params = "" window "preferences_window" diff --git a/tgui/packages/tgui-panel/chat/renderer.js b/tgui/packages/tgui-panel/chat/renderer.js index 318388b568e..59a1d0cddf0 100644 --- a/tgui/packages/tgui-panel/chat/renderer.js +++ b/tgui/packages/tgui-panel/chat/renderer.js @@ -128,12 +128,17 @@ class ChatRenderer { /** @type {HTMLElement} */ this.scrollNode = null; this.scrollTracking = true; + this.lastScrollHeight = 0; this.handleScroll = (type) => { const node = this.scrollNode; + if (!node) { + return; + } const height = node.scrollHeight; const bottom = node.scrollTop + node.offsetHeight; const scrollTracking = - Math.abs(height - bottom) < SCROLL_TRACKING_TOLERANCE; + Math.abs(height - bottom) < SCROLL_TRACKING_TOLERANCE || + this.lastScrollHeight === 0; if (scrollTracking !== this.scrollTracking) { this.scrollTracking = scrollTracking; this.events.emit('scrollTrackingChanged', scrollTracking); @@ -160,12 +165,6 @@ class ChatRenderer { else { this.rootNode = node; } - // Find scrollable parent - this.scrollNode = findNearestScrollableParent(this.rootNode); - this.scrollNode.addEventListener('scroll', this.handleScroll); - setImmediate(() => { - this.scrollToBottom(); - }); // Flush the queue this.tryFlushQueue(); } @@ -179,6 +178,7 @@ class ChatRenderer { if (this.isReady() && this.queue.length > 0) { this.processBatch(this.queue); this.queue = []; + this.scrollToBottom(); } } @@ -268,6 +268,7 @@ class ChatRenderer { } scrollToBottom() { + this.tryFindScrollable(); // scrollHeight is always bigger than scrollTop and is // automatically clamped to the valid range. this.scrollNode.scrollTop = this.scrollNode.scrollHeight; @@ -322,6 +323,17 @@ class ChatRenderer { return null; } + tryFindScrollable() { + // Find scrollable parent + if (this.rootNode) { + if (!this.scrollNode || this.scrollNode.scrollHeight === undefined) { + this.scrollNode = findNearestScrollableParent(this.rootNode); + this.scrollNode.addEventListener('scroll', this.handleScroll); + logger.debug(`reset scrollNode to ${this.scrollNode}`); + } + } + } + processBatch(batch, options = {}) { const { prepend, notifyListeners = true } = options; const now = Date.now(); @@ -334,6 +346,10 @@ class ChatRenderer { } return; } + // Store last scroll position + if (this.scrollNode) { + this.lastScrollHeight = this.scrollNode.scrollHeight; + } // Insert messages const fragment = document.createDocumentFragment(); const countByType = {}; diff --git a/tgui/packages/tgui-panel/panelFocus.js b/tgui/packages/tgui-panel/panelFocus.ts similarity index 85% rename from tgui/packages/tgui-panel/panelFocus.js rename to tgui/packages/tgui-panel/panelFocus.ts index b7cea229314..2ae69e0aa51 100644 --- a/tgui/packages/tgui-panel/panelFocus.js +++ b/tgui/packages/tgui-panel/panelFocus.ts @@ -19,9 +19,9 @@ const deferredFocusMap = () => setImmediate(() => focusMap()); export const setupPanelFocusHacks = () => { let focusStolen = false; - let clickStartPos = null; + let clickStartPos: number[] | null = null; window.addEventListener('focusin', (e) => { - focusStolen = canStealFocus(e.target); + focusStolen = canStealFocus(e.target as HTMLElement); }); window.addEventListener('mousedown', (e) => { clickStartPos = [e.screenX, e.screenY]; @@ -33,6 +33,9 @@ export const setupPanelFocusHacks = () => { if (dist >= MIN_SELECTION_DISTANCE) { focusStolen = true; } + if (document.activeElement?.className?.includes('Button')) { + focusStolen = true; + } } if (!focusStolen) { deferredFocusMap(); diff --git a/tgui/public/tgui.html b/tgui/public/tgui.html index fc6a569cfd1..fadae54e191 100644 --- a/tgui/public/tgui.html +++ b/tgui/public/tgui.html @@ -201,12 +201,43 @@ } }; + let MAX_PACKET_SIZE = 1024; + Byond.sendMessage = function (type, payload) { var message = typeof type === 'string' ? { type: type, payload: payload } : type; // JSON-encode the payload + if (message.payload !== null && message.payload !== undefined) { message.payload = JSON.stringify(message.payload); + + if (!Byond.TRIDENT && message.payload.length > MAX_PACKET_SIZE) { + var chunks = []; + + for ( + var i = 0, charsLength = message.payload.length; + i < charsLength; + i += MAX_PACKET_SIZE + ) { + chunks.push(message.payload.substring(i, i + MAX_PACKET_SIZE)); + } + + for (var i = 0; i < chunks.length; i++) { + var to_send = chunks[i]; + + message = { + type: type, + packet: to_send, + packetId: i + 1, + totalPackets: chunks.length, + tgui: 1, + window_id: Byond.windowId, + }; + Byond.topic(message); + } + + return; + } } // Append an identifying header assign(message, { @@ -699,22 +730,22 @@