@{ ViewData["Title"] = "SMAPI log parser"; IDictionary<string, LogModInfo[]> contentPacks = Model.ParsedLog?.Mods ?.GroupBy(mod => mod.ContentPackFor) .Where(group => group.Key != null) .ToDictionary(group => group.Key, group => group.ToArray()); Regex slugInvalidCharPattern = new Regex("[^a-z0-9]", RegexOptions.Compiled | RegexOptions.IgnoreCase); string GetSlug(string modName) { return slugInvalidCharPattern.Replace(modName, ""); } } @using System.Text.RegularExpressions @using Newtonsoft.Json @using StardewModdingAPI.Web.Framework.LogParsing.Models @model StardewModdingAPI.Web.ViewModels.LogParserModel @section Head { <link rel="stylesheet" href="~/Content/css/log-parser.css?r=20180225" /> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" crossorigin="anonymous"></script> <script src="~/Content/js/log-parser.js?r=20180225"></script> <script> $(function() { smapi.logParser({ logStarted: new Date(@Json.Serialize(Model.ParsedLog?.Timestamp)), showPopup: @Json.Serialize(Model.ParsedLog == null), showMods: @Json.Serialize(Model.ParsedLog?.Mods?.Select(p => GetSlug(p.Name)).Distinct().ToDictionary(slug => slug, slug => true), new JsonSerializerSettings { Formatting = Formatting.None }), showLevels: { trace: false, debug: false, info: true, alert: true, warn: true, error: true } }, '@Model.SectionUrl'); }); </script> } @********* ** Intro *********@ <p id="blurb">This page lets you upload, view, and share a SMAPI log to help troubleshoot mod issues.</p> @if (Model.ParsedLog?.IsValid == true) { <div class="banner success" v-pre> <strong>The log was uploaded successfully!</strong><br/> Share this URL when asking for help: <code>@(new Uri(new Uri(Model.SectionUrl), Model.PasteID))</code><br/> (Or <a id="upload-button" href="#">upload a new log</a>.) </div> } else if (Model.ParsedLog?.IsValid == false) { <div class="banner error" v-pre> <strong>Oops, couldn't parse that file. (Make sure you upload the log file, not the console text.)</strong><br /> Share this URL when asking for help: <code>@(new Uri(new Uri(Model.SectionUrl), Model.PasteID))</code><br /> (Or <a id="upload-button" href="#">upload a new log</a>.)<br /> <br /> <small v-pre>Error details: @Model.ParsedLog.Error</small> </div> } else { <input type="button" id="upload-button" value="Share a new log" /> } @********* ** Parsed log *********@ @if (Model.ParsedLog?.IsValid == true) { <h2>Log info</h2> <div id="output"> <table id="metadata"> <caption>Game info:</caption> <tr> <th>Stardew Valley:</th> <td v-pre>@Model.ParsedLog.GameVersion on @Model.ParsedLog.OperatingSystem</td> </tr> <tr> <th>SMAPI:</th> <td v-pre>@Model.ParsedLog.ApiVersion</td> </tr> <tr> <th>Folder:</th> <td v-pre>@Model.ParsedLog.GamePath</td> </tr> <tr> <th>Log started:</th> <td>@Model.ParsedLog.Timestamp.UtcDateTime.ToString("yyyy-MM-dd HH:mm") UTC ({{localTimeStarted}} your time)</td> </tr> </table> <br /> <table id="mods"> <caption> Installed mods: <span class="notice txt"><i>click any mod to filter</i></span> <span class="notice btn txt" v-on:click="showAllMods" v-show="stats.modsHidden > 0">show all</span> <span class="notice btn txt" v-on:click="hideAllMods" v-show="stats.modsShown > 0 && stats.modsHidden > 0">hide all</span> </caption> @foreach (var mod in Model.ParsedLog.Mods.Where(p => p.ContentPackFor == null)) { <tr v-on:click="toggleMod('@GetSlug(mod.Name)')" class="mod-entry" v-bind:class="{ hidden: !showMods['@GetSlug(mod.Name)'] }"> <td><input type="checkbox" v-bind:checked="showMods['@GetSlug(mod.Name)']" v-show="anyModsHidden" /></td> <td v-pre> <strong>@mod.Name</strong> @mod.Version @if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out LogModInfo[] contentPackList)) { <div class="content-packs"> @foreach (var contentPack in contentPackList) { <text>+ @contentPack.Name @contentPack.Version</text><br /> } </div> } </td> <td v-pre>@mod.Author</td> @if (mod.Errors == 0) { <td v-pre class="color-green">no errors</td> } else if (mod.Errors == 1) { <td v-pre class="color-red">@mod.Errors error</td> } else { <td v-pre class="color-red">@mod.Errors errors</td> } </tr> } </table> <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> <table id="log"> @foreach (var message in Model.ParsedLog.Messages) { string levelStr = message.Level.ToString().ToLower(); <tr class="@levelStr mod" v-show="filtersAllow('@GetSlug(message.Mod)', '@levelStr')"> <td v-pre>@message.Time</td> <td v-pre>@message.Level.ToString().ToUpper()</td> <td v-pre data-title="@message.Mod">@message.Mod</td> <td v-pre>@message.Text</td> </tr> if (message.Repeated > 0) { <tr class="@levelStr mod mod-repeat" v-show="filtersAllow('@GetSlug(message.Mod)', '@levelStr')"> <td colspan="3"></td> <td v-pre><i>repeats [@message.Repeated] times.</i></td> </tr> } } </table> </div> } else if (Model.ParsedLog?.IsValid == false) { <h3>Raw log</h3> <pre v-pre>@Model.ParsedLog.RawText</pre> } <div id="upload-area"> <div id="popup-upload" class="popup"> <h1>Upload log file</h1> <div class="frame"> <ol> <li><a href="https://stardewvalleywiki.com/Modding:Player_FAQs#SMAPI_log" target="_blank">Find your SMAPI log file</a> (not the console text).</li> <li>Drag the file onto the textbox below (or paste the text in).</li> <li>Click <em>Parse</em>.</li> </ol> <textarea id="input" placeholder="Paste or drag the log here"></textarea> <div class="buttons"> <input type="button" id="submit" value="Parse" /> <input type="button" id="cancel" value="Cancel" /> </div> </div> </div> <div id="uploader"></div> </div>