Monaco Editor
The monaco editor is one of the libraries which requires runtime imports. This requires the application to serve the index.html file through a custom scheme and also serve the required files.
Install
Add the monaco editor to your project by using the following command:
npx add-dependencies monaco-editor
Copy Source Files to Bin
The monaco editor requires some files to be present "somewhere on disk". In this case next to the application in "dynamic_sources". To achieve this, we have to add the following to the root CMakeLists.txt:
if (EMSCRIPTEN)
# ...
else()
# ...
# Copy bin files to destination.
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_BINARY_DIR}/module_${PROJECT_NAME}/bin" "$<TARGET_FILE_DIR:${PROJECT_NAME}>/dynamic_sources"
# The example code uses the index.html file that is bundled.
# Remove this if you want to also load your index from disk.
COMMAND ${CMAKE_COMMAND} -E rm "$<TARGET_FILE_DIR:${PROJECT_NAME}>/dynamic_sources/index.html"
COMMAND ${CMAKE_COMMAND} -E rm "$<TARGET_FILE_DIR:${PROJECT_NAME}>/dynamic_sources/index.js"
VERBATIM
)
endif()
Serve Application Through Custom Scheme
To serve the application through a custom scheme, you have to register the scheme.
backend/main.hpp:
#pragma once
#include <memory>
#include <filesystem>
class Main
{
public:
Main(std::filesystem::path const& programDirectory);
~Main();
Main(Main const&) = delete;
Main(Main&&);
Main& operator=(Main const&) = delete;
Main& operator=(Main&&)
void run();
private:
struct Implementation;
std::unique_ptr<Implementation> impl_;
};
backend/main.cpp:
#include <backend/main.hpp>
// This file is generated by nui.
#include <index.hpp>
#include <nui/core.hpp>
#include <nui/window.hpp>
#include <roar/mime_type.hpp>
#include <string>
#include <sstream>
#include <fstream>
using namespace std::string_literals;
struct Main::Implementation
{
Nui::Window window;
// This function is called when a "myScheme://" url is requested.
auto onSchemeRequest(std::filesystem::path const& programDirectory, Nui::CustomSchemeRequest const& request)
{
using namespace Nui;
const auto makeCorsHeader = [](std::string const& contentType) {
return std::unordered_multimap<std::string, std::string>{
{"Content-Type"s, contentType},
// Do not forget to allow CORS
{"Access-Control-Allow-Origin"s, "*"s},
};
};
const auto makeCorsResponse =
[&makeCorsHeader](
int statusCode, std::string const& reasonPhrase, std::string const& contentType, std::string body) {
return CustomSchemeResponse{
.statusCode = statusCode,
.reasonPhrase = reasonPhrase,
.headers = makeCorsHeader(contentType),
.body = std::move(body),
};
};
const auto url = request.parseUrl();
const auto pathString = url->pathAsString();
if (!url)
return makeCorsResponse(400, "Bad Request", "text/plain"s, "Bad Request");
// return index from memory (not necessary, can also load from disk, if you want).
if (pathString == "/index.html")
return makeCorsResponse(200, "OK", "text/html"s, index());
// Point path to dynamic_sources folder.
const auto file = programDirectory / "dynamic_sources" / std::filesystem::relative(pathString, "/");
// Check if file exists and return 404 if not
if (!std::filesystem::exists(file))
return makeCorsResponse(404, "Not Found", "text/plain"s, "Not Found");
// Read file
std::ifstream reader{file, std::ios::binary};
if (!reader)
{
return makeCorsResponse(
500, "Internal Server Error", "text/plain"s, "Internal Server Error - Could not open file.");
}
std::ostringstream payload;
payload << reader.rdbuf();
const std::string content = std::move(payload).str();
// Get mime type
const std::string mime = Roar::extensionToMime(file.extension().string()).value_or("application/octet-stream");
// Return file
return makeCorsResponse(content.empty() ? 204 : 200, "OK", mime, std::move(content));
}
auto createSchemeHandler(std::filesystem::path const& programDirectory)
{
return Nui::CustomScheme{
.scheme = "myScheme"s,
.allowedOrigins = {"*"s},
.onRequest = std::bind(&Implementation::onSchemeRequest, this, programDirectory, std::placeholders::_1),
.treatAsSecure = true,
.hasAuthorityComponent = true,
};
}
Implementation(std::filesystem::path const& programDirectory)
: window{Nui::WindowOptions{
.title = "Nui",
.customSchemes = {createSchemeHandler(programDirectory)},
}}
{
window.setSize(1650, 960, Nui::WebViewHint::WEBVIEW_HINT_NONE);
}
};
Main::Main(std::filesystem::path const& programDirectory)
: impl_{std::make_unique<Implementation>(programDirectory)}
{}
~Main::Main() = default;
Main::Main(Main&&) = default;
Main& Main::operator=(Main&&) = default;
void Main::run()
{
// app.example is intentional to circumvent DNS timeout on windows:
impl_->window.navigate("myScheme://app.example/index.html");
impl_->window.run();
}
int main(int, char** argv)
{
Main main{std::filesystem::path{argv[0]}.parent_path()};
main.run();
return 0;
}
Create an Editor Class
editor.hpp:
#pragma once
#include <nui/frontend/element_renderer.hpp>
class Editor
{
public:
Nui::ElementRenderer operator()();
};
The inline part can also be placed in any javascript file. You will have better language support from your IDE if you do. This just packs everything close together. editor.cpp:
#include <editor.hpp>
#include <nui/frontend/elements.hpp>
#include <nui/frontend/attributes.hpp>
// clang-format off
#ifdef NUI_INLINE
// @inline(js, monaco-editor)
js_import JSONWorker from 'url:monaco-editor/esm/vs/language/json/json.worker.js';
js_import CSSWorker from 'url:monaco-editor/esm/vs/language/css/css.worker.js';
js_import HTMLWorker from 'url:monaco-editor/esm/vs/language/html/html.worker.js';
js_import TSWorker from 'url:monaco-editor/esm/vs/language/typescript/ts.worker.js';
js_import EditorWorker from 'url:monaco-editor/esm/vs/editor/editor.worker.js';
js_import * as monaco from 'monaco-editor';
globalThis.monaco = monaco;
globalThis.MonacoEnvironment = {
getWorker(_workerId, label) {
if (label === 'json') {
return new JSONWorker();
}
if (label === 'css' || label === 'scss' || label === 'less') {
return new CSSWorker();
}
if (label === 'html' || label === 'handlebars' || label === 'razor') {
return new HTMLWorker();
}
if (label === 'typescript' || label === 'javascript') {
return new TSWorker();
}
return new EditorWorker();
}
};
globalThis.monacoEditors = {};
// @endinline
#endif
// clang-format on
Nui::ElementRenderer Editor::operator()()
{
using namespace Nui::Elements;
using namespace Nui::Attributes;
using Nui::Elements::div;
auto createEditor = [](Nui::val element) {
Nui::val options = Nui::val::object();
options.set("value", "{\"a\": 1}");
options.set("language", "javascript");
options.set("automaticLayout", true);
options.set("theme", "vs-dark");
Nui::val::global("monacoEditors")
.set("main-editor", Nui::val::global("monaco")["editor"].call<Nui::val>("create", element, options));
};
return div{
reference.onMaterialize([&createEditor](Nui::val element) {
createEditor(element);
}),
}();
}
Use the Editor
frontend/main_page.hpp:
#pragma once
#include <nui/frontend/element_renderer.hpp>
#include <memory>
class MainPage
{
public:
MainPage();
~MainPage();
MainPage(MainPage const&) = delete;
MainPage(MainPage&&);
MainPage& operator=(MainPage const&) = delete;
MainPage& operator=(MainPage&&);
Nui::ElementRenderer operator()();
private:
struct Implementation;
std::unique_ptr<Implementation> impl_;
};
frontend/main_page.cpp:
#include <frontend/main_page.hpp>
#include <frontend/editor.hpp>
#include <nui/frontend/elements.hpp>
struct MainPage::Implementation
{
Editor editor{};
};
MainPage::MainPage()
: impl_{std::make_unique<Implementation>()}
{}
MainPage::~MainPage() = default;
MainPage::MainPage(MainPage&&) = default;
MainPage& MainPage::operator=(MainPage&&) = default;
Nui::ElementRenderer MainPage::operator()()
{
using namespace Nui;
using namespace Nui::Elements;
using Nui::Elements::div; // because of the global div.
return body{}(impl_->editor());
}