MonacoEditPage.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. #include "pch.h"
  2. #include "MonacoEditPage.h"
  3. #if __has_include("MonacoEditPage.g.cpp")
  4. #include "MonacoEditPage.g.cpp"
  5. #endif
  6. #include <winrt/Windows.Storage.Streams.h>
  7. #include <winrt/Microsoft.Web.WebView2.Core.h>
  8. #include <nlohmann/json.hpp>
  9. #include "UI.h"
  10. #include "ConfigUtil.h"
  11. using namespace winrt;
  12. using namespace Windows::Storage;
  13. using namespace Windows::Storage::Streams;
  14. using namespace Windows::UI::Xaml;
  15. using namespace Microsoft::Web::WebView2::Core;
  16. namespace winrt::Maple_App::implementation
  17. {
  18. MonacoEditPage::MonacoEditPage()
  19. {
  20. InitializeComponent();
  21. }
  22. fire_and_forget MonacoEditPage::Page_Loaded(IInspectable const&, RoutedEventArgs const&)
  23. {
  24. try {
  25. auto const lifetime{ get_strong() };
  26. co_await initializeWebView();
  27. }
  28. catch (...)
  29. {
  30. UI::NotifyException(L"Initializing WebView");
  31. }
  32. }
  33. IAsyncAction MonacoEditPage::initializeWebView() {
  34. auto const lifetime{ get_strong() };
  35. auto const webview = WebView();
  36. if (m_webviewState != MonacoEditPageWebViewState::Uninitialized)
  37. {
  38. co_return;
  39. }
  40. m_webviewState = MonacoEditPageWebViewState::AwaitingEditorReady;
  41. co_await webview.EnsureCoreWebView2Async();
  42. if (!m_webviewResourceRequestedEventHandle)
  43. {
  44. webview.CoreWebView2().AddWebResourceRequestedFilter(
  45. hstring(CONFIG_ROOT_VIRTUAL_HOSTW) + L"*",
  46. CoreWebView2WebResourceContext::Fetch
  47. );
  48. webview.CoreWebView2().AddWebResourceRequestedFilter(
  49. hstring(CONFIG_ROOT_VIRTUAL_HOSTW) + L"*",
  50. CoreWebView2WebResourceContext::XmlHttpRequest
  51. );
  52. webview.CoreWebView2().AddWebResourceRequestedFilter(
  53. hstring(CONFIG_ROOT_VIRTUAL_HOSTW) + L"*",
  54. CoreWebView2WebResourceContext::Document
  55. );
  56. m_webviewResourceRequestedEventHandle = webview.CoreWebView2().WebResourceRequested(
  57. [weak = weak_ref(lifetime)](auto const webview, CoreWebView2WebResourceRequestedEventArgs const e) -> fire_and_forget
  58. {
  59. auto const deferral = e.GetDeferral();
  60. try {
  61. if (auto const self{ weak.get() }) {
  62. auto const configDir = co_await ConfigUtil::GetConfigFolder();
  63. if (configDir.Path() != self->m_lastConfigFolderPath)
  64. {
  65. co_return;
  66. }
  67. auto path = to_string(e.Request().Uri());
  68. if (auto const pos{ path.find(CONFIG_ROOT_VIRTUAL_HOST) }; pos != std::string::npos)
  69. {
  70. path = path.replace(pos, strlen(CONFIG_ROOT_VIRTUAL_HOST), "");
  71. }
  72. auto const file = co_await configDir.GetFileAsync(to_hstring(path));
  73. auto const fstream = co_await file.OpenReadAsync();
  74. e.Response(webview.Environment().CreateWebResourceResponse(
  75. std::move(fstream),
  76. 200,
  77. L"OK",
  78. hstring(L"Content-Length: ") + to_hstring(fstream.Size()) + L"\nContent-Type: text/plain\nAccess-Control-Allow-Origin: http://maple-monaco-editor-app-root.com"
  79. ));
  80. }
  81. }
  82. catch (...)
  83. {
  84. UI::NotifyException(L"Handling web requests");
  85. }
  86. deferral.Complete();
  87. });
  88. }
  89. webview.CoreWebView2().SetVirtualHostNameToFolderMapping(
  90. L"maple-monaco-editor-app-root.com",
  91. packagePath,
  92. CoreWebView2HostResourceAccessKind::DenyCors
  93. );
  94. webview.Source(Uri{ WEBVIEW_EDITOR_URL });
  95. }
  96. fire_and_forget MonacoEditPage::OnNavigatedTo(NavigationEventArgs const& e) {
  97. try {
  98. auto const lifetime{ get_strong() };
  99. auto const param = e.Parameter().as<Maple_App::ConfigViewModel>();
  100. hstring fileName;
  101. try
  102. {
  103. fileName = param.File().Name();
  104. }
  105. catch (...) {}
  106. if (fileName.empty())
  107. {
  108. co_return;
  109. }
  110. auto const parent = co_await param.File().GetParentAsync();
  111. if (!parent)
  112. {
  113. UI::NotifyUser("Failed to load config folder.", L"Error: loading file");
  114. co_return;
  115. }
  116. auto const parentPath = parent.Path();
  117. if (parentPath != m_lastConfigFolderPath)
  118. {
  119. m_lastConfigFolderPath = parentPath;
  120. m_webviewState = MonacoEditPageWebViewState::Uninitialized;
  121. }
  122. m_currentFileName = fileName;
  123. switch (m_webviewState)
  124. {
  125. case MonacoEditPageWebViewState::Uninitialized:
  126. co_await initializeWebView();
  127. co_await lifetime->WebView().ExecuteScriptAsync(L"window.mapleHostApi.resetFiles()");
  128. break;
  129. case MonacoEditPageWebViewState::AwaitingEditorReady:
  130. break;
  131. case MonacoEditPageWebViewState::EditorReady:
  132. co_await lifetime->WebView().ExecuteScriptAsync(hstring{ L"window.mapleHostApi.loadFile(`http://maple-monaco-editor-config-root.com/" } +
  133. fileName +
  134. L"`)");
  135. break;
  136. }
  137. }
  138. catch (...)
  139. {
  140. UI::NotifyException(L"Loading WebView page");
  141. }
  142. }
  143. fire_and_forget MonacoEditPage::WebView_WebMessageReceived(
  144. MUXC::WebView2 const& sender,
  145. CoreWebView2WebMessageReceivedEventArgs const& args
  146. )
  147. {
  148. try {
  149. auto const lifetime{ get_strong() };
  150. auto const source = args.Source();
  151. if (source != WEBVIEW_EDITOR_URL)
  152. {
  153. co_return;
  154. }
  155. nlohmann::json doc;
  156. bool hasError{};
  157. try {
  158. doc = nlohmann::json::parse(to_string(args.WebMessageAsJson()));
  159. }
  160. catch (...)
  161. {
  162. hasError = true;
  163. }
  164. if (hasError)
  165. {
  166. co_return;
  167. }
  168. std::string const& cmd = doc["cmd"];
  169. if (cmd == "editorReady")
  170. {
  171. m_webviewState = MonacoEditPageWebViewState::EditorReady;
  172. co_await WebView().ExecuteScriptAsync(hstring{ L"window.mapleHostApi.loadFile(`" } +
  173. CONFIG_ROOT_VIRTUAL_HOSTW +
  174. m_currentFileName +
  175. L"`)");
  176. SaveModifiedContent = [weak = weak_ref{ lifetime }]()->IAsyncAction
  177. {
  178. if (auto const lifetime{ weak.get() })
  179. {
  180. struct awaiter : std::suspend_always
  181. {
  182. void await_suspend(
  183. std::coroutine_handle<> handle)
  184. {
  185. lifetime->m_fileSaveHandle = handle;
  186. }
  187. com_ptr<MonacoEditPage> lifetime;
  188. };
  189. lifetime->WebView().ExecuteScriptAsync(hstring{ L"window.mapleHostApi.requestSaveCurrent()" });
  190. co_await awaiter{ .lifetime = lifetime };
  191. }
  192. co_return;
  193. };
  194. }
  195. else if (cmd == "save")
  196. {
  197. if (m_webviewState != MonacoEditPageWebViewState::EditorReady)
  198. {
  199. co_return;
  200. }
  201. std::string path{ doc["path"] };
  202. if (path == m_currentSavingFileName)
  203. {
  204. co_return;
  205. }
  206. m_currentSavingFileName = path;
  207. auto const configDir = co_await ConfigUtil::GetConfigFolder();
  208. if (configDir.Path() != m_lastConfigFolderPath)
  209. {
  210. co_return;
  211. }
  212. if (auto const pos{ path.find(CONFIG_ROOT_VIRTUAL_HOST) }; pos != std::string::npos)
  213. {
  214. path = path.replace(pos, strlen(CONFIG_ROOT_VIRTUAL_HOST), "");
  215. }
  216. hstring fileName = to_hstring(path);
  217. StorageFile file{ nullptr };
  218. try {
  219. file = co_await configDir.GetFileAsync(fileName);
  220. }
  221. catch (hresult_error const& hr)
  222. {
  223. if (hr.code().value == 0x80070002)
  224. {
  225. co_return;
  226. }
  227. throw;
  228. }
  229. std::string const data{ doc["text"] };
  230. auto const fstream = co_await file.OpenAsync(FileAccessMode::ReadWrite, StorageOpenOptions::AllowOnlyReaders);
  231. try {
  232. DataWriter const wr(fstream);
  233. try {
  234. wr.WriteBytes(array_view(reinterpret_cast <uint8_t const*>(data.data()), data.size()));
  235. co_await wr.StoreAsync();
  236. co_await fstream.FlushAsync().as<IAsyncOperation<bool>>();
  237. fstream.Size(data.size());
  238. wr.Close();
  239. }
  240. catch (...)
  241. {
  242. wr.Close();
  243. throw;
  244. }
  245. fstream.Close();
  246. }
  247. catch (...)
  248. {
  249. fstream.Close();
  250. throw;
  251. }
  252. co_await resume_foreground(Dispatcher());
  253. if (auto const fileSaveHandle{ std::exchange(m_fileSaveHandle, nullptr) }) {
  254. fileSaveHandle();
  255. }
  256. }
  257. m_currentSavingFileName = "";
  258. co_return;
  259. }
  260. catch (...)
  261. {
  262. m_currentSavingFileName = "";
  263. UI::NotifyException(L"Processing web messages");
  264. }
  265. }
  266. }