HelpWindow.cpp 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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 void update() override { }
  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(const JsonObject& object)
  35. {
  36. m_keys.clear();
  37. object.for_each_member([this](auto& name, auto&) {
  38. m_keys.append(name);
  39. });
  40. did_update();
  41. }
  42. private:
  43. HelpListModel()
  44. {
  45. }
  46. Vector<String> m_keys;
  47. };
  48. RefPtr<HelpWindow> HelpWindow::s_the { nullptr };
  49. HelpWindow::HelpWindow(GUI::Window* parent)
  50. : GUI::Window(parent)
  51. {
  52. resize(530, 365);
  53. set_title("Spreadsheet Functions Help");
  54. set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-help.png"));
  55. auto& widget = set_main_widget<GUI::Widget>();
  56. widget.set_layout<GUI::VerticalBoxLayout>();
  57. widget.set_fill_with_background_color(true);
  58. auto& splitter = widget.add<GUI::HorizontalSplitter>();
  59. auto& left_frame = splitter.add<GUI::Frame>();
  60. left_frame.set_layout<GUI::VerticalBoxLayout>();
  61. left_frame.set_fixed_width(100);
  62. m_listview = left_frame.add<GUI::ListView>();
  63. m_listview->set_activates_on_selection(true);
  64. m_listview->set_model(HelpListModel::create());
  65. m_webview = splitter.add<Web::OutOfProcessWebView>();
  66. m_webview->on_link_click = [this](auto& url, auto&, auto&&) {
  67. VERIFY(url.protocol() == "spreadsheet");
  68. if (url.host() == "example") {
  69. auto entry = LexicalPath(url.path()).basename();
  70. auto doc_option = m_docs.get(entry);
  71. if (!doc_option.is_object()) {
  72. GUI::MessageBox::show_error(this, String::formatted("No documentation entry found for '{}'", url.path()));
  73. return;
  74. }
  75. auto& doc = doc_option.as_object();
  76. const auto& name = url.fragment();
  77. auto example_data_value = doc.get_or("example_data", JsonObject {});
  78. if (!example_data_value.is_object()) {
  79. GUI::MessageBox::show_error(this, String::formatted("No example data found for '{}'", url.path()));
  80. return;
  81. }
  82. auto& example_data = example_data_value.as_object();
  83. auto value = example_data.get(name);
  84. if (!value.is_object()) {
  85. GUI::MessageBox::show_error(this, String::formatted("Example '{}' not found for '{}'", name, url.path()));
  86. return;
  87. }
  88. auto window = GUI::Window::construct(this);
  89. window->resize(size());
  90. window->set_icon(icon());
  91. window->set_title(String::formatted("Spreadsheet Help - Example {} for {}", name, entry));
  92. window->on_close = [window = window.ptr()] { window->remove_from_parent(); };
  93. auto& widget = window->set_main_widget<SpreadsheetWidget>(NonnullRefPtrVector<Sheet> {}, false);
  94. auto sheet = Sheet::from_json(value.as_object(), widget.workbook());
  95. if (!sheet) {
  96. GUI::MessageBox::show_error(this, String::formatted("Corrupted example '{}' in '{}'", name, url.path()));
  97. return;
  98. }
  99. widget.add_sheet(sheet.release_nonnull());
  100. window->show();
  101. } else if (url.host() == "doc") {
  102. auto entry = LexicalPath(url.path()).basename();
  103. m_webview->load(URL::create_with_data("text/html", render(entry)));
  104. } else {
  105. dbgln("Invalid spreadsheet action domain '{}'", url.host());
  106. }
  107. };
  108. m_listview->on_activation = [this](auto& index) {
  109. if (!m_webview)
  110. return;
  111. auto key = static_cast<HelpListModel*>(m_listview->model())->key(index);
  112. m_webview->load(URL::create_with_data("text/html", render(key)));
  113. };
  114. }
  115. String HelpWindow::render(const StringView& key)
  116. {
  117. auto doc_option = m_docs.get(key);
  118. VERIFY(doc_option.is_object());
  119. auto& doc = doc_option.as_object();
  120. auto name = doc.get("name").to_string();
  121. auto argc = doc.get("argc").to_u32(0);
  122. auto argnames_value = doc.get("argnames");
  123. VERIFY(argnames_value.is_array());
  124. auto& argnames = argnames_value.as_array();
  125. auto docstring = doc.get("doc").to_string();
  126. auto examples_value = doc.get_or("examples", JsonObject {});
  127. VERIFY(examples_value.is_object());
  128. auto& examples = examples_value.as_object();
  129. StringBuilder markdown_builder;
  130. markdown_builder.append("# NAME\n`");
  131. markdown_builder.append(name);
  132. markdown_builder.append("`\n\n");
  133. markdown_builder.append("# ARGUMENTS\n");
  134. if (argc > 0)
  135. markdown_builder.appendff("{} required argument(s):\n", argc);
  136. else
  137. markdown_builder.appendf("No required arguments.\n");
  138. for (size_t i = 0; i < argc; ++i)
  139. markdown_builder.appendff("- `{}`\n", argnames.at(i).to_string());
  140. if (argc > 0)
  141. markdown_builder.append("\n");
  142. if ((size_t)argnames.size() > argc) {
  143. auto opt_count = argnames.size() - argc;
  144. markdown_builder.appendff("{} optional argument(s):\n", opt_count);
  145. for (size_t i = argc; i < (size_t)argnames.size(); ++i)
  146. markdown_builder.appendff("- `{}`\n", argnames.at(i).to_string());
  147. markdown_builder.append("\n");
  148. }
  149. markdown_builder.append("# DESCRIPTION\n");
  150. markdown_builder.append(docstring);
  151. markdown_builder.append("\n\n");
  152. if (!examples.is_empty()) {
  153. markdown_builder.append("# EXAMPLES\n");
  154. examples.for_each_member([&](auto& text, auto& description_value) {
  155. dbgln("- {}\n\n```js\n{}\n```\n", description_value.to_string(), text);
  156. markdown_builder.appendff("- {}\n\n```js\n{}\n```\n", description_value.to_string(), text);
  157. });
  158. }
  159. auto document = Markdown::Document::parse(markdown_builder.string_view());
  160. return document->render_to_html();
  161. }
  162. void HelpWindow::set_docs(JsonObject&& docs)
  163. {
  164. m_docs = move(docs);
  165. static_cast<HelpListModel*>(m_listview->model())->set_from(m_docs);
  166. m_listview->update();
  167. }
  168. HelpWindow::~HelpWindow()
  169. {
  170. }
  171. }