Skip to content

Commit 607417b

Browse files
Extracted the SQS message editor to a separate partial page
1 parent 3a0b3f8 commit 607417b

File tree

3 files changed

+368
-300
lines changed

3 files changed

+368
-300
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*
6+
* Copyright © Vincent Bengtsson & Contributors 2022-2024
7+
* https://github.com/Visual-Vincent/GuiStack
8+
*/
9+
10+
using System;
11+
12+
namespace GuiStack.Models
13+
{
14+
public class SQSMessageEditorModel
15+
{
16+
public string ElementId { get; set; }
17+
public string EditorVariable { get; set; }
18+
public string ContainerVariable { get; set; }
19+
20+
public SQSMessageEditorModel(string elementId, string containerVariable, string editorVariable)
21+
{
22+
ElementId = elementId;
23+
ContainerVariable = containerVariable;
24+
EditorVariable = editorVariable;
25+
}
26+
}
27+
}
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
@*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*
6+
* Copyright © Vincent Bengtsson & Contributors 2022-2024
7+
* https://github.com/Visual-Vincent/GuiStack
8+
*@
9+
10+
@using System.Text.RegularExpressions;
11+
@using GuiStack.Models
12+
13+
@model SQSMessageEditorModel
14+
15+
@{
16+
string prefix = Regex.Replace(Model.ElementId, "[^A-Z0-9_]+", "_", RegexOptions.IgnoreCase);
17+
string container = $"__sqs_{prefix}_editor_container";
18+
string editor = $"__sqs_{prefix}_msg_editor";
19+
string langlist = $"__sqs_{prefix}_lang_list";
20+
string protolist = $"__sqs_{prefix}_protobuf_list";
21+
}
22+
23+
<div id="@Model.ElementId">
24+
<table class="gs-info-table">
25+
<tbody>
26+
<tr>
27+
<td class="additional-info"></td>
28+
<td></td>
29+
<td class="text-right">
30+
<div>
31+
Syntax highlighting:
32+
<select class="lang-select">
33+
<option value="plaintext" selected>Plain Text</option>
34+
<option value="json">JSON</option>
35+
<option value="xml">XML</option>
36+
<option value="protojson">Protobuf (JSON)</option>
37+
</select>
38+
</div>
39+
<div class="definition-box hidden" style="margin-top: 4px">
40+
Selected definition: <span class="definition-label" style="color: #00E0FF"></span>
41+
</div>
42+
</td>
43+
<td></td>
44+
</tr>
45+
<tr>
46+
<td class="additional-info" style="vertical-align: top; height: 1px">
47+
<div class="gs-drop-container" style="display: flex; flex-direction: column; height: 100%">
48+
<div class="gs-drop-overlay">
49+
Drop Protobuf file(s)
50+
</div>
51+
<form>
52+
<a no-href class="gs-upload-field">
53+
<i class="bi bi-plus-circle"></i> Add definition(s)...
54+
<input type="file" class="sqs-protobuf-fileupload" multiple />
55+
</a>
56+
</form>
57+
@* calc(height - fontsize * lineheight - padding * 2 - border * 2 - scrollbar)*@
58+
<div style="width: 200px; max-height: calc(400px - 1em * 1.2 - 8px * 2 - 1px * 2 - 16px); overflow: auto">
59+
<partial name="~/Pages/_ProtobufListPartial.cshtml" />
60+
</div>
61+
</div>
62+
</td>
63+
<td class="collapsed">
64+
<a no-href class="vertical" onclick="sqs_MsgEditor_@(prefix)_ProtobufPanel_Toggle(event)">
65+
<i class="fa-solid fa-angles-down" style="font-size: 12px"></i> Protobuf <i class="fa-solid fa-angles-down" style="font-size: 12px"></i>
66+
</a>
67+
</td>
68+
<td class="expanded">
69+
<a no-href class="vertical" onclick="sqs_MsgEditor_@(prefix)_ProtobufPanel_Toggle(event)">
70+
<i class="fa-solid fa-angles-up" style="font-size: 12px"></i> Protobuf <i class="fa-solid fa-angles-up" style="font-size: 12px"></i>
71+
</a>
72+
</td>
73+
<td style="width: 100%">
74+
<div class="gs-sqs-message-contents" placeholder="Enter message"></div>
75+
</td>
76+
<td style="vertical-align: top">
77+
<a no-href class="gs-button lnk-sqs-send" style="display: block; margin-left: 8px; margin-bottom: 8px">Send</a>
78+
<input id="sqs-@prefix-send-as-base64" class="sqs-send-as-base64" type="checkbox" />
79+
<label for="sqs-@prefix-send-as-base64" title="Send message as a Base64-encoded string">Base64</label>
80+
</td>
81+
</tr>
82+
</tbody>
83+
</table>
84+
</div>
85+
86+
<script type="text/javascript">
87+
var @editor = null;
88+
var @container = document.getElementById("@Model.ElementId");
89+
var @langlist = @(container).querySelector("select.lang-select");
90+
var @protolist = @(container).querySelector(".gs-protobuf-list");
91+
92+
@if(!string.IsNullOrWhiteSpace(Model.ContainerVariable))
93+
{
94+
<text>@Model.ContainerVariable = @container;</text>
95+
}
96+
97+
$("#@(Model.ElementId) select.lang-select").change(sqs_MsgEditor_@(prefix)_LanguageSelect);
98+
$("#@(Model.ElementId) .sqs-protobuf-fileupload").change(sqs_MsgEditor_@(prefix)_ProtobufUpload_FilesSelected);
99+
100+
// jQuery does not work as expected with custom events
101+
@(protolist).addEventListener("gs.protobuf.selected", sqs_MsgEditor_@(prefix)_ProtobufList_DefinitionSelected);
102+
103+
require(["vs/editor/editor.main"], function() {
104+
@editor = monaco.editor.create(@(container).querySelector(".gs-sqs-message-contents"), {
105+
value: "",
106+
language: "plaintext",
107+
theme: "vs-dark",
108+
minimap: {
109+
enabled: false
110+
}
111+
});
112+
113+
@if(!string.IsNullOrWhiteSpace(Model.EditorVariable))
114+
{
115+
<text>@Model.EditorVariable = @editor;</text>
116+
}
117+
118+
@(editor).layout();
119+
});
120+
121+
function sqs_MsgEditor_@(prefix)_LanguageSelect(event)
122+
{
123+
var lang = event.currentTarget.value;
124+
var parentRow = gs_GetParentTableRow(event.currentTarget, true);
125+
var definitionBox = parentRow.querySelector(".definition-box");
126+
127+
if(lang == "protojson")
128+
{
129+
lang = "json";
130+
definitionBox.classList.remove("hidden");
131+
}
132+
else
133+
{
134+
definitionBox.classList.add("hidden");
135+
}
136+
137+
monaco.editor.setModelLanguage(@(editor).getModel(), lang);
138+
}
139+
140+
function sqs_MsgEditor_@(prefix)_ProtobufPanel_Toggle(event)
141+
{
142+
var editorElement = @(container).querySelector(".gs-sqs-message-contents");
143+
gsevent_InfoTable_ToggleButton_Click(event);
144+
145+
// Force the editor to shrink before resizing
146+
editorElement.style.width = "1px";
147+
@(editor).layout();
148+
editorElement.style.removeProperty("width");
149+
@(editor).layout();
150+
}
151+
152+
function sqs_MsgEditor_@(prefix)_ProtobufList_DefinitionSelected(event)
153+
{
154+
var protoPath = event.detail.protoPath;
155+
var protoContents = gs_GetProtobufMessage(protoPath);
156+
157+
@(editor).setValue(protoContents);
158+
159+
@(container).setAttribute("data-selected-proto", event.detail.protoPath);
160+
@(container).querySelector(".definition-label").innerText = event.detail.protoPath;
161+
162+
gs_SelectOption(@langlist, "protojson");
163+
}
164+
165+
function sqs_MsgEditor_@(prefix)_ProtobufUpload_FilesSelected(event)
166+
{
167+
var element = event.currentTarget;
168+
var files = element.files;
169+
var formData = new FormData();
170+
171+
var cleanup = function() {
172+
navigator.sendBeacon("@Url.Action("ClearSession", "Proto")");
173+
document.getElementById("gs-upload-progress-overlay").classList.remove("visible");
174+
};
175+
176+
if(files.length <= 0)
177+
return;
178+
179+
for(var i = 0; i < files.length; i++)
180+
{
181+
formData.append("files", files[i]);
182+
}
183+
184+
document.getElementById("gs-upload-progress-overlay").classList.add("visible");
185+
186+
$.ajax({
187+
type: "POST",
188+
url: "@Url.Action("Upload", "Proto")",
189+
data: formData,
190+
cache: false,
191+
processData: false,
192+
contentType: false,
193+
error: function(request, status, errorThrown) {
194+
cleanup();
195+
gsevent_AjaxError(request, status, errorThrown);
196+
},
197+
success: function(result) {
198+
if(isNull(result))
199+
{
200+
cleanup();
201+
gs_DisplayError("Server response was empty");
202+
return;
203+
}
204+
205+
if(typeof result === "string")
206+
{
207+
cleanup();
208+
gs_DisplayError("Response was a string, expected JSON object. Value: " + result);
209+
return;
210+
}
211+
212+
if(!isNull(result.error))
213+
{
214+
cleanup();
215+
gs_DisplayError("An error occurred: " + result.error);
216+
return;
217+
}
218+
219+
if(!Array.isArray(result))
220+
{
221+
cleanup();
222+
gs_DisplayError("Unexpected response: " + JSON.stringify(result));
223+
return;
224+
}
225+
226+
try
227+
{
228+
var parsed = 0;
229+
230+
for(var i = 0; i < result.length; i++)
231+
{
232+
var filename = result[i];
233+
234+
if(typeof filename !== "string")
235+
continue;
236+
237+
protobuf.load("/api/Proto/Download/" + filename, function(err, root) {
238+
if(err)
239+
{
240+
parsed++;
241+
242+
if(parsed >= result.length)
243+
cleanup();
244+
245+
gs_DisplayError(
246+
"An error occurred while parsing '" + filename + "'.\n\n" +
247+
"Things to keep in mind if you receive error 404:\n" +
248+
"* Make sure you include all Protobuf files that '" + filename + "' requires in the same request\n" +
249+
"* GuiStack does not support special characters in filenames and thus will convert them to '_'. This will break the importing of such files\n\n" +
250+
"Error message: " + err
251+
);
252+
return;
253+
}
254+
255+
try
256+
{
257+
var packages = root.nested;
258+
259+
for(var pkgName in packages)
260+
{
261+
if(pkgName === "google")
262+
continue;
263+
264+
var pkgRoot = packages[pkgName];
265+
var rootJObj = pkgRoot.toJSON();
266+
var rootJson = JSON.stringify(rootJObj);
267+
var types = pkgRoot.nested;
268+
269+
localStorage.setItem("protoroot/" + pkgName, rootJson);
270+
271+
for(var typeName in types)
272+
{
273+
var type = types[typeName];
274+
275+
// TODO: How are we going to handle enums?
276+
if(!(type instanceof protobuf.Message || type instanceof protobuf.Type))
277+
continue;
278+
279+
var empty = type.create({});
280+
var obj = type.toObject(empty, {
281+
enums: String,
282+
longs: String,
283+
bytes: String,
284+
defaults: true,
285+
arrays: true,
286+
objects: true
287+
});
288+
289+
var json = JSON.stringify(obj, null, 4);
290+
localStorage.setItem("proto/" + pkgName + "/" + typeName, json);
291+
}
292+
}
293+
}
294+
catch(error)
295+
{
296+
gs_DisplayError("An error occurred while parsing '" + filename + "': " + error);
297+
}
298+
299+
parsed++;
300+
301+
if(parsed >= result.length)
302+
{
303+
cleanup();
304+
gs_RefreshProtobufList(@protolist);
305+
}
306+
});
307+
}
308+
}
309+
catch(error)
310+
{
311+
cleanup();
312+
gs_DisplayError("An error occurred while parsing Protobuf definitions: " + error);
313+
}
314+
},
315+
xhr: function() {
316+
var xhr = $.ajaxSettings.xhr();
317+
318+
xhr.upload.addEventListener("progress", function(event) {
319+
if(!event.lengthComputable)
320+
return;
321+
322+
var progress = Math.round(event.loaded / event.total * 100);
323+
document.querySelector("#gs-upload-progress-overlay .upload-progress").innerText = progress.toFixed(0) + "%";
324+
});
325+
326+
return xhr ;
327+
}
328+
});
329+
330+
element.value = null;
331+
}
332+
</script>

0 commit comments

Comments
 (0)