diff options
-rw-r--r-- | src/SMAPI.Web/Views/LogParser/Index.cshtml | 13 | ||||
-rw-r--r-- | src/SMAPI.Web/wwwroot/Content/css/main.css | 32 | ||||
-rw-r--r-- | src/SMAPI.Web/wwwroot/Content/js/log-parser.js | 235 |
3 files changed, 124 insertions, 156 deletions
diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml index 7aa0fd6b..d95499b7 100644 --- a/src/SMAPI.Web/Views/LogParser/Index.cshtml +++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml @@ -94,6 +94,19 @@ </script> } +@* quick navigation links *@ +@if (log != null) +{ + <aside id="quickNav"> + <h4>Scroll To...</h4> + <ul> + <li><a href="#content">Top</a></li> + <li><a href="#filterHolder">Log Start</a></li> + <li><a href="#footer">Bottom</a></li> + </ul> + </aside> +} + @* upload result banner *@ @if (Model.UploadError != null) { diff --git a/src/SMAPI.Web/wwwroot/Content/css/main.css b/src/SMAPI.Web/wwwroot/Content/css/main.css index dcc7a798..52b304d0 100644 --- a/src/SMAPI.Web/wwwroot/Content/css/main.css +++ b/src/SMAPI.Web/wwwroot/Content/css/main.css @@ -72,6 +72,7 @@ a { color: #666; } +#quickNav h4, #sidebar h4 { margin: 1.5em 0 0.2em 0; width: 10em; @@ -80,11 +81,13 @@ a { font-weight: normal; } +#quickNav a, #sidebar a { color: #77B; border: 0; } +#quickNav ul, #quickNav li, #sidebar ul, #sidebar li { margin: 0; padding: 0; @@ -93,10 +96,29 @@ a { color: #888; } +#quickNav li, #sidebar li { margin-left: 1em; } +/* quick navigation */ + +#quickNav { + position: fixed; + left: 8px; + bottom: 3em; + width: 12em; + color: #666; +} + +@media (max-height: 400px) { + #quickNav { + position: unset; + width: auto; + } +} + + /* footer */ #footer { margin: 1em; @@ -111,11 +133,16 @@ a { /* mobile fixes */ @media (min-width: 1020px) and (max-width: 1199px) { + #quickNav, #sidebar { width: 7em; background: none; } + #quickNav h4 { + width: unset; + } + #content-column { left: 7em; } @@ -138,4 +165,9 @@ a { top: inherit; left: inherit; } + + #quickNav { + position: unset; + width: auto; + } } diff --git a/src/SMAPI.Web/wwwroot/Content/js/log-parser.js b/src/SMAPI.Web/wwwroot/Content/js/log-parser.js index 8538423f..37c57082 100644 --- a/src/SMAPI.Web/wwwroot/Content/js/log-parser.js +++ b/src/SMAPI.Web/wwwroot/Content/js/log-parser.js @@ -97,120 +97,23 @@ smapi.logParser = function (state) { }, /** - * Convert an array of boolean values into a bitmap. - * @param {Boolean[]} value An array of boolean values - * @returns {BigInt} + * Try parsing the value as an integer, in base 10. Return the number + * if it's valid, or return the default value otherwise. + * @param {String} value The value to parse. + * @param {Number} defaultValue The value to return if parsing fails. + * @param {Function} critera An optional criteria to check the number with. */ - toBitmap(value) { - let result = BigInt(0); - if (!Array.isArray(value)) - return value ? BigInt(1) : BigInt(0); - - for (let i = 0; i < value.length; i++) { - if (value[i]) - result += BigInt(2) ** BigInt(value.length - i - 1); + tryNumber(value, defaultValue, critera = null) { + try { + value = parseInt(value, 10); + } catch { + return defaultValue; } - return result; - }, - - /** - * Convert a bitmap into an array of boolean values. - * @param {BigInt} value The bitmap - * @param {Number} length The expected length of the result - * @returns {Boolean[]} - */ - fromBitmap(value, length = -1) { - if (typeof value != "bigint") - value = ""; - else - value = value.toString(2); - - const result = []; - while (length > value.length) { - result.push(false); - length--; - } - - for (let i = 0; i < value.length; i++) { - result.push(value[i] === "1" ? true : false); - } - - return result; - }, - - /** - * Convert a base-64 string to a BigInt. - * @param {string} value - * @returns {BigInt} - */ - b64ToBigInt(value) { - const bin = atob(value); - const hex = []; - - for (let i = 0; i < bin.length; i++) { - let h = bin.charCodeAt(i).toString(16); - if (h.length % 2) h = `0${h}`; - hex.push(h); - } - - return BigInt(`0x${hex.join('')}`); - }, - - /** - * Convert a BigInt to a base-64 string. - * @param {BigInt} value - * @returns {string} - */ - bigIntTo64(value) { - let hex = value.toString(16); - if (hex.length % 2) hex = `0${hex}`; - - const result = []; - for (let i = 0; i < hex.length; i += 2) { - const val = parseInt(hex.slice(i, i + 2), 16); - result.push(String.fromCharCode(val)); - } - - return btoa(result.join('')); - }, - - /** - * Make a base-64 string URL safe. - * @param {string} value - * @returns {string} - */ - b64ToUrl(value) { - return value.replace(/\//g, '_').replace(/=/g, '-').replace(/\+/g, '.'); - }, - - /** - * Convert a URL safe base-64 string back to normal. - * @param {string} value - * @returns {string} - */ - urlTob64(value) { - return value.replace(/_/g, '/').replace(/-/g, '=').replace(/\./g, '+'); - }, + if (isNaN(value) || !isFinite(value) || (critera && !critera(value))) + return defaultValue; - /** - * Convert an array of booleans to a BigInt bitmap, then convert that - * to a base-64 string, then make it URL safe. - * @param {Boolean[]} value - * @returns {string} - */ - toUrlBitmap(value) { - return helpers.b64ToUrl(helpers.bigIntTo64(helpers.toBitmap(value))); - }, - - /** - * Convert a URL safe base-64 string to a normal base-64 string, convert - * that to a BigInt, and then parse a bitmap from the BigInt. - * @param {string} value - * @param {Number} length The expected length of the bitmap. - */ - fromUrlBitmap(value, length = -1) { - return helpers.fromBitmap(helpers.b64ToBigInt(helpers.urlTob64(value)), length); + return value; }, /** @@ -433,12 +336,18 @@ smapi.logParser = function (state) { "div", { class: "stats" }, [ - "showing ", - createElement("strong", helpers.formatNumber(props.start + 1)), - " to ", - createElement("strong", helpers.formatNumber(props.end)), - " of ", - createElement("strong", helpers.formatNumber(props.filtered)), + createElement('abbr', { + attrs: { + title: "These numbers may be inaccurate when using filtering with sections collapsed." + } + }, [ + "showing ", + createElement("strong", helpers.formatNumber(props.start + 1)), + " to ", + createElement("strong", helpers.formatNumber(props.end)), + " of ", + createElement("strong", helpers.formatNumber(props.filtered)) + ]), " (total: ", createElement("strong", helpers.formatNumber(props.total)), ")" @@ -788,64 +697,66 @@ smapi.logParser = function (state) { loadFromUrl: function () { const params = new URL(location).searchParams; if (params.has("PerPage")) { - const perPage = parseInt(params.get("PerPage")); - if (!isNaN(perPage) && isFinite(perPage) && perPage > 0) - state.perPage = perPage; + state.perPage = helpers.tryNumber(params.get("PerPage"), 1000, n => n > 0); + } else { + state.perPage = 1000; } if (params.has("Page")) { - const page = parseInt(params.get("Page")); - if (!isNaN(page) && isFinite(page) && page > 0) - this.page = page; + this.page = helpers.tryNumber(params.get("Page"), 1, n => n > 0); + } else { + this.page = 1; } - let updateFilter = false; - - if (params.has("Filter")) { + if (params.has("Filter")) state.filterText = params.get("Filter"); - updateFilter = true; - } + else + state.filterText = ""; if (params.has("FilterMode")) { - const values = helpers.fromUrlBitmap(params.get("FilterMode"), 3); - state.useRegex = values[0]; - state.useInsensitive = values[1]; - state.useWord = values[2]; - updateFilter = true; + const values = params.get("FilterMode").split("~"); + state.useRegex = values.includes('Regex'); + state.useInsensitive = !values.includes('Sensitive'); + state.useWord = values.includes('Word'); + } else { + state.useRegex = false; + state.useInsensitive = true; + state.useWord = false; } if (params.has("Mods")) { - const keys = Object.keys(this.showMods); - const value = params.get("Mods"); - const values = value === "all" ? true : value === "none" ? false : helpers.fromUrlBitmap(value, keys.length); - - for (let i = 0; i < keys.length; i++) { - this.showMods[keys[i]] = Array.isArray(values) ? values[i] : values; - } + const value = params.get("Mods").split("~"); + for (const key of Object.keys(this.showMods)) + this.showMods[key] = value.includes(key); - updateModFilters(); + } else { + for (const key of Object.keys(this.showMods)) + this.showMods[key] = state.defaultMods[key]; } if (params.has("Levels")) { - const keys = Object.keys(this.showLevels); - const values = helpers.fromUrlBitmap(params.get("Levels"), keys.length); + const values = params.get("Levels").split("~"); + for (const key of Object.keys(this.showLevels)) + this.showLevels[key] = values.includes(key); - for (let i = 0; i < keys.length; i++) { - this.showLevels[keys[i]] = values[i]; - } + } else { + const keys = Object.keys(this.showLevels); + for (const key of Object.keys(this.showLevels)) + this.showLevels[key] = state.defaultLevels[key]; } if (params.has("Sections")) { - const keys = Object.keys(this.showSections); - const values = helpers.fromUrlBitmap(params.get("Levels"), keys.length); + const values = params.get("Sections").split("~"); + for (const key of Object.keys(this.showSections)) + this.showSections[key] = values.includes(key); - for (let i = 0; i < keys.length; i++) { - this.showSections[keys[i]] = values[i]; - } + } else { + for (const key of Object.keys(this.showSections)) + this.showSections[key] = state.defaultSections[key]; } - if (updateFilter) - this.updateFilterText(); + updateModFilters(); + this.updateFilterText(); }, // Whenever the page state changed, replace the current page URL. Using @@ -858,23 +769,35 @@ smapi.logParser = function (state) { url.searchParams.set("PerPage", state.perPage); if (!helpers.shallowEquals(this.showMods, state.defaultMods)) - url.searchParams.set("Mods", stats.modsHidden == 0 ? "all" : stats.modsShown == 0 ? "none" : helpers.toUrlBitmap(Object.values(this.showMods))); + url.searchParams.set("Mods", Object.entries(this.showMods).filter(x => x[1]).map(x => x[0]).join("~")); else url.searchParams.delete("Mods"); if (!helpers.shallowEquals(this.showLevels, state.defaultLevels)) - url.searchParams.set("Levels", helpers.toUrlBitmap(Object.values(this.showLevels))); + url.searchParams.set("Levels", Object.entries(this.showLevels).filter(x => x[1]).map(x => x[0]).join("~")); else url.searchParams.delete("Levels"); if (!helpers.shallowEquals(this.showSections, state.defaultSections)) - url.searchParams.set("Sections", helpers.toUrlBitmap(Object.values(this.showSections))); + url.searchParams.set("Sections", Object.entries(this.showSections).filter(x => x[1]).map(x => x[0]).join("~")); else url.searchParams.delete("Sections"); if (state.filterText && state.filterText.length) { url.searchParams.set("Filter", state.filterText); - url.searchParams.set("FilterMode", helpers.toUrlBitmap([state.useRegex, state.useInsensitive, state.useWord])); + const modes = []; + if (state.useRegex) + modes.push("Regex"); + if (!state.useInsensitive) + modes.push("Sensitive"); + if (state.useWord) + modes.push("Word"); + + if (modes.length) + url.searchParams.set("FilterMode", modes.join("~")); + else + url.searchParams.delete("FilterMode"); + } else { url.searchParams.delete("Filter"); url.searchParams.delete("FilterMode"); |