Skip to content

Commit c943ad9

Browse files
committed
Added monaco editor docs and improved components page.
1 parent c8479dd commit c943ad9

File tree

3 files changed

+400
-8
lines changed

3 files changed

+400
-8
lines changed

docs/third_party/monaco_editor.md

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
# Monaco Editor
2+
The monaco editor is one of the libraries which requires runtime imports.
3+
This requires the application to serve the index.html file through a custom scheme and also serve the required files.
4+
5+
## Install
6+
Add the monaco editor to your project by using the following command:
7+
```bash
8+
npx add-dependencies monaco-editor
9+
```
10+
11+
## Copy Source Files to Bin
12+
The monaco editor requires some files to be present "somewhere on disk". In this case next to the application in "dynamic_sources".
13+
To achieve this, we have to add the following to the root CMakeLists.txt:
14+
```cmake
15+
if (EMSCRIPTEN)
16+
# ...
17+
else()
18+
# ...
19+
20+
# Copy bin files to destination.
21+
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
22+
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_BINARY_DIR}/module_${PROJECT_NAME}/bin" "$<TARGET_FILE_DIR:${PROJECT_NAME}>/dynamic_sources"
23+
24+
# The example code uses the index.html file that is bundled.
25+
# Remove this if you want to also load your index from disk.
26+
COMMAND ${CMAKE_COMMAND} -E rm "$<TARGET_FILE_DIR:${PROJECT_NAME}>/dynamic_sources/index.html"
27+
COMMAND ${CMAKE_COMMAND} -E rm "$<TARGET_FILE_DIR:${PROJECT_NAME}>/dynamic_sources/index.js"
28+
VERBATIM
29+
)
30+
endif()
31+
```
32+
33+
## Serve Application Through Custom Scheme
34+
To serve the application through a custom scheme, you have to register the scheme.
35+
36+
backend/main.hpp:
37+
```cpp
38+
#pragma once
39+
40+
#include <memory>
41+
#include <filesystem>
42+
43+
class Main
44+
{
45+
public:
46+
Main(std::filesystem::path const& programDirectory);
47+
~Main();
48+
Main(Main const&) = delete;
49+
Main(Main&&);
50+
Main& operator=(Main const&) = delete;
51+
Main& operator=(Main&&)
52+
53+
void run();
54+
55+
private:
56+
struct Implementation;
57+
std::unique_ptr<Implementation> impl_;
58+
};
59+
```
60+
61+
backend/main.cpp:
62+
```cpp
63+
#include <backend/main.hpp>
64+
65+
// This file is generated by nui.
66+
#include <index.hpp>
67+
68+
#include <nui/core.hpp>
69+
#include <nui/window.hpp>
70+
#include <roar/mime_type.hpp>
71+
72+
#include <string>
73+
#include <sstream>
74+
#include <fstream>
75+
76+
using namespace std::string_literals;
77+
78+
struct Main::Implementation
79+
{
80+
Nui::Window window;
81+
82+
// This function is called when a "myScheme://" url is requested.
83+
auto onSchemeRequest(std::filesystem::path const& programDirectory, Nui::CustomSchemeRequest const& request)
84+
{
85+
using namespace Nui;
86+
const auto makeCorsHeader = [](std::string const& contentType) {
87+
return std::unordered_multimap<std::string, std::string>{
88+
{"Content-Type"s, contentType},
89+
// Do not forget to allow CORS
90+
{"Access-Control-Allow-Origin"s, "*"s},
91+
};
92+
};
93+
94+
const auto makeCorsResponse =
95+
[&makeCorsHeader](
96+
int statusCode, std::string const& reasonPhrase, std::string const& contentType, std::string body) {
97+
return CustomSchemeResponse{
98+
.statusCode = statusCode,
99+
.reasonPhrase = reasonPhrase,
100+
.headers = makeCorsHeader(contentType),
101+
.body = std::move(body),
102+
};
103+
};
104+
105+
const auto url = request.parseUrl();
106+
const auto pathString = url->pathAsString();
107+
108+
if (!url)
109+
return makeCorsResponse(400, "Bad Request", "text/plain"s, "Bad Request");
110+
111+
// return index from memory (not necessary, can also load from disk, if you want).
112+
if (pathString == "/index.html")
113+
return makeCorsResponse(200, "OK", "text/html"s, index());
114+
115+
// Point path to dynamic_sources folder.
116+
const auto file = programDirectory / "dynamic_sources" / std::filesystem::relative(pathString, "/");
117+
118+
// Check if file exists and return 404 if not
119+
if (!std::filesystem::exists(file))
120+
return makeCorsResponse(404, "Not Found", "text/plain"s, "Not Found");
121+
122+
// Read file
123+
std::ifstream reader{file, std::ios::binary};
124+
if (!reader)
125+
{
126+
return makeCorsResponse(
127+
500, "Internal Server Error", "text/plain"s, "Internal Server Error - Could not open file.");
128+
}
129+
std::ostringstream payload;
130+
payload << reader.rdbuf();
131+
const std::string content = std::move(payload).str();
132+
133+
// Get mime type
134+
const std::string mime = Roar::extensionToMime(file.extension().string()).value_or("application/octet-stream");
135+
136+
// Return file
137+
return makeCorsResponse(content.empty() ? 204 : 200, "OK", mime, std::move(content));
138+
}
139+
140+
auto createSchemeHandler(std::filesystem::path const& programDirectory)
141+
{
142+
return Nui::CustomScheme{
143+
.scheme = "myScheme"s,
144+
.allowedOrigins = {"*"s},
145+
.onRequest = std::bind(&Implementation::onSchemeRequest, this, programDirectory, std::placeholders::_1),
146+
.treatAsSecure = true,
147+
.hasAuthorityComponent = true,
148+
};
149+
}
150+
151+
Implementation(std::filesystem::path const& programDirectory)
152+
: window{Nui::WindowOptions{
153+
.title = "Nui",
154+
.customSchemes = {createSchemeHandler(programDirectory)},
155+
}}
156+
{
157+
window.setSize(1650, 960, Nui::WebViewHint::WEBVIEW_HINT_NONE);
158+
}
159+
};
160+
161+
Main::Main(std::filesystem::path const& programDirectory)
162+
: impl_{std::make_unique<Implementation>(programDirectory)}
163+
{}
164+
~Main::Main() = default;
165+
Main::Main(Main&&) = default;
166+
Main& Main::operator=(Main&&) = default;
167+
168+
void Main::run()
169+
{
170+
// app.example is intentional to circumvent DNS timeout on windows:
171+
impl_->window.navigate("myScheme://app.example/index.html");
172+
impl_->window.run();
173+
}
174+
175+
int main(int, char** argv)
176+
{
177+
Main main{std::filesystem::path{argv[0]}.parent_path()};
178+
main.run();
179+
return 0;
180+
}
181+
```
182+
183+
## Create an Editor Class
184+
editor.hpp:
185+
```cpp
186+
#pragma once
187+
188+
#include <nui/frontend/element_renderer.hpp>
189+
190+
class Editor
191+
{
192+
public:
193+
Nui::ElementRenderer operator()();
194+
};
195+
```
196+
197+
The inline part can also be placed in any javascript file. You will have better language support from your IDE if you do.
198+
This just packs everything close together.
199+
editor.cpp:
200+
```cpp
201+
#include <editor.hpp>
202+
203+
#include <nui/frontend/elements.hpp>
204+
#include <nui/frontend/attributes.hpp>
205+
206+
// clang-format off
207+
#ifdef NUI_INLINE
208+
// @inline(js, monaco-editor)
209+
js_import JSONWorker from 'url:monaco-editor/esm/vs/language/json/json.worker.js';
210+
js_import CSSWorker from 'url:monaco-editor/esm/vs/language/css/css.worker.js';
211+
js_import HTMLWorker from 'url:monaco-editor/esm/vs/language/html/html.worker.js';
212+
js_import TSWorker from 'url:monaco-editor/esm/vs/language/typescript/ts.worker.js';
213+
js_import EditorWorker from 'url:monaco-editor/esm/vs/editor/editor.worker.js';
214+
215+
js_import * as monaco from 'monaco-editor';
216+
globalThis.monaco = monaco;
217+
218+
globalThis.MonacoEnvironment = {
219+
getWorker(_workerId, label) {
220+
if (label === 'json') {
221+
return new JSONWorker();
222+
}
223+
if (label === 'css' || label === 'scss' || label === 'less') {
224+
return new CSSWorker();
225+
}
226+
if (label === 'html' || label === 'handlebars' || label === 'razor') {
227+
return new HTMLWorker();
228+
}
229+
if (label === 'typescript' || label === 'javascript') {
230+
return new TSWorker();
231+
}
232+
return new EditorWorker();
233+
}
234+
};
235+
236+
globalThis.monacoEditors = {};
237+
// @endinline
238+
#endif
239+
// clang-format on
240+
241+
Nui::ElementRenderer Editor::operator()()
242+
{
243+
using namespace Nui::Elements;
244+
using namespace Nui::Attributes;
245+
using Nui::Elements::div;
246+
247+
auto createEditor = [](Nui::val element) {
248+
Nui::val options = Nui::val::object();
249+
options.set("value", "{\"a\": 1}");
250+
options.set("language", "javascript");
251+
options.set("automaticLayout", true);
252+
options.set("theme", "vs-dark");
253+
Nui::val::global("monacoEditors")
254+
.set("main-editor", Nui::val::global("monaco")["editor"].call<Nui::val>("create", element, options));
255+
};
256+
257+
return div{
258+
reference.onMaterialize([&createEditor](Nui::val element) {
259+
createEditor(element);
260+
}),
261+
}();
262+
}
263+
```
264+
265+
## Use the Editor
266+
frontend/main_page.hpp:
267+
```cpp
268+
#pragma once
269+
270+
#include <nui/frontend/element_renderer.hpp>
271+
272+
#include <memory>
273+
274+
class MainPage
275+
{
276+
public:
277+
MainPage();
278+
~MainPage();
279+
MainPage(MainPage const&) = delete;
280+
MainPage(MainPage&&);
281+
MainPage& operator=(MainPage const&) = delete;
282+
MainPage& operator=(MainPage&&);
283+
284+
Nui::ElementRenderer operator()();
285+
286+
private:
287+
struct Implementation;
288+
std::unique_ptr<Implementation> impl_;
289+
};
290+
```
291+
292+
frontend/main_page.cpp:
293+
```cpp
294+
#include <frontend/main_page.hpp>
295+
#include <frontend/editor.hpp>
296+
297+
#include <nui/frontend/elements.hpp>
298+
299+
struct MainPage::Implementation
300+
{
301+
Editor editor{};
302+
};
303+
304+
MainPage::MainPage()
305+
: impl_{std::make_unique<Implementation>()}
306+
{}
307+
MainPage::~MainPage() = default;
308+
MainPage::MainPage(MainPage&&) = default;
309+
MainPage& MainPage::operator=(MainPage&&) = default;
310+
311+
Nui::ElementRenderer MainPage::operator()()
312+
{
313+
using namespace Nui;
314+
using namespace Nui::Elements;
315+
using Nui::Elements::div; // because of the global div.
316+
317+
return body{}(impl_->editor());
318+
}
319+
```

0 commit comments

Comments
 (0)