HelpWindow.cpp 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  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. DeprecatedString 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<DeprecatedString> 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::load_from_file("/res/icons/16x16/app-help.png"sv).release_value_but_fixme_should_propagate_errors());
  56. auto widget = set_main_widget<GUI::Widget>().release_value_but_fixme_should_propagate_errors();
  57. widget->set_layout<GUI::VerticalBoxLayout>();
  58. widget->set_fill_with_background_color(true);
  59. auto& splitter = widget->add<GUI::HorizontalSplitter>();
  60. auto& left_frame = splitter.add<GUI::Frame>();
  61. left_frame.set_layout<GUI::VerticalBoxLayout>();
  62. // FIXME: Get rid of the magic number, dynamically calculate initial size based on left frame contents
  63. left_frame.set_preferred_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.scheme() == "spreadsheet");
  70. if (url.host().template has<String>() && url.host().template get<String>() == "example"sv) {
  71. auto example_path = url.serialize_path();
  72. auto entry = LexicalPath::basename(example_path);
  73. auto doc_option = m_docs.get_object(entry);
  74. if (!doc_option.has_value()) {
  75. GUI::MessageBox::show_error(this, DeprecatedString::formatted("No documentation entry found for '{}'", example_path));
  76. return;
  77. }
  78. auto& doc = doc_option.value();
  79. auto name = url.fragment();
  80. auto maybe_example_data = doc.get_object("example_data"sv);
  81. if (!maybe_example_data.has_value()) {
  82. GUI::MessageBox::show_error(this, DeprecatedString::formatted("No example data found for '{}'", example_path));
  83. return;
  84. }
  85. auto& example_data = maybe_example_data.value();
  86. if (!example_data.has_object(name)) {
  87. GUI::MessageBox::show_error(this, DeprecatedString::formatted("Example '{}' not found for '{}'", name, example_path));
  88. return;
  89. }
  90. auto& value = example_data.get_object(name).value();
  91. auto window = GUI::Window::construct(this);
  92. window->resize(size());
  93. window->set_icon(icon());
  94. window->set_title(DeprecatedString::formatted("Spreadsheet Help - Example {} for {}", name, entry));
  95. window->on_close = [window = window.ptr()] { window->remove_from_parent(); };
  96. auto widget = window->set_main_widget<SpreadsheetWidget>(window, Vector<NonnullRefPtr<Sheet>> {}, false).release_value_but_fixme_should_propagate_errors();
  97. auto sheet = Sheet::from_json(value, widget->workbook());
  98. if (!sheet) {
  99. GUI::MessageBox::show_error(this, DeprecatedString::formatted("Corrupted example '{}' in '{}'", name, example_path));
  100. return;
  101. }
  102. widget->add_sheet(sheet.release_nonnull());
  103. window->show();
  104. } else if (url.host() == String::from_utf8_short_string("doc"sv)) {
  105. auto entry = LexicalPath::basename(url.serialize_path());
  106. m_webview->load(URL::create_with_data("text/html"sv, render(entry)));
  107. } else {
  108. dbgln("Invalid spreadsheet action domain '{}'", url.serialized_host().release_value_but_fixme_should_propagate_errors());
  109. }
  110. };
  111. m_listview->on_activation = [this](auto& index) {
  112. if (!m_webview)
  113. return;
  114. auto key = static_cast<HelpListModel*>(m_listview->model())->key(index);
  115. m_webview->load(URL::create_with_data("text/html"sv, render(key)));
  116. };
  117. }
  118. DeprecatedString HelpWindow::render(StringView key)
  119. {
  120. VERIFY(m_docs.has_object(key));
  121. auto& doc = m_docs.get_object(key).value();
  122. auto name = doc.get_deprecated_string("name"sv).value_or({});
  123. auto argc = doc.get_u32("argc"sv).value_or(0);
  124. VERIFY(doc.has_array("argnames"sv));
  125. auto& argnames = doc.get_array("argnames"sv).value();
  126. auto docstring = doc.get_deprecated_string("doc"sv).value_or({});
  127. StringBuilder markdown_builder;
  128. markdown_builder.append("# NAME\n`"sv);
  129. markdown_builder.append(name);
  130. markdown_builder.append("`\n\n"sv);
  131. markdown_builder.append("# ARGUMENTS\n"sv);
  132. if (argc > 0)
  133. markdown_builder.appendff("{} required argument(s):\n", argc);
  134. else
  135. markdown_builder.append("No required arguments.\n"sv);
  136. for (size_t i = 0; i < argc; ++i)
  137. markdown_builder.appendff("- `{}`\n", argnames.at(i).to_deprecated_string());
  138. if (argc > 0)
  139. markdown_builder.append("\n"sv);
  140. if ((size_t)argnames.size() > argc) {
  141. auto opt_count = argnames.size() - argc;
  142. markdown_builder.appendff("{} optional argument(s):\n", opt_count);
  143. for (size_t i = argc; i < (size_t)argnames.size(); ++i)
  144. markdown_builder.appendff("- `{}`\n", argnames.at(i).to_deprecated_string());
  145. markdown_builder.append("\n"sv);
  146. }
  147. markdown_builder.append("# DESCRIPTION\n"sv);
  148. markdown_builder.append(docstring);
  149. markdown_builder.append("\n\n"sv);
  150. if (doc.has("examples"sv)) {
  151. auto examples = doc.get_object("examples"sv);
  152. VERIFY(examples.has_value());
  153. markdown_builder.append("# EXAMPLES\n"sv);
  154. examples->for_each_member([&](auto& text, auto& description_value) {
  155. dbgln("```js\n{}\n```\n\n- {}\n", text, description_value.to_deprecated_string());
  156. markdown_builder.appendff("```js\n{}\n```\n\n- {}\n", text, description_value.to_deprecated_string());
  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. }