diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2022-05-01 18:16:09 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2022-05-01 18:16:09 -0400 |
commit | c8ad50dad1d706a1901798f9396f6becfea36c0e (patch) | |
tree | 28bd818a5db39ec5ece1bd141a28de955950463b /src/SMAPI.Web/Views/LogParser/Index.cshtml | |
parent | 451b70953ff4c0b1b27ae0de203ad99379b45b2a (diff) | |
parent | f78093bdb58d477b400cde3f19b70ffd6ddf833d (diff) | |
download | SMAPI-c8ad50dad1d706a1901798f9396f6becfea36c0e.tar.gz SMAPI-c8ad50dad1d706a1901798f9396f6becfea36c0e.tar.bz2 SMAPI-c8ad50dad1d706a1901798f9396f6becfea36c0e.zip |
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI.Web/Views/LogParser/Index.cshtml')
-rw-r--r-- | src/SMAPI.Web/Views/LogParser/Index.cshtml | 265 |
1 files changed, 177 insertions, 88 deletions
diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml index 91fc3535..5e55906d 100644 --- a/src/SMAPI.Web/Views/LogParser/Index.cshtml +++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml @@ -7,15 +7,25 @@ @{ ViewData["Title"] = "SMAPI log parser"; + + ParsedLog? log = Model!.ParsedLog; + IDictionary<string, LogModInfo[]> contentPacks = Model.GetContentPacksByMod(); IDictionary<string, bool> defaultFilters = Enum - .GetValues(typeof(LogLevel)) - .Cast<LogLevel>() + .GetValues<LogLevel>() .ToDictionary(level => level.ToString().ToLower(), level => level != LogLevel.Trace); - string curPageUrl = this.Url.PlainAction("Index", "LogParser", new { id = Model.PasteID }, absoluteUrl: true); + IDictionary<int, string> logLevels = Enum + .GetValues<LogLevel>() + .ToDictionary(level => (int)level, level => level.ToString().ToLower()); + + IDictionary<int, string> logSections = Enum + .GetValues<LogSection>() + .ToDictionary(section => (int)section, section => section.ToString()); - ISet<int> screenIds = new HashSet<int>(Model.ParsedLog?.Messages?.Select(p => p.ScreenId) ?? new int[0]); + string curPageUrl = this.Url.PlainAction("Index", "LogParser", new { id = Model.PasteID }, absoluteUrl: true)!; + + ISet<int> screenIds = new HashSet<int>(log?.Messages.Select(p => p.ScreenId) ?? Array.Empty<int>()); } @section Head { @@ -24,31 +34,81 @@ <meta name="robots" content="noindex" /> } <link rel="stylesheet" href="~/Content/css/file-upload.css" /> - <link rel="stylesheet" href="~/Content/css/log-parser.css" /> + <link rel="stylesheet" href="~/Content/css/log-parser.css?r=20220409" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tabbyjs@12.0.3/dist/css/tabby-ui-vertical.min.css" /> <script src="https://cdn.jsdelivr.net/npm/tabbyjs@12.0.3" crossorigin="anonymous"></script> - <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11" crossorigin="anonymous"></script> + <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1" crossorigin="anonymous"></script> <script src="~/Content/js/file-upload.js"></script> - <script src="~/Content/js/log-parser.js"></script> + <script src="~/Content/js/log-parser.js?r=20220409"></script> + + <script id="serializedData" type="application/json"> + @if (!Model.ShowRaw) + { + <text> + { + "messages": @this.ForJson(log?.Messages), + "sections": @this.ForJson(logSections), + "logLevels": @this.ForJson(logLevels), + "modSlugs": @this.ForJson(log?.Mods.DistinctBy(p => p.Name).Select(p => new {p.Name, Slug = Model.GetSlug(p.Name)}).Where(p => p.Name != p.Slug).ToDictionary(p => p.Name, p => p.Slug)), + "screenIds": @this.ForJson(screenIds) + } + </text> + } + else + { + <text> + { + "messages": [], + "sections": {}, + "logLevels": {}, + "modSlugs": {}, + "screenIds": [] + } + </text> + } + </script> + <script> $(function() { - smapi.logParser({ - logStarted: new Date(@this.ForJson(Model.ParsedLog?.Timestamp)), - showPopup: @this.ForJson(Model.ParsedLog == null), - showMods: @this.ForJson(Model.ParsedLog?.Mods?.Select(p => Model.GetSlug(p.Name)).Distinct().ToDictionary(slug => slug, slug => true)), - showSections: @this.ForJson(Enum.GetNames(typeof(LogSection)).ToDictionary(section => section, section => false)), - showLevels: @this.ForJson(defaultFilters), - enableFilters: @this.ForJson(!Model.ShowRaw), - screenIds: @this.ForJson(screenIds) - }, '@this.Url.PlainAction("Index", "LogParser", values: null)'); + smapi.logParser( + { + logStarted: new Date(@this.ForJson(log?.Timestamp)), + dataElement: "script#serializedData", + showPopup: @this.ForJson(log == null), + showMods: @this.ForJson(log?.Mods.Where(p => p.Loaded && !p.IsContentPack).Select(p => Model.GetSlug(p.Name)).Distinct().ToDictionary(slug => slug, _ => true)), + showSections: @this.ForJson(Enum.GetNames(typeof(LogSection)).ToDictionary(section => section, _ => false)), + showLevels: @this.ForJson(defaultFilters), + enableFilters: @this.ForJson(!Model.ShowRaw) + } + ); - new Tabby('[data-tabs]'); + @if (log == null) + { + <text> + new Tabby("[data-tabs]"); + </text> + } }); </script> } +@* quick navigation links *@ +@section SidebarExtra { + @if (log != null) + { + <nav 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> + </nav> + } +} + @* upload result banner *@ @if (Model.UploadError != null) { @@ -67,7 +127,7 @@ else if (Model.ParseError != null) <small v-pre>Error details: @Model.ParseError</small> </div> } -else if (Model.ParsedLog?.IsValid == true) +else if (log?.IsValid == true) { <div class="banner success" v-pre> <strong>Share this link to let someone else see the log:</strong> <code>@curPageUrl</code><br /> @@ -92,7 +152,7 @@ else if (Model.ParsedLog?.IsValid == true) } @* upload new log *@ -@if (Model.ParsedLog == null) +@if (log == null) { <h2>Where do I find my SMAPI log?</h2> <div id="os-instructions"> @@ -157,7 +217,7 @@ else if (Model.ParsedLog?.IsValid == true) </div> <h2>How do I share my log?</h2> - <form action="@this.Url.PlainAction("PostAsync", "LogParser")" method="post"> + <form action="@this.Url.PlainAction("Post", "LogParser")" method="post"> <input id="inputFile" type="file" /> <ol> <li> @@ -174,10 +234,10 @@ else if (Model.ParsedLog?.IsValid == true) } @* parsed log *@ -@if (Model.ParsedLog?.IsValid == true) +@if (log?.IsValid == true) { <div id="output"> - @if (Model.ParsedLog.Mods.Any(mod => mod.HasUpdate)) + @if (log.Mods.Any(mod => mod.HasUpdate)) { <h2>Suggested fixes</h2> <ul id="fix-list"> @@ -185,12 +245,12 @@ else if (Model.ParsedLog?.IsValid == true) Consider updating these mods to fix problems: <table id="updates" class="table"> - @foreach (LogModInfo mod in Model.ParsedLog.Mods.Where(mod => (mod.HasUpdate && mod.ContentPackFor == null) || (contentPacks != null && contentPacks.TryGetValue(mod.Name, out LogModInfo[] contentPackList) && contentPackList.Any(pack => pack.HasUpdate)))) + @foreach (LogModInfo mod in log.Mods.Where(mod => (mod.HasUpdate && !mod.IsContentPack) || (contentPacks.TryGetValue(mod.Name, out LogModInfo[]? contentPackList) && contentPackList.Any(pack => pack.HasUpdate)))) { <tr class="mod-entry"> <td> <strong class=@(!mod.HasUpdate ? "hidden" : "")>@mod.Name</strong> - @if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out LogModInfo[] contentPackList)) + @if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out LogModInfo[]? contentPackList)) { <div class="content-packs"> @foreach (LogModInfo contentPack in contentPackList.Where(pack => pack.HasUpdate)) @@ -204,7 +264,7 @@ else if (Model.ParsedLog?.IsValid == true) @if (mod.HasUpdate) { <a href="@mod.UpdateLink" target="_blank"> - @(mod.Version == null ? @mod.UpdateVersion : $"{mod.Version} → {mod.UpdateVersion}") + @(mod.Version == null ? mod.UpdateVersion : $"{mod.Version} → {mod.UpdateVersion}") </a> } else @@ -230,23 +290,33 @@ else if (Model.ParsedLog?.IsValid == true) } <h2>Log info</h2> - <table id="metadata" class="table"> + <table + id="metadata" + class="table" + data-code-mods="@log.Mods.Count(p => !p.IsContentPack)" + data-content-packs="@log.Mods.Count(p => p.IsContentPack)" + data-os="@log.OperatingSystem" + data-game-version="@log.GameVersion" + data-game-path="@log.GamePath" + data-smapi-version="@log.ApiVersion" + data-log-started="@log.Timestamp.UtcDateTime.ToString("O")" + > <caption>Game info:</caption> <tr> <th>Stardew Valley:</th> - <td v-pre>@Model.ParsedLog.GameVersion on @Model.ParsedLog.OperatingSystem</td> + <td v-pre>@log.GameVersion on @log.OperatingSystem</td> </tr> <tr> <th>SMAPI:</th> - <td v-pre>@Model.ParsedLog.ApiVersion</td> + <td v-pre>@log.ApiVersion</td> </tr> <tr> <th>Folder:</th> - <td v-pre>@Model.ParsedLog.GamePath</td> + <td v-pre>@log.GamePath</td> </tr> <tr> <th>Log started:</th> - <td>@Model.ParsedLog.Timestamp.UtcDateTime.ToString("yyyy-MM-dd HH:mm") UTC ({{localTimeStarted}} your time)</td> + <td>@log.Timestamp.UtcDateTime.ToString("yyyy-MM-dd HH:mm") UTC ({{localTimeStarted}} your time)</td> </tr> </table> <br /> @@ -258,29 +328,34 @@ else if (Model.ParsedLog?.IsValid == true) <span class="notice txt"><i>click any mod to filter</i></span> <span class="notice btn txt" v-on:click="showAllMods" v-bind:class="{ invisible: !anyModsHidden }">show all</span> <span class="notice btn txt" v-on:click="hideAllMods" v-bind:class="{ invisible: !anyModsShown || !anyModsHidden }">hide all</span> + <span class="notice btn txt" v-on:click="toggleContentPacks">toggle content packs in list</span> } </caption> - @foreach (var mod in Model.ParsedLog.Mods.Where(p => p.Loaded && p.ContentPackFor == null)) + @foreach (var mod in log.Mods.Where(p => p.Loaded && !p.IsContentPack)) { + if (contentPacks == null || !contentPacks.TryGetValue(mod.Name, out LogModInfo[]? contentPackList)) + contentPackList = null; + <tr v-on:click="toggleMod('@Model.GetSlug(mod.Name)')" class="mod-entry" v-bind:class="{ hidden: !showMods['@Model.GetSlug(mod.Name)'] }"> <td><input type="checkbox" v-bind:checked="showMods['@Model.GetSlug(mod.Name)']" v-bind:class="{ invisible: !anyModsHidden }" /></td> - <td v-pre> - <strong>@mod.Name</strong> @mod.Version - @if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out LogModInfo[] contentPackList)) + <td> + <strong v-pre>@mod.Name</strong> @mod.Version + @if (contentPackList != null) { - <div class="content-packs"> + <div v-if="!hideContentPacks" class="content-packs"> @foreach (var contentPack in contentPackList) { <text>+ @contentPack.Name @contentPack.Version</text><br /> } </div> + <span v-else class="content-packs-collapsed"> (+ @contentPackList.Length content packs)</span> } </td> - <td v-pre> + <td> @mod.Author - @if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out contentPackList)) + @if (contentPackList != null) { - <div class="content-packs"> + <div v-if="!hideContentPacks" class="content-packs"> @foreach (var contentPack in contentPackList) { <text>+ @contentPack.Author</text><br /> @@ -306,61 +381,75 @@ else if (Model.ParsedLog?.IsValid == true) @if (!Model.ShowRaw) { + <div id="filterHolder"></div> <div id="filters"> - Filter messages: - <span v-bind:class="{ active: showLevels['trace'] }" v-on:click="toggleLevel('trace')">TRACE</span> | - <span v-bind:class="{ active: showLevels['debug'] }" v-on:click="toggleLevel('debug')">DEBUG</span> | - <span v-bind:class="{ active: showLevels['info'] }" v-on:click="toggleLevel('info')">INFO</span> | - <span v-bind:class="{ active: showLevels['alert'] }" v-on:click="toggleLevel('alert')">ALERT</span> | - <span v-bind:class="{ active: showLevels['warn'] }" v-on:click="toggleLevel('warn')">WARN</span> | - <span v-bind:class="{ active: showLevels['error'] }" v-on:click="toggleLevel('error')">ERROR</span> + <div class="toggles"> + <div> + Filter messages: + </div> + <div> + <span role="button" v-bind:class="{ active: showLevels['trace'] }" v-on:click="toggleLevel('trace')">TRACE</span> | + <span role="button" v-bind:class="{ active: showLevels['debug'] }" v-on:click="toggleLevel('debug')">DEBUG</span> | + <span role="button" v-bind:class="{ active: showLevels['info'] }" v-on:click="toggleLevel('info')">INFO</span> | + <span role="button" v-bind:class="{ active: showLevels['alert'] }" v-on:click="toggleLevel('alert')">ALERT</span> | + <span role="button" v-bind:class="{ active: showLevels['warn'] }" v-on:click="toggleLevel('warn')">WARN</span> | + <span role="button" v-bind:class="{ active: showLevels['error'] }" v-on:click="toggleLevel('error')">ERROR</span> + <div class="filter-text"> + <input + type="text" + v-bind:class="{ active: !!filterText }" + v-model="filterText" + v-on:input="updateFilterText" + placeholder="search to filter log..." + /> + <span role="button" v-bind:class="{ active: filterUseRegex }" v-on:click="toggleFilterUseRegex" title="Use regular expression syntax.">.*</span> + <span role="button" v-bind:class="{ active: !filterInsensitive }" v-on:click="toggleFilterInsensitive" title="Match exact capitalization only.">aA</span> + <span role="button" v-bind:class="{ active: filterUseWord, 'whole-word': true }" v-on:click="toggleFilterWord" title="Match whole word only."><i>“ ”</i></span> + <span role="button" v-bind:class="{ active: shouldHighlight }" v-on:click="toggleHighlight" title="Highlight matches in the log text.">HL</span> + <div + v-if="filterError" + class="filter-error" + > + {{ filterError }} + </div> + </div> + <filter-stats + v-bind:start="start" + v-bind:end="end" + v-bind:pages="totalPages" + v-bind:filtered="filteredMessages.total" + v-bind:total="totalMessages" + /> + </div> + </div> + <pager + v-bind:page="page" + v-bind:pages="totalPages" + v-bind:prevPage="prevPage" + v-bind:nextPage="nextPage" + /> </div> - <table id="log"> - @foreach (var message in Model.ParsedLog.Messages) - { - string levelStr = message.Level.ToString().ToLower(); - string sectionStartClass = message.IsStartOfSection ? "section-start" : null; - string sectionFilter = message.Section != null && !message.IsStartOfSection ? $"&& sectionsAllow('{message.Section}')" : null; // filter the message by section if applicable + <noscript> + <div> + This website uses JavaScript to display a filterable table. To view this log, please enable JavaScript or <a href="@this.Url.PlainAction("Index", "LogParser", new { id = Model.PasteID, format = LogViewFormat.RawView })">view the raw log</a>. + </div> + <br/> + </noscript> - <tr class="mod @levelStr @sectionStartClass" - @if (message.IsStartOfSection) { <text> v-on:click="toggleSection('@message.Section')" </text> } - v-show="filtersAllow('@Model.GetSlug(message.Mod)', '@levelStr') @sectionFilter"> - <td v-pre>@message.Time</td> - @if (screenIds.Count > 1) - { - <td v-pre>screen_@message.ScreenId</td> - } - <td v-pre>@message.Level.ToString().ToUpper()</td> - <td v-pre data-title="@message.Mod">@message.Mod</td> - <td> - <span v-pre class="log-message-text">@message.Text</span> - @if (message.IsStartOfSection) - { - <span class="section-toggle-message"> - <template v-if="sectionsAllow('@message.Section')"> - This section is shown. Click here to hide it. - </template> - <template v-else> - This section is hidden. Click here to show it. - </template> - </span> - } - </td> - </tr> - if (message.Repeated > 0) - { - <tr class="@levelStr mod mod-repeat" v-show="filtersAllow('@Model.GetSlug(message.Mod)', '@levelStr') @sectionFilter"> - <td colspan="4"></td> - <td v-pre><i>repeats [@message.Repeated] times.</i></td> - </tr> - } - } - </table> + <log-table> + <log-line + v-for="msg in visibleMessages" + v-bind:key="msg.id" + v-bind:showScreenId="showScreenId" + v-bind:message="msg" + v-bind:highlight="shouldHighlight" + /> + </log-table> } else { - <pre v-pre>@Model.ParsedLog.RawText</pre> + <pre v-pre>@log.RawText</pre> } <small> @@ -377,8 +466,8 @@ else if (Model.ParsedLog?.IsValid == true) </small> </div> } -else if (Model.ParsedLog?.IsValid == false) +else if (log?.IsValid == false) { <h3>Raw log</h3> - <pre v-pre>@Model.ParsedLog.RawText</pre> + <pre v-pre>@log.RawText</pre> } |