MonacoEditPage.cpp 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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.h>
  7. #include <winrt/Windows.Storage.Streams.h>
  8. #include <winrt/Microsoft.Web.WebView2.Core.h>
  9. #include <nlohmann/json.hpp>
  10. #include "UI.h"
  11. using namespace winrt;
  12. using namespace Windows::Storage;
  13. using namespace Windows::Storage::Streams;
  14. using namespace Windows::UI::Xaml;
  15. namespace winrt::Maple_App::implementation
  16. {
  17. MonacoEditPage::MonacoEditPage()
  18. {
  19. InitializeComponent();
  20. }
  21. fire_and_forget MonacoEditPage::Page_Loaded(IInspectable const&, RoutedEventArgs const&)
  22. {
  23. try {
  24. auto const lifetime{ get_strong() };
  25. co_await initializeWebView();
  26. }
  27. catch (...)
  28. {
  29. UI::NotifyException(L"Initializing WebView");
  30. }
  31. }
  32. IAsyncAction MonacoEditPage::initializeWebView() {
  33. auto const lifetime{ get_strong() };
  34. auto const webview = WebView();
  35. if (m_webviewState != MonacoEditPageWebViewState::Uninitialized)
  36. {
  37. co_return;
  38. }
  39. m_webviewState = MonacoEditPageWebViewState::AwaitingEditorReady;
  40. co_await webview.EnsureCoreWebView2Async();
  41. webview.CoreWebView2().SetVirtualHostNameToFolderMapping(
  42. L"maple-monaco-editor-app-root.com",
  43. packagePath,
  44. Microsoft::Web::WebView2::Core::CoreWebView2HostResourceAccessKind::DenyCors
  45. );
  46. webview.CoreWebView2().SetVirtualHostNameToFolderMapping(
  47. L"maple-monaco-editor-config-root.com",
  48. configPath,
  49. Microsoft::Web::WebView2::Core::CoreWebView2HostResourceAccessKind::Allow
  50. );
  51. webview.Source(Uri{ WEBVIEW_EDITOR_URL });
  52. }
  53. fire_and_forget MonacoEditPage::OnNavigatedTo(NavigationEventArgs const& e) {
  54. try {
  55. auto const lifetime{ get_strong() };
  56. auto const param = e.Parameter().as<Maple_App::ConfigViewModel>();
  57. hstring fileName;
  58. try
  59. {
  60. fileName = param.File().Name();
  61. }
  62. catch (...) {}
  63. if (fileName.empty())
  64. {
  65. co_return;
  66. }
  67. m_currentFileName = fileName;
  68. switch (m_webviewState)
  69. {
  70. case MonacoEditPageWebViewState::Uninitialized:
  71. co_await initializeWebView();
  72. break;
  73. case MonacoEditPageWebViewState::AwaitingEditorReady:
  74. break;
  75. case MonacoEditPageWebViewState::EditorReady:
  76. co_await lifetime->WebView().ExecuteScriptAsync(hstring{ L"window.mapleHostApi.loadFile(`http://maple-monaco-editor-config-root.com/" } +
  77. fileName +
  78. L"`)");
  79. break;
  80. }
  81. }
  82. catch (...)
  83. {
  84. UI::NotifyException(L"Loading WebView page");
  85. }
  86. }
  87. fire_and_forget MonacoEditPage::WebView_WebMessageReceived(
  88. MUXC::WebView2 const& sender,
  89. CoreWebView2WebMessageReceivedEventArgs const& args
  90. )
  91. {
  92. try {
  93. auto const lifetime{ get_strong() };
  94. auto const source = args.Source();
  95. if (source != WEBVIEW_EDITOR_URL)
  96. {
  97. co_return;
  98. }
  99. nlohmann::json doc;
  100. bool hasError{};
  101. try {
  102. doc = nlohmann::json::parse(to_string(args.WebMessageAsJson()));
  103. }
  104. catch (...)
  105. {
  106. hasError = true;
  107. }
  108. if (hasError)
  109. {
  110. co_return;
  111. }
  112. std::string const& cmd = doc["cmd"];
  113. if (cmd == "editorReady")
  114. {
  115. m_webviewState = MonacoEditPageWebViewState::EditorReady;
  116. co_await WebView().ExecuteScriptAsync(hstring{ L"window.mapleHostApi.loadFile(`" } +
  117. CONFIG_ROOT_VIRTUAL_HOSTW +
  118. m_currentFileName +
  119. L"`)");
  120. SaveModifiedContent = [weak = weak_ref{ lifetime }]()->IAsyncAction
  121. {
  122. if (auto const lifetime{ weak.get() })
  123. {
  124. struct awaiter : std::suspend_always
  125. {
  126. void await_suspend(
  127. std::coroutine_handle<> handle)
  128. {
  129. lifetime->m_fileSaveHandle = handle;
  130. }
  131. com_ptr<MonacoEditPage> lifetime;
  132. };
  133. lifetime->WebView().ExecuteScriptAsync(hstring{ L"window.mapleHostApi.requestSaveCurrent()" });
  134. co_await awaiter{ .lifetime = lifetime };
  135. }
  136. co_return;
  137. };
  138. }
  139. else if (cmd == "save")
  140. {
  141. std::string path{ doc["path"] };
  142. if (path == m_currentSavingFileName)
  143. {
  144. co_return;
  145. }
  146. m_currentSavingFileName = path;
  147. auto const configDir{ co_await ApplicationData::Current().LocalFolder().CreateFolderAsync(L"config", CreationCollisionOption::OpenIfExists) };
  148. auto const configDirPath{ to_string(configDir.Path()) + "\\" };
  149. if (auto const pos{ path.find(CONFIG_ROOT_VIRTUAL_HOST) }; pos != std::string::npos)
  150. {
  151. path = path.replace(pos, strlen(CONFIG_ROOT_VIRTUAL_HOST), configDirPath);
  152. }
  153. auto const file{ co_await StorageFile::GetFileFromPathAsync(to_hstring(path)) };
  154. std::string const data{ doc["text"] };
  155. auto const fstream = co_await file.OpenAsync(FileAccessMode::ReadWrite, StorageOpenOptions::AllowOnlyReaders);
  156. fstream.Size(0);
  157. try {
  158. DataWriter const wr(fstream);
  159. try {
  160. wr.WriteBytes(array_view(reinterpret_cast <uint8_t const*>(data.data()), data.size()));
  161. co_await wr.StoreAsync();
  162. co_await fstream.FlushAsync().as<IAsyncOperation<bool>>();
  163. wr.Close();
  164. }
  165. catch (...)
  166. {
  167. wr.Close();
  168. throw;
  169. }
  170. fstream.Close();
  171. }
  172. catch (...)
  173. {
  174. fstream.Close();
  175. throw;
  176. }
  177. co_await resume_foreground(Dispatcher());
  178. if (auto const fileSaveHandle{ std::exchange(m_fileSaveHandle, nullptr) }) {
  179. fileSaveHandle();
  180. }
  181. }
  182. m_currentSavingFileName = "";
  183. co_return;
  184. }
  185. catch (...)
  186. {
  187. m_currentSavingFileName = "";
  188. UI::NotifyException(L"Processing web messages");
  189. }
  190. }
  191. }