HelpWindow.cpp 7.0 KB

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