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
|
@{
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=20180101" />
<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=20180101"></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?.ToDictionary(p => GetSlug(p.Name), p => 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>
<input type="button" id="upload-button" value="Share a new log" />
@if (Model.ParsedLog?.IsValid == true)
{
<h2>Parsed log</h2>
<div id="output">
<table id="metadata">
<caption>Game info:</caption>
<tr>
<td>SMAPI version:</td>
<td>@Model.ParsedLog.ApiVersion</td>
</tr>
<tr>
<td>Game version:</td>
<td>@Model.ParsedLog.GameVersion</td>
</tr>
<tr>
<td>Platform:</td>
<td>@Model.ParsedLog.OperatingSystem</td>
</tr>
<tr>
<td>Mods path:</td>
<td>@Model.ParsedLog.ModPath</td>
</tr>
<tr>
<td>Log started:</td>
<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-if="stats.modsHidden > 0">show all</span>
<span class="notice btn txt" v-on:click="hideAllMods" v-if="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-if="anyModsHidden" /></td>
<td>
@mod.Name
@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>
}
</div>
}
</td>
<td>@mod.Version</td>
<td>@mod.Author</td>
@if (mod.Errors == 0)
{
<td class="color-green">no errors</td>
}
else if (mod.Errors == 1)
{
<td class="color-red">@mod.Errors error</td>
}
else
{
<td 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-if="showMods['@message.Mod'] && showLevels['@levelStr']">
<td>@message.Time</td>
<td>@message.Level.ToString().ToUpper()</td>
<td data-title="@message.Mod">@message.Mod</td>
<td>@message.Text</td>
</tr>
if (message.Repeated > 0)
{
<tr class="@levelStr mod mod-repeat" v-if="showMods['@message.Mod'] && showLevels['@levelStr']">
<td colspan="3"></td>
<td><i>repeats [@message.Repeated] times.</i></td>
</tr>
}
}
</table>
</div>
}
else if (Model.ParsedLog?.IsValid == false)
{
<h2>Parsed log</h2>
<div id="error" class="color-red">
<p><strong>We couldn't parse that file, but you can still share the link.</strong></p>
<p>Error details: @Model.ParsedLog.Error</p>
</div>
<h3>Raw log</h3>
<pre>@Model.ParsedLog.RawTextIfError</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>
<li>Share the URL of the new page.</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>
|