HelpWindow.cpp 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. /*
  2. * Copyright (c) 2020, the SerenityOS developers.
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "HelpWindow.h"
  7. #include "SpreadsheetWidget.h"
  8. #include <AK/LexicalPath.h>
  9. #include <LibGUI/BoxLayout.h>
  10. #include <LibGUI/Frame.h>
  11. #include <LibGUI/ListView.h>
  12. #include <LibGUI/MessageBox.h>
  13. #include <LibGUI/Model.h>
  14. #include <LibGUI/Splitter.h>
  15. #include <LibMarkdown/Document.h>
  16. #include <LibWeb/Layout/Node.h>
  17. #include <LibWeb/OutOfProcessWebView.h>
  18. namespace Spreadsheet {
  19. class HelpListModel final : public GUI::Model {
  20. public:
  21. static NonnullRefPtr<HelpListModel> create() { return adopt_ref(*new HelpListModel); }
  22. virtual ~HelpListModel() override { }
  23. virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return m_keys.size(); }
  24. virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return 1; }
  25. virtual GUI::Variant data(const GUI::ModelIndex& index, GUI::ModelRole role = GUI::ModelRole::Display) const override
  26. {
  27. if (role == GUI::ModelRole::Display) {
  28. return key(index);
  29. }
  30. return {};
  31. }
  32. String key(const GUI::ModelIndex& index) const { return m_keys[index.row()]; }
  33. void set_from(const JsonObject& object)
  34. {
  35. m_keys.clear();
  36. object.for_each_member([this](auto& name, auto&) {
  37. m_keys.append(name);
  38. });
  39. invalidate();
  40. }
  41. private:
  42. HelpListModel()
  43. {
  44. }
  45. Vector<String> m_keys;
  46. };
  47. RefPtr<HelpWindow> HelpWindow::s_the { nullptr };
  48. HelpWindow::HelpWindow(GUI::Window* parent)
  49. : GUI::Window(parent)
  50. {
  51. resize(530, 365);
  52. set_title("Spreadsheet Functions Help");
  53. set_icon(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/app-help.png"));
  54. auto& widget = set_main_widget<GUI::Widget>();
  55. widget.set_layout<GUI::VerticalBoxLayout>();
  56. widget.set_fill_with_background_color(true);
  57. auto& splitter = widget.add<GUI::HorizontalSplitter>();
  58. auto& left_frame = splitter.add<GUI::Frame>();
  59. left_frame.set_layout<GUI::VerticalBoxLayout>();
  60. left_frame.set_fixed_width(100);
  61. m_listview = left_frame.add<GUI::ListView>();
  62. m_listview->set_activates_on_selection(true);
  63. m_listview->set_model(HelpListModel::create());
  64. m_webview = splitter.add<Web::OutOfProcessWebView>();
  65. m_webview->on_link_click = [this](auto& url, auto&, auto&&) {
  66. VERIFY(url.protocol() == "spreadsheet");
  67. if (url.host() == "example") {
  68. auto entry = LexicalPath::basename(url.path());
  69. auto doc_option = m_docs.get(entry);
  70. if (!doc_option.is_object()) {
  71. GUI::MessageBox::show_error(this, String::formatted("No documentation entry found for '{}'", url.path()));
  72. return;
  73. }
  74. auto& doc = doc_option.as_object();
  75. const auto& name = url.fragment();
  76. auto* example_data_ptr = doc.get_ptr("example_data");
  77. if (!example_data_ptr || !example_data_ptr->is_object()) {
  78. GUI::MessageBox::show_error(this, String::formatted("No example data found for '{}'", url.path()));
  79. return;
  80. }
  81. auto& example_data = example_data_ptr->as_object();
  82. if (!example_data.has_object(name)) {
  83. GUI::MessageBox::show_error(this, String::formatted("Example '{}' not found for '{}'", name, url.path()));
  84. return;
  85. }
  86. auto& value = example_data.get(name);
  87. auto window = GUI::Window::construct(this);
  88. window->resize(size());
  89. window->set_icon(icon());
  90. window->set_title(String::formatted("Spreadsheet Help - Example {} for {}", name, entry));
  91. window->on_close = [window = window.ptr()] { window->remove_from_parent(); };
  92. auto& widget = window->set_main_widget<SpreadsheetWidget>(NonnullRefPtrVector<Sheet> {}, false);
  93. auto sheet = Sheet::from_json(value.as_object(), widget.workbook());
  94. if (!sheet) {
  95. GUI::MessageBox::show_error(this, String::formatted("Corrupted example '{}' in '{}'", name, url.path()));
  96. return;
  97. }
  98. widget.add_sheet(sheet.release_nonnull());
  99. window->show();
  100. } else if (url.host() == "doc") {
  101. auto entry = LexicalPath::basename(url.path());
  102. m_webview->load(URL::create_with_data("text/html", render(entry)));
  103. } else {
  104. dbgln("Invalid spreadsheet action domain '{}'", url.host());
  105. }
  106. };
  107. m_listview->on_activation = [this](auto& index) {
  108. if (!m_webview)
  109. return;
  110. auto key = static_cast<HelpListModel*>(m_listview->model())->key(index);
  111. m_webview->load(URL::create_with_data("text/html", render(key)));
  112. };
  113. }
  114. String HelpWindow::render(const StringView& key)
  115. {
  116. VERIFY(m_docs.has_object(key));
  117. auto& doc = m_docs.get(key).as_object();
  118. auto name = doc.get("name").to_string();
  119. auto argc = doc.get("argc").to_u32(0);
  120. VERIFY(doc.has_array("argnames"));
  121. auto& argnames = doc.get("argnames").as_array();
  122. auto docstring = doc.get("doc").to_string();
  123. StringBuilder markdown_builder;
  124. markdown_builder.append("# NAME\n`");
  125. markdown_builder.append(name);
  126. markdown_builder.append("`\n\n");
  127. markdown_builder.append("# ARGUMENTS\n");
  128. if (argc > 0)
  129. markdown_builder.appendff("{} required argument(s):\n", argc);
  130. else
  131. markdown_builder.append("No required arguments.\n");
  132. for (size_t i = 0; i < argc; ++i)
  133. markdown_builder.appendff("- `{}`\n", argnames.at(i).to_string());
  134. if (argc > 0)
  135. markdown_builder.append("\n");
  136. if ((size_t)argnames.size() > argc) {
  137. auto opt_count = argnames.size() - argc;
  138. markdown_builder.appendff("{} optional argument(s):\n", opt_count);
  139. for (size_t i = argc; i < (size_t)argnames.size(); ++i)
  140. markdown_builder.appendff("- `{}`\n", argnames.at(i).to_string());
  141. markdown_builder.append("\n");
  142. }
  143. markdown_builder.append("# DESCRIPTION\n");
  144. markdown_builder.append(docstring);
  145. markdown_builder.append("\n\n");
  146. if (doc.has("examples")) {
  147. auto& examples = doc.get("examples");
  148. VERIFY(examples.is_object());
  149. markdown_builder.append("# EXAMPLES\n");
  150. examples.as_object().for_each_member([&](auto& text, auto& description_value) {
  151. dbgln("- {}\n\n```js\n{}\n```\n", description_value.to_string(), text);
  152. markdown_builder.appendff("- {}\n\n```js\n{}\n```\n", description_value.to_string(), text);
  153. });
  154. }
  155. auto document = Markdown::Document::parse(markdown_builder.string_view());
  156. return document->render_to_html();
  157. }
  158. void HelpWindow::set_docs(JsonObject&& docs)
  159. {
  160. m_docs = move(docs);
  161. static_cast<HelpListModel*>(m_listview->model())->set_from(m_docs);
  162. m_listview->update();
  163. }
  164. HelpWindow::~HelpWindow()
  165. {
  166. }
  167. }