HelpWindow.cpp 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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. set_window_mode(GUI::WindowMode::Modeless);
  57. auto widget = set_main_widget<GUI::Widget>().release_value_but_fixme_should_propagate_errors();
  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. // FIXME: Get rid of the magic number, dynamically calculate initial size based on left frame contents
  64. left_frame.set_preferred_width(100);
  65. m_listview = left_frame.add<GUI::ListView>();
  66. m_listview->set_activates_on_selection(true);
  67. m_listview->set_model(HelpListModel::create());
  68. m_webview = splitter.add<WebView::OutOfProcessWebView>();
  69. m_webview->on_link_click = [this](auto& url, auto&, auto&&) {
  70. VERIFY(url.scheme() == "spreadsheet");
  71. if (url.host().template has<String>() && url.host().template get<String>() == "example"sv) {
  72. auto example_path = url.serialize_path();
  73. auto entry = LexicalPath::basename(example_path);
  74. auto doc_option = m_docs.get_object(entry);
  75. if (!doc_option.has_value()) {
  76. GUI::MessageBox::show_error(this, DeprecatedString::formatted("No documentation entry found for '{}'", example_path));
  77. return;
  78. }
  79. auto& doc = doc_option.value();
  80. auto name = url.fragment();
  81. auto maybe_example_data = doc.get_object("example_data"sv);
  82. if (!maybe_example_data.has_value()) {
  83. GUI::MessageBox::show_error(this, DeprecatedString::formatted("No example data found for '{}'", example_path));
  84. return;
  85. }
  86. auto& example_data = maybe_example_data.value();
  87. if (!example_data.has_object(name)) {
  88. GUI::MessageBox::show_error(this, DeprecatedString::formatted("Example '{}' not found for '{}'", name, example_path));
  89. return;
  90. }
  91. auto& value = example_data.get_object(name).value();
  92. auto window = GUI::Window::construct(this);
  93. window->resize(size());
  94. window->set_icon(icon());
  95. window->set_title(DeprecatedString::formatted("Spreadsheet Help - Example {} for {}", name, entry));
  96. window->on_close = [window = window.ptr()] { window->remove_from_parent(); };
  97. auto widget = window->set_main_widget<SpreadsheetWidget>(window, Vector<NonnullRefPtr<Sheet>> {}, false).release_value_but_fixme_should_propagate_errors();
  98. auto sheet = Sheet::from_json(value, widget->workbook());
  99. if (!sheet) {
  100. GUI::MessageBox::show_error(this, DeprecatedString::formatted("Corrupted example '{}' in '{}'", name, example_path));
  101. return;
  102. }
  103. widget->add_sheet(sheet.release_nonnull());
  104. window->show();
  105. } else if (url.host() == "doc"_short_string) {
  106. auto entry = LexicalPath::basename(url.serialize_path());
  107. m_webview->load(URL::create_with_data("text/html"sv, render(entry)));
  108. } else {
  109. dbgln("Invalid spreadsheet action domain '{}'", url.serialized_host().release_value_but_fixme_should_propagate_errors());
  110. }
  111. };
  112. m_listview->on_activation = [this](auto& index) {
  113. if (!m_webview)
  114. return;
  115. auto key = static_cast<HelpListModel*>(m_listview->model())->key(index);
  116. m_webview->load(URL::create_with_data("text/html"sv, render(key)));
  117. };
  118. }
  119. DeprecatedString HelpWindow::render(StringView key)
  120. {
  121. VERIFY(m_docs.has_object(key));
  122. auto& doc = m_docs.get_object(key).value();
  123. auto name = doc.get_deprecated_string("name"sv).value_or({});
  124. auto argc = doc.get_u32("argc"sv).value_or(0);
  125. VERIFY(doc.has_array("argnames"sv));
  126. auto& argnames = doc.get_array("argnames"sv).value();
  127. auto docstring = doc.get_deprecated_string("doc"sv).value_or({});
  128. StringBuilder markdown_builder;
  129. markdown_builder.append("# NAME\n`"sv);
  130. markdown_builder.append(name);
  131. markdown_builder.append("`\n\n"sv);
  132. markdown_builder.append("# ARGUMENTS\n"sv);
  133. if (argc > 0)
  134. markdown_builder.appendff("{} required argument(s):\n", argc);
  135. else
  136. markdown_builder.append("No required arguments.\n"sv);
  137. for (size_t i = 0; i < argc; ++i)
  138. markdown_builder.appendff("- `{}`\n", argnames.at(i).to_deprecated_string());
  139. if (argc > 0)
  140. markdown_builder.append("\n"sv);
  141. if ((size_t)argnames.size() > argc) {
  142. auto opt_count = argnames.size() - argc;
  143. markdown_builder.appendff("{} optional argument(s):\n", opt_count);
  144. for (size_t i = argc; i < (size_t)argnames.size(); ++i)
  145. markdown_builder.appendff("- `{}`\n", argnames.at(i).to_deprecated_string());
  146. markdown_builder.append("\n"sv);
  147. }
  148. markdown_builder.append("# DESCRIPTION\n"sv);
  149. markdown_builder.append(docstring);
  150. markdown_builder.append("\n\n"sv);
  151. if (doc.has("examples"sv)) {
  152. auto examples = doc.get_object("examples"sv);
  153. VERIFY(examples.has_value());
  154. markdown_builder.append("# EXAMPLES\n"sv);
  155. examples->for_each_member([&](auto& text, auto& description_value) {
  156. dbgln("```js\n{}\n```\n\n- {}\n", text, description_value.to_deprecated_string());
  157. markdown_builder.appendff("```js\n{}\n```\n\n- {}\n", text, description_value.to_deprecated_string());
  158. });
  159. }
  160. auto document = Markdown::Document::parse(markdown_builder.string_view());
  161. return document->render_to_html();
  162. }
  163. void HelpWindow::set_docs(JsonObject&& docs)
  164. {
  165. m_docs = move(docs);
  166. static_cast<HelpListModel*>(m_listview->model())->set_from(m_docs);
  167. m_listview->update();
  168. }
  169. }