1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
|
@{
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_Guide/Troubleshooting#Find_your_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>
|