summaryrefslogtreecommitdiff
path: root/src/SMAPI.Web/Views/LogParser/Index.cshtml
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2022-05-01 18:16:09 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2022-05-01 18:16:09 -0400
commitc8ad50dad1d706a1901798f9396f6becfea36c0e (patch)
tree28bd818a5db39ec5ece1bd141a28de955950463b /src/SMAPI.Web/Views/LogParser/Index.cshtml
parent451b70953ff4c0b1b27ae0de203ad99379b45b2a (diff)
parentf78093bdb58d477b400cde3f19b70ffd6ddf833d (diff)
downloadSMAPI-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.cshtml265
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>
}