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