From d48c5afc9b5b81c5a5041cf8c41db5284ba61732 Mon Sep 17 00:00:00 2001 From: Nick Krecklow Date: Mon, 8 Jun 2020 16:29:15 -0500 Subject: [PATCH] New hover tooltip layout, use template literals --- assets/js/favorites.js | 4 +- assets/js/graph.js | 96 +++++++++++++++++++++++++----------------- assets/js/mojang.js | 4 +- assets/js/percbar.js | 21 +++++---- assets/js/scale.js | 66 ++++++++++------------------- assets/js/servers.js | 68 +++++++++++++++--------------- assets/js/socket.js | 17 +++----- assets/js/sort.js | 4 +- assets/js/util.js | 17 +++++--- 9 files changed, 150 insertions(+), 147 deletions(-) diff --git a/assets/js/favorites.js b/assets/js/favorites.js index 873860a..b84b408 100644 --- a/assets/js/favorites.js +++ b/assets/js/favorites.js @@ -20,7 +20,7 @@ export class FavoritesManager { serverRegistration.isFavorite = true // Update icon since by default it is unfavorited - document.getElementById('favorite-toggle_' + serverRegistration.serverId).setAttribute('class', this.getIconClass(serverRegistration.isFavorite)) + document.getElementById(`favorite-toggle_${serverRegistration.serverId}`).setAttribute('class', this.getIconClass(serverRegistration.isFavorite)) } } } @@ -47,7 +47,7 @@ export class FavoritesManager { serverRegistration.isFavorite = !serverRegistration.isFavorite // Update the displayed favorite icon - document.getElementById('favorite-toggle_' + serverRegistration.serverId).setAttribute('class', this.getIconClass(serverRegistration.isFavorite)) + document.getElementById(`favorite-toggle_${serverRegistration.serverId}`).setAttribute('class', this.getIconClass(serverRegistration.isFavorite)) // Request the app controller instantly re-sort the server listing // This handles the favorite sorting logic internally diff --git a/assets/js/graph.js b/assets/js/graph.js index 199a49e..1f2305b 100644 --- a/assets/js/graph.js +++ b/assets/js/graph.js @@ -50,10 +50,7 @@ export class GraphDisplayManager { } // Paint updated data structure - this._plotInstance.setData([ - this._graphTimestamps, - ...this._graphData - ]) + this._plotInstance.setData(this.getGraphData()) } loadLocalStorage () { @@ -127,6 +124,13 @@ export class GraphDisplayManager { } } + getGraphData () { + return [ + this._graphTimestamps, + ...this._graphData + ] + } + getGraphDataPoint (serverId, index) { const graphData = this._graphData[serverId] if (graphData && index < graphData.length && typeof graphData[index] === 'number') { @@ -134,6 +138,37 @@ export class GraphDisplayManager { } } + getClosestPlotSeriesIndex (idx) { + let closestSeriesIndex = -1 + let closestSeriesDist = Number.MAX_VALUE + + for (let i = 1; i < this._plotInstance.series.length; i++) { + const series = this._plotInstance.series[i] + + if (!series.show) { + continue + } + + const point = this._plotInstance.data[i][idx] + + if (typeof point === 'number') { + const scale = this._plotInstance.scales[series.scale] + const posY = (1 - ((point - scale.min) / (scale.max - scale.min))) * this._plotInstance.height + + // +20 to offset some sort of strange calculation bug + // cursor.top does not seem to correctly align with the generated posY values + const dist = Math.abs(posY - (this._plotInstance.cursor.top + 20)) + + if (dist < closestSeriesDist) { + closestSeriesIndex = i + closestSeriesDist = dist + } + } + } + + return closestSeriesIndex + } + buildPlotInstance (timestamps, data) { // Lazy load settings from localStorage, if any and if enabled if (!this._hasLoadedSettings) { @@ -150,7 +185,7 @@ export class GraphDisplayManager { scale: 'Players', stroke: serverRegistration.data.color, width: 2, - value: (_, raw) => formatNumber(raw) + ' Players', + value: (_, raw) => `${formatNumber(raw)} Players`, show: serverRegistration.isVisible, spanGaps: true, points: { @@ -165,44 +200,32 @@ export class GraphDisplayManager { // eslint-disable-next-line new-cap this._plotInstance = new uPlot({ plugins: [ - uPlotTooltipPlugin((pos, id) => { + uPlotTooltipPlugin((pos, idx) => { if (pos) { - let text = this._app.serverRegistry.getServerRegistrations() + const closestSeriesIndex = this.getClosestPlotSeriesIndex(idx) + + const text = this._app.serverRegistry.getServerRegistrations() .filter(serverRegistration => serverRegistration.isVisible) .sort((a, b) => { if (a.isFavorite !== b.isFavorite) { return a.isFavorite ? -1 : 1 - } - - const aPoint = this.getGraphDataPoint(a.serverId, id) - const bPoint = this.getGraphDataPoint(b.serverId, id) - - if (typeof aPoint === typeof bPoint) { - if (typeof aPoint === 'undefined') { - return 0 - } } else { - return typeof aPoint === 'number' ? -1 : 1 + return a.data.name.localeCompare(b.data.name) } - - return bPoint - aPoint }) .map(serverRegistration => { - const point = this.getGraphDataPoint(serverRegistration.serverId, id) + const point = this.getGraphDataPoint(serverRegistration.serverId, idx) let serverName = serverRegistration.data.name + if (closestSeriesIndex === serverRegistration.getGraphDataIndex()) { + serverName = `${serverName}` + } if (serverRegistration.isFavorite) { - serverName = ' ' + serverName + serverName = ` ${serverName}` } - if (typeof point === 'number') { - return serverName + ': ' + formatNumber(point) - } else { - return serverName + ': -' - } - }).join('
') - - text += '

' + formatTimestampSeconds(this._graphTimestamps[id]) + '' + return `${serverName}: ${formatNumber(point)}` + }).join('
') + `

${formatTimestampSeconds(this._graphTimestamps[idx])}` this._app.tooltip.set(pos.left, pos.top, 10, 10, text) } else { @@ -238,8 +261,8 @@ export class GraphDisplayManager { }, split: () => { const visibleGraphData = this.getVisibleGraphData() - const [, max, scale] = RelativeScale.scaleMatrix(visibleGraphData, tickCount, maxFactor) - const ticks = RelativeScale.generateTicks(0, max, scale) + const { scaledMax, scale } = RelativeScale.scaleMatrix(visibleGraphData, tickCount, maxFactor) + const ticks = RelativeScale.generateTicks(0, scaledMax, scale) return ticks } } @@ -249,18 +272,15 @@ export class GraphDisplayManager { auto: false, range: () => { const visibleGraphData = this.getVisibleGraphData() - const [, scaledMax] = RelativeScale.scaleMatrix(visibleGraphData, tickCount, maxFactor) - return [0, scaledMax] + const { scaledMin, scaledMax } = RelativeScale.scaleMatrix(visibleGraphData, tickCount, maxFactor) + return [scaledMin, scaledMax] } } }, legend: { show: false } - }, [ - this._graphTimestamps, - ...this._graphData - ], document.getElementById('big-graph')) + }, this.getGraphData(), document.getElementById('big-graph')) // Show the settings-toggle element document.getElementById('settings-toggle').style.display = 'inline-block' @@ -273,7 +293,7 @@ export class GraphDisplayManager { // Copy application state into the series data used by uPlot for (const serverRegistration of this._app.serverRegistry.getServerRegistrations()) { - this._plotInstance.series[serverRegistration.serverId + 1].show = serverRegistration.isVisible + this._plotInstance.series[serverRegistration.getGraphDataIndex()].show = serverRegistration.isVisible } this._plotInstance.redraw() diff --git a/assets/js/mojang.js b/assets/js/mojang.js index ac4b6a0..498dce9 100644 --- a/assets/js/mojang.js +++ b/assets/js/mojang.js @@ -13,8 +13,8 @@ export class MojangUpdater { updateServiceStatus (name, title) { // HACK: ensure mojang-status is added for alignment, replace existing class to swap status color - document.getElementById('mojang-status_' + name).setAttribute('class', MOJANG_STATUS_BASE_CLASS + ' mojang-status-' + title.toLowerCase()) - document.getElementById('mojang-status-text_' + name).innerText = title + document.getElementById(`mojang-status_${name}`).setAttribute('class', `${MOJANG_STATUS_BASE_CLASS} mojang-status-${title.toLowerCase()}`) + document.getElementById(`mojang-status-text_${name}`).innerText = title } reset () { diff --git a/assets/js/percbar.js b/assets/js/percbar.js index 596bdf2..f23339a 100644 --- a/assets/js/percbar.js +++ b/assets/js/percbar.js @@ -20,12 +20,15 @@ export class PercentageBar { // Update position/width // leftPadding is a sum of previous iterations width value - const div = document.getElementById('perc-bar-part_' + serverRegistration.serverId) || this.createPart(serverRegistration) + const div = document.getElementById(`perc-bar-part_${serverRegistration.serverId}`) || this.createPart(serverRegistration) + + const widthPixels = `${width}px` + const leftPaddingPixels = `${leftPadding}px` // Only redraw if needed - if (div.style.width !== width + 'px' || div.style.left !== leftPadding + 'px') { - div.style.width = width + 'px' - div.style.left = leftPadding + 'px' + if (div.style.width !== widthPixels || div.style.left !== leftPaddingPixels) { + div.style.width = widthPixels + div.style.left = leftPaddingPixels } leftPadding += width @@ -35,7 +38,7 @@ export class PercentageBar { createPart (serverRegistration) { const div = document.createElement('div') - div.id = 'perc-bar-part_' + serverRegistration.serverId + div.id = `perc-bar-part_${serverRegistration.serverId}` div.style.background = serverRegistration.data.color div.setAttribute('class', 'perc-bar-part') @@ -55,10 +58,10 @@ export class PercentageBar { const serverRegistration = this._app.serverRegistry.getServerRegistration(serverId) this._app.tooltip.set(event.target.offsetLeft, event.target.offsetTop, 10, this._parent.offsetTop + this._parent.offsetHeight + 10, - (typeof serverRegistration.rankIndex !== 'undefined' ? '#' + (serverRegistration.rankIndex + 1) + ' ' : '') + - serverRegistration.data.name + - '
' + formatNumber(serverRegistration.playerCount) + ' Players
' + - '' + formatPercent(serverRegistration.playerCount, this._app.getTotalPlayerCount()) + '') + `${typeof serverRegistration.rankIndex !== 'undefined' ? `#${serverRegistration.rankIndex + 1} ` : ''} + ${serverRegistration.data.name}
+ ${formatNumber(serverRegistration.playerCount)} Players
+ ${formatPercent(serverRegistration.playerCount, this._app.getTotalPlayerCount())}`) } handleMouseOut = () => { diff --git a/assets/js/scale.js b/assets/js/scale.js index a94ca17..01ecd7e 100644 --- a/assets/js/scale.js +++ b/assets/js/scale.js @@ -1,6 +1,6 @@ export class RelativeScale { static scale (data, tickCount, maxFactor) { - const [min, max] = RelativeScale.calculateBounds(data) + const { min, max } = RelativeScale.calculateBounds(data) let factor = 1 @@ -12,8 +12,12 @@ export class RelativeScale { const ticks = (scaledMax - scaledMin) / scale - if (ticks < tickCount + 1 || (typeof maxFactor === 'number' && factor === maxFactor)) { - return [scaledMin, scaledMax, scale] + if (ticks <= tickCount || (typeof maxFactor === 'number' && factor === maxFactor)) { + return { + scaledMin, + scaledMax, + scale + } } else { // Too many steps between min/max, increase factor and try again factor++ @@ -22,27 +26,9 @@ export class RelativeScale { } static scaleMatrix (data, tickCount, maxFactor) { - let max = Number.MIN_VALUE + const max = Math.max(...data.flat()) - for (const row of data) { - let testMax = Number.MIN_VALUE - - for (const point of row) { - if (point > testMax) { - testMax = point - } - } - - if (testMax > max) { - max = testMax - } - } - - if (max === Number.MIN_VALUE) { - max = 0 - } - - return RelativeScale.scale([0, max], tickCount, maxFactor) + return RelativeScale.scale([0, RelativeScale.isFiniteOrZero(max)], tickCount, maxFactor) } static generateTicks (min, max, step) { @@ -55,30 +41,22 @@ export class RelativeScale { static calculateBounds (data) { if (data.length === 0) { - return [0, 0] + return { + min: 0, + max: 0 + } } else { - let min = Number.MAX_VALUE - let max = Number.MIN_VALUE + const min = Math.min(...data) + const max = Math.max(...data) - for (const point of data) { - if (typeof point === 'number') { - if (point > max) { - max = point - } - if (point < min) { - min = point - } - } + return { + min: RelativeScale.isFiniteOrZero(min), + max: RelativeScale.isFiniteOrZero(max) } - - if (min === Number.MAX_VALUE) { - min = 0 - } - if (max === Number.MIN_VALUE) { - max = 0 - } - - return [min, max] } } + + static isFiniteOrZero (val) { + return Number.isFinite(val) ? val : 0 + } } diff --git a/assets/js/servers.js b/assets/js/servers.js index b79945e..f103f67 100644 --- a/assets/js/servers.js +++ b/assets/js/servers.js @@ -67,6 +67,10 @@ export class ServerRegistration { this._failedSequentialPings = 0 } + getGraphDataIndex () { + return this.serverId + 1 + } + addGraphPoints (points, timestampPoints) { this._graphData = [ timestampPoints.slice(), @@ -87,9 +91,7 @@ export class ServerRegistration { if (typeof playerCount !== 'number') { this._app.tooltip.hide() } else { - const text = formatNumber(playerCount) + ' Players
' + formatTimestampSeconds(this._graphData[0][id]) - - this._app.tooltip.set(pos.left, pos.top, 10, 10, text) + this._app.tooltip.set(pos.left, pos.top, 10, 10, `${formatNumber(playerCount)} Players
${formatTimestampSeconds(this._graphData[0][id])}`) } } else { this._app.tooltip.hide() @@ -116,7 +118,7 @@ export class ServerRegistration { scale: 'Players', stroke: '#E9E581', width: 2, - value: (_, raw) => formatNumber(raw) + ' Players', + value: (_, raw) => `${formatNumber(raw)} Players`, spanGaps: true, points: { show: false @@ -139,8 +141,8 @@ export class ServerRegistration { width: 1 }, split: () => { - const [min, max, scale] = RelativeScale.scale(this._graphData[1], tickCount) - const ticks = RelativeScale.generateTicks(min, max, scale) + const { scaledMin, scaledMax, scale } = RelativeScale.scale(this._graphData[1], tickCount) + const ticks = RelativeScale.generateTicks(scaledMin, scaledMax, scale) return ticks } } @@ -149,7 +151,7 @@ export class ServerRegistration { Players: { auto: false, range: () => { - const [scaledMin, scaledMax] = RelativeScale.scale(this._graphData[1], tickCount) + const { scaledMin, scaledMax } = RelativeScale.scale(this._graphData[1], tickCount) return [scaledMin, scaledMax] } } @@ -157,7 +159,7 @@ export class ServerRegistration { legend: { show: false } - }, this._graphData, document.getElementById('chart_' + this.serverId)) + }, this._graphData, document.getElementById(`chart_${this.serverId}`)) } handlePing (payload, timestamp) { @@ -193,15 +195,15 @@ export class ServerRegistration { updateServerRankIndex (rankIndex) { this.rankIndex = rankIndex - document.getElementById('ranking_' + this.serverId).innerText = '#' + (rankIndex + 1) + document.getElementById(`ranking_${this.serverId}`).innerText = `#${rankIndex + 1}` } _renderValue (prefix, handler) { - const labelElement = document.getElementById(prefix + '_' + this.serverId) + const labelElement = document.getElementById(`${prefix}_${this.serverId}`) labelElement.style.display = 'block' - const valueElement = document.getElementById(prefix + '-value_' + this.serverId) + const valueElement = document.getElementById(`${prefix}-value_${this.serverId}`) const targetElement = valueElement || labelElement if (targetElement) { @@ -214,7 +216,7 @@ export class ServerRegistration { } _hideValue (prefix) { - const element = document.getElementById(prefix + '_' + this.serverId) + const element = document.getElementById(`${prefix}_${this.serverId}`) element.style.display = 'none' } @@ -227,8 +229,8 @@ export class ServerRegistration { if (ping.recordData) { this._renderValue('record', (element) => { if (ping.recordData.timestamp > 0) { - element.innerText = formatNumber(ping.recordData.playerCount) + ' (' + formatDate(ping.recordData.timestamp) + ')' - element.title = 'At ' + formatDate(ping.recordData.timestamp) + ' ' + formatTimestampSeconds(ping.recordData.timestamp) + element.innerText = `${formatNumber(ping.recordData.playerCount)} (${formatDate(ping.recordData.timestamp)})` + element.title = `At ${formatDate(ping.recordData.timestamp)} ${formatTimestampSeconds(ping.recordData.timestamp)}` } else { element.innerText = formatNumber(ping.recordData.playerCount) } @@ -240,7 +242,7 @@ export class ServerRegistration { if (ping.graphPeakData) { this._renderValue('peak', (element) => { element.innerText = formatNumber(ping.graphPeakData.playerCount) - element.title = 'At ' + formatTimestampSeconds(ping.graphPeakData.timestamp) + element.title = `At ${formatTimestampSeconds(ping.graphPeakData.timestamp)}` }) this.lastPeakData = ping.graphPeakData @@ -262,7 +264,7 @@ export class ServerRegistration { // An updated favicon has been sent, update the src if (ping.favicon) { - const faviconElement = document.getElementById('favicon_' + this.serverId) + const faviconElement = document.getElementById(`favicon_${this.serverId}`) // Since favicons may be URLs, only update the attribute when it has changed // Otherwise the browser may send multiple requests to the same URL @@ -275,20 +277,20 @@ export class ServerRegistration { initServerStatus (latestPing) { const serverElement = document.createElement('div') - serverElement.id = 'container_' + this.serverId - serverElement.innerHTML = '
' + - '' + - '' + - '
' + - '
' + - '

' + this.data.name + '

' + - '' + - 'Players: ' + - '' + this._app.publicConfig.graphDurationLabel + ' Peak: -' + - 'Record: -' + - '' + - '
' + - '
' + serverElement.id = `container_${this.serverId}` + serverElement.innerHTML = `
+ + +
+
+

${this.data.name}

+ + Players: + ${this._app.publicConfig.graphDurationLabel} Peak: - + Record: - + +
+
` serverElement.setAttribute('class', 'server') @@ -297,8 +299,8 @@ export class ServerRegistration { updateHighlightedValue (selectedCategory) { ['player-count', 'peak', 'record'].forEach((category) => { - const labelElement = document.getElementById(category + '_' + this.serverId) - const valueElement = document.getElementById(category + '-value_' + this.serverId) + const labelElement = document.getElementById(`${category}_${this.serverId}`) + const valueElement = document.getElementById(`${category}-value_${this.serverId}`) if (selectedCategory && category === selectedCategory) { labelElement.setAttribute('class', 'server-highlighted-label') @@ -311,7 +313,7 @@ export class ServerRegistration { } initEventListeners () { - document.getElementById('favorite-toggle_' + this.serverId).addEventListener('click', () => { + document.getElementById(`favorite-toggle_${this.serverId}`).addEventListener('click', () => { this._app.favoritesManager.handleFavoriteButtonClick(this) }, false) } diff --git a/assets/js/socket.js b/assets/js/socket.js index 25cdd01..ece4177 100644 --- a/assets/js/socket.js +++ b/assets/js/socket.js @@ -15,7 +15,7 @@ export class SocketManager { webSocketProtocol = 'wss:' } - this._webSocket = new WebSocket(webSocketProtocol + '//' + location.host) + this._webSocket = new WebSocket(`${webSocketProtocol}//${location.host}`) // The backend will automatically push data once connected this._webSocket.onopen = () => { @@ -119,10 +119,10 @@ export class SocketManager { .forEach(serverName => { const serverRegistration = this._app.serverRegistry.getServerRegistration(serverName) - controlsHTML += '' + - '' + - ' ' + serverName + - '' + controlsHTML += ` + + ${serverName} + ` // Occasionally break table rows using a magic number if (++lastRowCounter % 6 === 0) { @@ -131,10 +131,7 @@ export class SocketManager { }) // Apply generated HTML and show controls - document.getElementById('big-graph-checkboxes').innerHTML = '' + - controlsHTML + - '
' - + document.getElementById('big-graph-checkboxes').innerHTML = `${controlsHTML}
` document.getElementById('big-graph-controls').style.display = 'block' // Bind click event for updating graph data @@ -171,7 +168,7 @@ export class SocketManager { this.createWebSocket() } else if (this._reconnectDelaySeconds > 0) { // Update displayed text - this._app.caption.set('Reconnecting in ' + this._reconnectDelaySeconds + 's...') + this._app.caption.set(`Reconnecting in ${this._reconnectDelaySeconds}s...`) } }, 1000) } diff --git a/assets/js/sort.js b/assets/js/sort.js index 7c9209c..34e1533 100644 --- a/assets/js/sort.js +++ b/assets/js/sort.js @@ -6,7 +6,7 @@ const SORT_OPTIONS = [ }, { getName: (app) => { - return app.publicConfig.graphDurationLabel + ' Peak' + return `${app.publicConfig.graphDurationLabel} Peak` }, sortFunc: (a, b) => { if (!a.lastPeakData && !b.lastPeakData) { @@ -188,7 +188,7 @@ export class SortController { // Update the DOM structure sortedServers.forEach(function (serverRegistration) { const parentElement = document.getElementById('server-list') - const serverElement = document.getElementById('container_' + serverRegistration.serverId) + const serverElement = document.getElementById(`container_${serverRegistration.serverId}`) parentElement.appendChild(serverElement) diff --git a/assets/js/util.js b/assets/js/util.js index 474cc76..398bcd1 100644 --- a/assets/js/util.js +++ b/assets/js/util.js @@ -19,8 +19,8 @@ export class Tooltip { offsetX *= -1 } - this._div.style.top = (y + offsetY) + 'px' - this._div.style.left = (x + offsetX) + 'px' + this._div.style.top = `${y + offsetY}px` + this._div.style.left = `${x + offsetX}px` } hide = () => { @@ -49,7 +49,7 @@ const MINECRAFT_DEFAULT_PORTS = [25565, 19132] export function formatMinecraftServerAddress (ip, port) { if (port && !MINECRAFT_DEFAULT_PORTS.includes(port)) { - return ip + ':' + port + return `${ip}:${port}` } return ip } @@ -93,8 +93,7 @@ export function formatMinecraftVersions (versions, knownVersions) { return startVersion } else { const endVersion = knownVersions[versionGroup[versionGroup.length - 1]] - - return startVersion + '-' + endVersion + return `${startVersion}-${endVersion}` } }).join(', ') } @@ -113,9 +112,13 @@ export function formatDate (secs) { export function formatPercent (x, over) { const val = Math.round((x / over) * 100 * 10) / 10 - return val + '%' + return `${val}%` } export function formatNumber (x) { - return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + if (typeof x !== 'number') { + return '-' + } else { + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + } }