diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index b5c3447871..0d4e963b66 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -43,6 +43,7 @@ jobs: with: submodules: 'recursive' - run: sudo apt-get install -qq g++-8 cmake build-essential python-pip python-virtualenv nodejs tar gzip libpthread-stubs0-dev libc6-dbg gdb + - run: sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 90 - run: make install-test-dependencies - name: Run headless test uses: GabrielBB/xvfb-action@v1 diff --git a/.gitignore b/.gitignore index 0fb1134a47..1f6b392483 100644 --- a/.gitignore +++ b/.gitignore @@ -68,8 +68,8 @@ demos/NK/web/NK.js.mem doc/doxygen/ examples/*/* -!examples/*/*.cc -!examples/*/*.h +!examples/*/*.cpp +!examples/*/*.hpp !examples/*/*.html !examples/*/images !examples/*/Makefile @@ -86,4 +86,3 @@ third-party/doxygen/ third-party/emsdk/ build/ doc/_build/ - diff --git a/doc/dev/guide-to-testing.md b/doc/dev/guide-to-testing.md index 101274a307..6306e2a095 100644 --- a/doc/dev/guide-to-testing.md +++ b/doc/dev/guide-to-testing.md @@ -120,3 +120,43 @@ This allows for easier debugging of failed tests. Catch provides several different frameworks for constructing test cases which are detailed within [their documentation](https://github.com/philsquared/Catch/blob/master/docs/tutorial.md). + +## Running Tests with Docker +------------------------- + +A [devosoft/empirical](https://hub.docker.com/r/devosoft/empirical) Docker image has been set up +to make recreating a development environment on your machine easier. +The first step is to download Docker. + +To download and run the Docker image, enter the following commands in the Docker terminal +``` bash +docker pull devosoft/empirical:latest +docker run --name emp-tests -dit devosoft/empirical:latest /sbin/init +docker exec -it emp-tests bash -l +``` + +To exit Docker containter shell +``` bash +CTRL+D +``` + +Commands to stop and start the Docker container +``` bash +docker start emp-tests +docker stop emp-tests +``` + +If you get `error: cannot open display: 99` when running Mocha web tests, try +```bash +Xvfb :99 -ac -screen 0 1024x768x8 & +export DISPLAY=:99 +``` + +If you get an error prompting you to *check if server X is already running* after +entering `Xvfb :99 -ac -screen 0 1024x768x8 &`, try this to kill the process +``` bash +ps -ef | grep Xvfb +kill *pid* +``` + +**Note:** Instructions adapted from and diff --git a/doc/library/prefab/prefab.rst b/doc/library/prefab/prefab.rst new file mode 100644 index 0000000000..554da916c1 --- /dev/null +++ b/doc/library/prefab/prefab.rst @@ -0,0 +1,355 @@ +Prefabricated Web Tools (for use with Emscripten) +================================================= + +These prefabricated tools were created to help you quickly create interesting web applicications without being overwhelmed with the underlying HTML, CSS, and Bootstrap classes required. +These tools use Empirical's web tools to provide structure for the site, and many of the prefab tools inherit from web tools so you can add your own styling and stream them into other web components in a similar way. + +When using these prefab tools be sure to link to the Bootstrap library, jQuery, and the default style stylesheet for this class in the head section of your HTML file. +.. code-block:: html + + + + + + + + +You can view these tools in action `here `_. + +Card +~~~~ +The Card class allows you to define a Bootstrap style card into your project. +A card that is not collapsible will have its state set to :code:`STATIC`. +Cards are static by default. +A card can be collapsible if its state parameter it set to :code:`INIT_OPEN` or :code:`INIT_CLOSED`. +By default, if a card is collapsible, it will have toggle icons in the header, but this can be overridden by setting the :code:`showGlyphs` parameter to :code:`false`. + +Since this class inherits from :code:`web::Div`, you can set styling and attributes with :code:`SetCSS` and :code:`SetAttr` respectively. +You can also stream your Card into other web components with the :code:`<<` operator. + +Example: +******** +.. code-block:: c++ + + #include "web/web.h" + #include "prefab/Card.h" + + emp::web::Document doc("emp_base"); + + emp::prefab::Card my_card("STATIC"); + doc << my_card; + + my_card.AddHeaderContent("Title"); + my_card.AddBodyContent("Content for the card's body"); + // Web components can also be passed as parameters to AddHeaderContent and AddBodyContent + +**Note**: The toggle icons that are avalible for collapsible cards use the `FontAwesome`_ library. +You will need to add the CSS file for this library to the head of your HTML file: + +.. code-block:: html + + + +CodeBlock +~~~~~~~~~ +The CardBlock class provides an interface for the `HighlightJS Library`_ which allows you to display code on web pages with language specific highlighting. +You can find a list of `all languages`_ on their GitHub page. + +To use this class, you need to pass the code you want displayed and the programming language to the constructor. + +Since this class inherits from :code:`web::Element`, you can stream your CodeBlock into other web components with the :code:`<<` operator. + +Example: +******** +.. code-block:: c++ + + #include "web/web.h" + #include "prefab/CodeBlock.h" + + emp::web::Document doc("emp_base"); + + std::string code_str = + R"( + int num = 9; + std::cout << num << " is a square number" << std::endl; + )"; + emp::prefab::CodeBlock code_block(code_str, "c++"); + + doc << code_block; + +**Note**: You will also need to add the following code to the bottom of the body section of your HTML file: + +.. code-block:: html + + + + + + +.. _HighlightJS Library: https://highlightjs.org/ +.. _all languages: https://github.com/highlightjs/highlight.js/blob/master/SUPPORTED_LANGUAGES.md + +Collapse +~~~~~~~~ +The CollapseCouple maintains a group of targets and controllers. +When a controller is clicked on a web page, all the associated targets will change state (expand/collapse). + +By default, the target element will start off closed, but this can be set to open by passing :code:`true` for the :code:`expanded` parameter. + +Since the collapse controller and collapse target element will not necessarily directly neighbor eachother, call :code:`GetControllerDiv()` and :code:`GetTargetDiv()` to obtain a vector of all the asspociated controllers and targets, respectively. +To obtain just one controller or target, pass its index into a get div function call. + +Example: +******** +.. code-block:: cpp + + #include "web/web.h" + #include "web/Div.h" + #include "prefab/CommentBox.h" + + #include "prefab/Collapse.h" + + emp::web::Document doc("emp_base"); + + emp::prefab::CommentBox box1; + box1.AddContent("

Box 1

"); + emp::web::Div btn1; + btn1.SetAttr("class", "btn btn-info"); + btn1 << "Button 1: controls box 1"; + + emp::prefab::CollapseCoupling collapse1(btn1, box1, true); + + doc << collapse1.GetControllerDiv(0); + doc << collapse1.GetTargetDiv(0); + +CommentBox +~~~~~~~~~~ +A CommentBox is a simple grey comment bubble. +Content can be added to it using :code:`AddContent()`. +If there is data you only want to be visible on mobile devices, use :code:`AddMobileContent()`. + +Since this class inherits from :code:`web::Div`, you can set styling and attributes with :code:`SetCSS()` and :code:`SetAttr()` respectively. +You can also stream your CommentBox into other web components with the :code:`<<` operator. + +Example: +******** +.. code-block:: cpp + + #include "web/web.h" + #include "prefab/CommentBox.h" + + emp::web::Document doc("emp_base"); + + emp::prefab::CommentBox my_box; + doc << my_box; + + my_box.AddContent("

Content that shows on all screen sizes

"); + my_box.AddMobileContent("
Content that only shows on small screens"); + // Web components can also be passed as parameters to AddContent and AddMobileContent + +ConfigPanel +~~~~~~~~~~~ +The ConfigPanel allows developers to easily set up a user interface for their configuration options. +It allows web apps to be interactive and dynamic, allowing users to change configuration settings within the applicaiton and providing a better user experiance. + +Using the ConfigPanel class, a configuration panel is constructed when passed a Config file. +It uses other Prefabricated components to add styling and structure to the panel. +Use :code:`GetConfigPanelDiv()` to stream this component into another web component or document. + +It is important to note that ConfigPanel instances are destroyed when they go out of scope. +This causes the form to no longer respond to changes made by the user. +You will need to initialize an instance outside of :code:`main()` if you would like the user to be able to interact with the panel. + +Example: +******** +.. code-block:: cpp + + #include "web/web.h" + #include "prefab/ConfigPanel.h" + #include "config/ArgManager.h" + + #include "SampleConfig.h" // Config file + + emp::web::Document doc("emp_base"); + Config cfg; + + emp::prefab::ConfigPanel config_panel(cfg); + + // apply configuration query params and config files to Config + auto specs = emp::ArgManager::make_builtin_specs(&cfg); + emp::ArgManager am(emp::web::GetUrlParams(), specs); + // cfg.Read("config.cfg"); + am.UseCallbacks(); + if (am.HasUnused()) std::exit(EXIT_FAILURE); + + // setup configuration panel + config_panel.Setup(); + doc << config_panel.GetConfigPanelDiv(); + +FontAwesomeIcon +~~~~~~~~~~~~~~~ +`FontAwesome`_ is a free library of icons that can be used in web pages. + +To use this class: + +1. Find the icon you wish to use in the `FontAwesome library`_ +2. Pass :code:`"fa-" + *icon name*` as a parameter to the constructor. +3. Add the following CSS file to the head of your HTML document. + +.. code-block:: html + + + +Since this class inherits from :code:`web::Element`, you can set styling and attributes with :code:`SetCSS()` and :code:`SetAttr()` respectively. +You can also stream your FontAwesomeIcon into other web components with the :code:`<<` operator. + +Example: +******** +.. code-block:: cpp + + #include "web/web.h" + #include "prefab/FontAwesomeIcon.h" + + emp::web::Document doc("emp_base"); + + emp::prefab::FontAwesomeIcon my_icon("fa-paw"); + doc << my_icon; + + my_icon.AddClass("custom_class"); + +.. _FontAwesome: https://fontawesome.com/v4.7.0/ +.. _FontAwesome library: https://fontawesome.com/v4.7.0/icons/ + +LoadingIcon +~~~~~~~~~~~ +The LoadingIcon class is used to add an animated loading icon. +One possible use for this icon is to be displayed while the contents of a web page is loading. +The icon is provided by `FontAwesome`_, so you will need to add its CSS to your HTML file to use this class. + +.. code-block:: html + + + +Since this class inherits from :code:`web::Element`, you can set styling and attributes with :code:`SetCSS()` and :code:`SetAttr()` respectively. +You can also stream your LoadingIcon into other web components with the :code:`<<` operator. + +Example: +******** +.. code-block:: cpp + + #include "web/web.h" + #include "prefab/LoadingIcon.h" + + emp::web::Document doc("emp_base"); + + emp::prefab::LoadingIcon spinner; + doc << spinner; + +LoadingModal +~~~~~~~~~~~~ +The LoadingModal header file makes adding a loading modal to a web page easy. +It will appear while the content of the page is rendering and will disappear when the page has completed loading. + +This header file is slightly different from the other prefab web tools. +To place the loading modal on your web page, you must import the LoadingModal.js script into your HTML file right after the opening body tag. +To close the modal you must call the :code:`CloseLoadingModal()` function in your .cc file after loading the desired content into the doc. + +Example: +******** +.. code-block:: cpp + + // .cc file + #include "web/web.h" + #include "LoadingModal.h" + + emp::web::Document doc("emp_base"); + + // Add elements to the doc a normal + + emp::prefab::CloseLoadingModal(); + +.. code-block:: html + + + + + + + + + + + + + + + + + + + +Modal +~~~~~ +The Modal class can be used to create Bootstrap modals that pops up in the middle of the screen. + +Since this class inherits from :code:`web::Div`, you can stream your Modal into other web components with the :code:`<<` operator. +You can also set the background color of the Modal with :code:`SetBackground()` passing it a string with a color name or its hex code value. + +Example: +******** +.. code-block:: cpp + + #include "web/web.h" + #include "web/Button.h" + #include "prefab/Modal.h" + + emp::web::Document doc("emp_base"); + + emp::prefab::Modal modal; + doc << modal; + + modal.AddHeaderContent("

Modal Header Section

"); + modal.AddBodyContent("This is the content of the modal"); + + modal.AddFooterContent("Modal Footer Section"); + UI::Button close_btn([](){;}, "Close"); + close_btn.SetAttr("class", "btn btn-secondary"); + modal.AddFooterContent(close_btn); + modal.AddButton(close_btn); + + modal.AddClosingX(); + + UI::Button modal_btn([](){;}, "Show Modal"); + doc << modal_btn; + modal_btn.SetAttr("class", "btn btn-info"); + modal.AddButton(modal_btn); + +ToggleSwitch +~~~~~~~~~~~~ +The ToggleSwitch class wraps checkbox input with Bootstrap custom swtich classes. +If you need to add a CSS class to the Input, do it after the creating the ToggleSwitch instance with :code:`AddClass()`. + + +Since this class inherits from :code:`web::Element`, you can set styling and attributes with :code:`SetCSS()` and :code:`SetAttr()` respectively. +You can also stream your ToggleSwitch into other web components with the :code:`<<` operator. + +Example: +******** +.. code-block:: cpp + + #include "web/web.h" + #include "prefab/ToggleSwitch.h" + + emp::prefab::ToggleSwitch on_switch([](std::string val){}, "Switch Defult On", true, "user_defined_switch_id"); + doc << on_switch; + + doc << "
"; + + emp::prefab::ToggleSwitch off_switch([](std::string val){}, NULL, false); + doc << off_switch; + off_switch.AddLabel("Switch Defult Off"); + +Add the link to Bootstrap in the head of your HTML file: +.. code-block:: html + + diff --git a/examples/Makefile b/examples/Makefile index 0e276fd4fe..007415d975 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -14,6 +14,7 @@ default: $(MAKE) -C io/ $(MAKE) -C math/ $(MAKE) -C meta/ + $(MAKE) -C prefab/ $(MAKE) -C ProjectTemplate/ $(MAKE) -C testing/ $(MAKE) -C tools/ @@ -35,6 +36,7 @@ clean: $(MAKE) clean -C io/ $(MAKE) clean -C math/ $(MAKE) clean -C meta/ + $(MAKE) clean -C prefab/ $(MAKE) clean -C ProjectTemplate/ $(MAKE) clean -C testing/ $(MAKE) clean -C tools/ @@ -56,12 +58,15 @@ debug: $(MAKE) debug -C io/ $(MAKE) debug -C math/ $(MAKE) debug -C meta/ + $(MAKE) debug -C prefab/ $(MAKE) debug -C ProjectTemplate/ $(MAKE) debug -C testing/ $(MAKE) debug -C tools/ $(MAKE) debug -C web/ web-test: + $(MAKE) debug -C prefab/ + $(MAKE) -C prefab/ $(MAKE) debug -C web/ $(MAKE) -C web/ diff --git a/examples/prefab/Card.cpp b/examples/prefab/Card.cpp new file mode 100644 index 0000000000..fa0eaca22b --- /dev/null +++ b/examples/prefab/Card.cpp @@ -0,0 +1,34 @@ +// This file is part of Empirical, https://github.com/devosoft/Empirical +// Copyright (C) Michigan State University, 2020. +// Released under the MIT Software license; see doc/LICENSE + +#include + +#include "emp/web/web.hpp" +#include "emp/prefab/Card.hpp" + +namespace UI = emp::web; + +UI::Document doc("emp_base"); + +int main() +{ + // Plain Card + emp::prefab::Card pCard("STATIC"); + pCard.AddHeaderContent("Plain card"); + pCard.AddBodyContent("Plain body content"); + doc << pCard; + + // Collapsible Card, default open + emp::prefab::Card openCard("INIT_OPEN", true); + // Header content with bootstrap link properties + openCard.AddHeaderContent("Open card", true); + openCard.AddBodyContent("Open body content
Glyphs
Linked title"); + doc << openCard; + + // Collapsible Card, default closed + emp::prefab::Card closedCard("INIT_CLOSED", false); + closedCard.AddHeaderContent("Closed card"); + closedCard.AddBodyContent("Closed body content
No Glyphs
Plain title"); + doc << closedCard; +} diff --git a/examples/prefab/Card.html b/examples/prefab/Card.html new file mode 100644 index 0000000000..d370e966cc --- /dev/null +++ b/examples/prefab/Card.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + +Card Examples + + + +
+

Card Examples

+
+ +
+
+
+ + + + + + + + diff --git a/examples/prefab/CodeBlock.cpp b/examples/prefab/CodeBlock.cpp new file mode 100644 index 0000000000..dc172560dd --- /dev/null +++ b/examples/prefab/CodeBlock.cpp @@ -0,0 +1,23 @@ +// This file is part of Empirical, https://github.com/devosoft/Empirical +// Copyright (C) Michigan State University, 2020. +// Released under the MIT Software license; see doc/LICENSE + +#include + +#include "emp/web/web.hpp" +#include "emp/prefab/CodeBlock.hpp" + +emp::web::Document doc("emp_base"); + +int main() +{ + std::cout << "entering main\n"; + std::string my_code = + R"( + int num = 9; + std::cout << num << " is a square number" << std::endl; + )"; + + emp::prefab::CodeBlock code_block(my_code, "c++"); + doc << code_block; +} diff --git a/examples/prefab/CodeBlock.html b/examples/prefab/CodeBlock.html new file mode 100644 index 0000000000..ac91f0a931 --- /dev/null +++ b/examples/prefab/CodeBlock.html @@ -0,0 +1,35 @@ + + + + + + + + + + + +Code Block Examplep + + + +
+

Code Block Example

+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/examples/prefab/Collapse.cpp b/examples/prefab/Collapse.cpp new file mode 100644 index 0000000000..f53e84f3b0 --- /dev/null +++ b/examples/prefab/Collapse.cpp @@ -0,0 +1,49 @@ +// This file is part of Empirical, https://github.com/devosoft/Empirical +// Copyright (C) Michigan State University, 2020. +// Released under the MIT Software license; see doc/LICENSE + +#include + +#include "emp/web/web.hpp" +#include "emp/web/Div.hpp" + +#include "emp/prefab/Collapse.hpp" +#include "emp/prefab/CommentBox.hpp" + + +namespace UI = emp::web; + +UI::Document doc("emp_base"); + +int main() +{ + emp::prefab::CommentBox box1; + box1.AddContent("

Box 1

"); + UI::Div btn1; + btn1.SetAttr("class", "btn btn-info"); + btn1 << "Button 1: controls box 1"; + + emp::prefab::CommentBox box2; + box2.AddContent("

Box 2

"); + UI::Div btn2; + btn2.SetAttr("class", "btn btn-info"); + btn2 << "Button 2: controls box 2"; + + emp::prefab::CollapseCoupling collapse1(btn1, box1, true); + emp::prefab::CollapseCoupling collapse2(btn2, box2, true); + + UI::Div btn3; + btn3.SetAttr("class", "btn btn-success"); + btn3 << "Button 3: controls all boxes"; + + collapse1.AddController(btn3, true); + collapse2.AddController(collapse1.GetControllerDiv(1), true); + + doc << collapse1.GetControllerDiv(); + doc << collapse1.GetTargetDiv(); + doc << collapse2.GetControllerDiv(); + doc << collapse2.GetTargetDiv(); + doc << collapse1.GetControllerDiv(1); + + std::cout << "end of main... !" << std::endl; +} diff --git a/examples/prefab/Collapse.html b/examples/prefab/Collapse.html new file mode 100644 index 0000000000..5eaece95aa --- /dev/null +++ b/examples/prefab/Collapse.html @@ -0,0 +1,26 @@ + + + + + + + +Collapse Examples + + + +
+

Collapse Examples

+
+ +
+
+
+ + + + + + + + diff --git a/examples/prefab/CommentBox.cpp b/examples/prefab/CommentBox.cpp new file mode 100644 index 0000000000..cbac406493 --- /dev/null +++ b/examples/prefab/CommentBox.cpp @@ -0,0 +1,23 @@ +// This file is part of Empirical, https://github.com/devosoft/Empirical +// Copyright (C) Michigan State University, 2020. +// Released under the MIT Software license; see doc/LICENSE + +#include + +#include "emp/web/web.hpp" +#include "emp/web/Div.hpp" +#include "emp/prefab/CommentBox.hpp" + +namespace UI = emp::web; + +UI::Document doc("emp_base"); + +int main() +{ + emp::prefab::CommentBox box("comment_box"); + doc << box; + box.AddContent("

Comment Box Title

"); + UI::Div body("body"); + box.AddContent(body); + body << "More content for comment box"; +} diff --git a/examples/prefab/CommentBox.html b/examples/prefab/CommentBox.html new file mode 100644 index 0000000000..b74c39819f --- /dev/null +++ b/examples/prefab/CommentBox.html @@ -0,0 +1,30 @@ + + + + + + + + + + + +Comment Box Example + + + +
+

Comment Box Example

+
+ +
+
+
+ + + + + + + + diff --git a/examples/prefab/ConfigPanel.cpp b/examples/prefab/ConfigPanel.cpp new file mode 100644 index 0000000000..881fc10d2f --- /dev/null +++ b/examples/prefab/ConfigPanel.cpp @@ -0,0 +1,43 @@ +// This file is part of Config Panel App +// Copyright (C) Matthew Andres Moreno, 2020. +// Released under MIT license; see LICENSE + +#include + +#include "emp/config/command_line.hpp" +#include "emp/config/ArgManager.hpp" +#include "emp/prefab/ConfigPanel.hpp" +#include "emp/web/web.hpp" +#include "emp/web/UrlParams.hpp" + +#include "assets/SampleConfig.hpp" + +namespace UI = emp::web; + +UI::Document doc("emp_base"); + +Config cfg; + +emp::prefab::ConfigPanel config_panel(cfg); + +int main() +{ + // apply configuration query params and config files to Config + auto specs = emp::ArgManager::make_builtin_specs(&cfg); + emp::ArgManager am(emp::web::GetUrlParams(), specs); + // cfg.Read("config.cfg"); + am.UseCallbacks(); + if (am.HasUnused()) std::exit(EXIT_FAILURE); + + // log configuraiton settings + std::cout << "==============================" << std::endl; + std::cout << "| How am I configured? |" << std::endl; + std::cout << "==============================" << std::endl; + cfg.Write(std::cout); + std::cout << "==============================\n" << std::endl; + + // setup configuration panel + config_panel.Setup(); + doc << config_panel.GetConfigPanelDiv(); + +} diff --git a/examples/prefab/ConfigPanel.html b/examples/prefab/ConfigPanel.html new file mode 100644 index 0000000000..8fe72120da --- /dev/null +++ b/examples/prefab/ConfigPanel.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + +Config Panel App + + + +
+

Config Panel App

+

source: github.com/mmore500/config-panel-app

+
+ +
+
+
+ + + + + + + + diff --git a/examples/prefab/FontAwesomeIcon.cpp b/examples/prefab/FontAwesomeIcon.cpp new file mode 100644 index 0000000000..2db12b3f85 --- /dev/null +++ b/examples/prefab/FontAwesomeIcon.cpp @@ -0,0 +1,42 @@ +// This file is part of Empirical, https://github.com/devosoft/Empirical +// Copyright (C) Michigan State University, 2020. +// Released under the MIT Software license; see doc/LICENSE + +#include + +#include "emp/web/web.hpp" +#include "emp/web/Div.hpp" +#include "emp/prefab/FontAwesomeIcon.hpp" + +namespace UI = emp::web; + +UI::Document doc("emp_base"); + +int main() +{ + UI::Div toggleIcons; + doc << toggleIcons; + toggleIcons << "

Collapse Icons

"; + emp::prefab::FontAwesomeIcon up("fa-angle-double-up"); + toggleIcons << up; + toggleIcons << "
"; + emp::prefab::FontAwesomeIcon down("fa-angle-double-down"); + toggleIcons << down; + toggleIcons << "
"; + emp::prefab::FontAwesomeIcon right("fa-angle-double-right"); + toggleIcons << right; + + doc << "


"; + + UI::Div infoIcons; + doc << infoIcons; + infoIcons << "

Circle icons

"; + emp::prefab::FontAwesomeIcon i_circle("fa-info-circle"); + infoIcons << i_circle; + infoIcons << "
"; + emp::prefab::FontAwesomeIcon question_circle("fa-question-circle-o"); + infoIcons << question_circle; + infoIcons << "
"; + emp::prefab::FontAwesomeIcon plus_circle("fa-plus-circle"); + infoIcons << plus_circle; +} diff --git a/examples/prefab/FontAwesomeIcon.html b/examples/prefab/FontAwesomeIcon.html new file mode 100644 index 0000000000..0d4d5a1a07 --- /dev/null +++ b/examples/prefab/FontAwesomeIcon.html @@ -0,0 +1,29 @@ + + + + + + + + + +Font Awesome Examples + + + +
+

Font Awesome Examples

+

View Font Awesome icon library: https://fontawesome.com/v4.7.0/icons/

+
+ +
+
+
+ + + + + + + + diff --git a/examples/prefab/LoadingIcon.cpp b/examples/prefab/LoadingIcon.cpp new file mode 100644 index 0000000000..3033244d29 --- /dev/null +++ b/examples/prefab/LoadingIcon.cpp @@ -0,0 +1,18 @@ +// This file is part of Empirical, https://github.com/devosoft/Empirical +// Copyright (C) Michigan State University, 2020. +// Released under the MIT Software license; see doc/LICENSE + +#include + +#include "emp/web/web.hpp" +#include "emp/prefab/LoadingIcon.hpp" + +namespace UI = emp::web; + +UI::Document doc("emp_base"); + +int main() +{ + emp::prefab::LoadingIcon spinner; + doc << spinner; +} diff --git a/examples/prefab/LoadingIcon.html b/examples/prefab/LoadingIcon.html new file mode 100644 index 0000000000..2f37b16bf9 --- /dev/null +++ b/examples/prefab/LoadingIcon.html @@ -0,0 +1,28 @@ + + + + + + + + + +Loading Icon Example + + + +
+

Loading Icon Example

+
+ +
+
+
+ + + + + + + + diff --git a/examples/prefab/LoadingModal.cpp b/examples/prefab/LoadingModal.cpp new file mode 100644 index 0000000000..2b1ae24dfc --- /dev/null +++ b/examples/prefab/LoadingModal.cpp @@ -0,0 +1,21 @@ +// This file is part of Empirical, https://github.com/devosoft/Empirical +// Copyright (C) Michigan State University, 2020. +// Released under the MIT Software license; see doc/LICENSE + +#include + +#include "emp/web/web.hpp" +#include "emp/prefab/LoadingModal.hpp" + +namespace UI = emp::web; + +UI::Document doc("emp_base"); +int main(){ + doc << "

Click button to show loading modal. It will close automatically after a few seconds.

"; + UI::Button loading_modal_demo([](){emscripten_run_script("DemoLoadingModal();");}, "Show Loading Modal"); + doc << loading_modal_demo; + loading_modal_demo.SetAttr( + "class", "btn btn-info" + ); + emp::prefab::CloseLoadingModal(); +} diff --git a/examples/prefab/LoadingModal.html b/examples/prefab/LoadingModal.html new file mode 100644 index 0000000000..19c463e177 --- /dev/null +++ b/examples/prefab/LoadingModal.html @@ -0,0 +1,32 @@ + + + + + + + + + + +Loading Modal Example + + + + + + + + + +
+

Loading Modal Example

+
+ +
+
+
+ + + + + diff --git a/examples/prefab/Makefile b/examples/prefab/Makefile new file mode 100644 index 0000000000..5274b1e55e --- /dev/null +++ b/examples/prefab/Makefile @@ -0,0 +1,56 @@ +# TODO: +# * Remove "-Wno-unused-function" ; right now it's required because static library functions +# are expected to be called at some point. +# * Remove "-Wno-gnu-zero-variadic-macro-arguments" ; right now it's in there because the +# EM_ASM macro seems to require extra arguments... +# * Remove "-s WASM=0" ; right now some canvas example files don't seem to work in +# WebAssembly. + +# Flags to use regardless of compiler +CFLAGS_all := -std=c++17 -Wall -Wno-unused-function -I../../include/ + +# Emscripten compiler information +CXX_web := emcc +CXX_native := g++ + +OFLAGS_native_debug := -g -DEMP_TRACK_MEM -Wnon-virtual-dtor -Wcast-align -Woverloaded-virtual -Wconversion -Weffc++ +OFLAGS_native_opt := -O3 -DNDEBUG + +OFLAGS_web_debug := -Wno-dollar-in-identifier-extension -s TOTAL_MEMORY=67108864 -s ASSERTIONS=2 -s DEMANGLE_SUPPORT=1 -s WASM=0 -Wnon-virtual-dtor -Wcast-align -Woverloaded-virtual -Wconversion -Weffc++ + # -s SAFE_HEAP=1 +OFLAGS_web_opt := -Os -DNDEBUG -s TOTAL_MEMORY=67108864 -s WASM=0 + +CFLAGS_native_debug := $(CFLAGS_all) $(OFLAGS_native_debug) +CFLAGS_native_opt := $(CFLAGS_all) $(OFLAGS_native_opt) +CFLAGS_native := $(CFLAGS_native_opt) + +CFLAGS_web_debug := $(CFLAGS_all) $(OFLAGS_web_debug) --js-library ../../include/emp/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback']" -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' -s NO_EXIT_RUNTIME=1 +CFLAGS_web_opt := $(CFLAGS_all) $(OFLAGS_web_opt) --js-library ../../include/emp/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback']" -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' -s NO_EXIT_RUNTIME=1 + +JS_TARGETS := Card.js CodeBlock.js Collapse.js CommentBox.js ConfigPanel.js FontAwesomeIcon.js LoadingIcon.js LoadingModal.js Modal.js ToggleSwitch.js + +CXX := $(CXX_web) +CFLAGS_web := $(CFLAGS_web_opt) + +%.js: %.cpp + $(CXX_web) $(CFLAGS_web) $< -o $@ + +debug: CFLAGS_web := $(CFLAGS_web_debug) +debug: $(JS_TARGETS) + +web: CXX := $(CXX_web) +web: CFLAGS_web := $(CFLAGS_web_opt) +web: $(JS_TARGETS) + +web-debug: CXX := $(CXX_web) +web-debug: CFLAGS := $(CFLAGS_web_debug) +web-debug: all + +default: web + +clean: + rm -f debug-* $(JS_TARGETS) *.js.map *.js.mem *.wasm *.wasm.map *.wast *~ source/*.o source/*/*.o + +# Debugging information +#print-%: ; @echo $*=$($*) +print-%: ; @echo '$(subst ','\'',$*=$($*))' diff --git a/examples/prefab/Modal.cpp b/examples/prefab/Modal.cpp new file mode 100644 index 0000000000..afe10c1416 --- /dev/null +++ b/examples/prefab/Modal.cpp @@ -0,0 +1,36 @@ +// This file is part of Empirical, https://github.com/devosoft/Empirical +// Copyright (C) Michigan State University, 2020. +// Released under the MIT Software license; see doc/LICENSE + +#include + +#include "emp/web/web.hpp" +#include "emp/web/Button.hpp" +#include "emp/prefab/Modal.hpp" + +namespace UI = emp::web; + +UI::Document doc("emp_base"); +int main(){ + emp::prefab::Modal modal; + doc << modal; + + modal.AddHeaderContent("

Modal Header Section

"); + modal.AddBodyContent("This is the content of the modal"); + + modal.AddFooterContent("Modal Footer Section"); + UI::Button close_btn([](){;}, "Close"); + close_btn.SetAttr("class", "btn btn-secondary"); + modal.AddFooterContent(close_btn); + modal.AddButton(close_btn); + + modal.AddClosingX(); + + UI::Button modal_btn([](){;}, "Show Modal"); + doc << modal_btn; + modal_btn.SetAttr("class", "btn btn-info"); + modal.AddButton(modal_btn); + + std::cout << "end of main... !" << std::endl; + +} diff --git a/examples/prefab/Modal.html b/examples/prefab/Modal.html new file mode 100644 index 0000000000..6ecf0b48e0 --- /dev/null +++ b/examples/prefab/Modal.html @@ -0,0 +1,29 @@ + + + + + + + + + + +Modal Example + + + + + + +
+

Modal Example

+
+ +
+
+
+ + + + + diff --git a/examples/prefab/ToggleSwitch.cpp b/examples/prefab/ToggleSwitch.cpp new file mode 100644 index 0000000000..db2d93a9ff --- /dev/null +++ b/examples/prefab/ToggleSwitch.cpp @@ -0,0 +1,26 @@ +// This file is part of Empirical, https://github.com/devosoft/Empirical +// Copyright (C) Michigan State University, 2020. +// Released under the MIT Software license; see doc/LICENSE + +#include + +#include "emp/web/web.hpp" +#include "emp/prefab/ToggleSwitch.hpp" + +namespace UI = emp::web; + +UI::Document doc("emp_base"); + +int main() +{ + emp::prefab::ToggleSwitch on_switch([](std::string val){}, "Switch Defult On", true, "user_defined_switch_id"); + doc << on_switch; + + doc << "
"; + + emp::prefab::ToggleSwitch off_switch([](std::string val){}, NULL, false); + doc << off_switch; + off_switch.AddLabel("Switch Defult Off"); + + std::cout << "end of main... !" << std::endl; +} diff --git a/examples/prefab/ToggleSwitch.html b/examples/prefab/ToggleSwitch.html new file mode 100644 index 0000000000..cd477eee6e --- /dev/null +++ b/examples/prefab/ToggleSwitch.html @@ -0,0 +1,30 @@ + + + + + + + + + + + +Toggle Switch Example + + + +
+

Toggle Switch Example

+
+ +
+
+
+ + + + + + + + diff --git a/examples/prefab/assets/SampleConfig.hpp b/examples/prefab/assets/SampleConfig.hpp new file mode 100644 index 0000000000..853e116e3d --- /dev/null +++ b/examples/prefab/assets/SampleConfig.hpp @@ -0,0 +1,44 @@ +// Adapted from Emily's memic_model project +// https://github.com/emilydolson/memic_model + +#pragma once + +#include "emp/config/config.hpp" + +EMP_BUILD_CONFIG( Config, + GROUP(MAIN, "Global settings"), + VALUE(BOOL_EX, bool, true, "example description"), + VALUE(SEED, int, -1, "Random number generator seed"), + VALUE(TIME_STEPS, int, 1000, "Number of time steps to run for"), + VALUE(PLATE_LENGTH, double, 10.0, "Length of plate in mm"), + VALUE(PLATE_WIDTH, double, 6.0, "Width of plate in mm"), + VALUE(PLATE_DEPTH, double, 1.45, "Depth of plate in mm"), + VALUE(CELL_DIAMETER, double, 20.0, "Cell length and width in microns"), + VALUE(INIT_POP_SIZE, int, 100, "Number of cells to seed population with"), + VALUE(DATA_RESOLUTION, int, 10, "How many updates between printing data?"), + + GROUP(CELL, "Cell settings"), + VALUE(NEUTRAL_MUTATION_RATE, double, .05, "Probability of a neutral mutation (only relevant for phylogenetic signature)"), + VALUE(ASYMMETRIC_DIVISION_PROB, double, 0, "Probability of a change in stemness"), + VALUE(MITOSIS_PROB, double, .5, "Probability of mitosis"), + VALUE(HYPOXIA_DEATH_PROB, double, .25, "Probability of dieing, given hypoxic conditions"), + VALUE(AGE_LIMIT, int, 100, "Age over which non-stem cells die"), + VALUE(BASAL_OXYGEN_CONSUMPTION, double, .00075, "Base oxygen consumption rate"), + VALUE(OXYGEN_CONSUMPTION_DIVISION, double, .00075*5, "Amount of oxygen a cell consumes on division"), + + GROUP(OXYGEN, "Oxygen settings"), + VALUE(INITIAL_OXYGEN_LEVEL, double, .5, "Initial oxygen level (will be placed in all cells)"), + VALUE(OXYGEN_DIFFUSION_COEFFICIENT, double, .1, "Oxygen diffusion coefficient"), + VALUE(DIFFUSION_STEPS_PER_TIME_STEP, int, 100, "Rate at which diffusion is calculated relative to rest of model"), + VALUE(OXYGEN_THRESHOLD, double, .1, "How much oxygen do cells need to survive?"), + VALUE(KM, double, 0.01, "Michaelis-Menten kinetic parameter"), + + GROUP(TREATMENT, "Treatment settings"), + VALUE(RADIATION_DOSES, int, 1, "Number of radiation doses to apply (for use in web interface - use a radiation prescription file for command-line)"), + VALUE(RADIATION_DOSE_SIZE, double, 2, "Dose size (Gy) (for use in web interface - use a radiation prescription file for command-line)"), + VALUE(RADIATION_PRESCRIPTION_FILE, std::string, "none", "File containing radiation prescription"), + VALUE(K_OER, double, 3.28, "Effective OER constant"), + VALUE(OER_MIN, double, 1, "OER min constant"), + VALUE(OER_ALPHA_MAX, double, 1.75, "OER alpha max constant"), + VALUE(OER_BETA_MAX, double, 3.25, "OER alpha (? this is what the paper says but I feel like it's supposed to be beta) max constant"), +); diff --git a/examples/web/Makefile b/examples/web/Makefile index 3272866403..6eebe68f5f 100644 --- a/examples/web/Makefile +++ b/examples/web/Makefile @@ -30,13 +30,12 @@ CFLAGS_web_opt := $(CFLAGS_all) $(OFLAGS_web_opt) --js-library ../../include/emp JS_TARGETS := Animate.js Animate2.js Attributes.js Canvas.js Div.js DP.js Example.js Font.js Graph.js Hello.js Image.js keypress.js Sudoku.js Table.js TextArea.js Tween.js Web.js -TARGETS := debug-Animate debug-keypress debug-Slate debug-web - -default: web - CXX := $(CXX_web) CFLAGS_web := $(CFLAGS_web_opt) +%.js: %.cpp + $(CXX_web) $(CFLAGS_web) $< -o $@ + debug: CFLAGS_web := $(CFLAGS_web_debug) debug: $(JS_TARGETS) @@ -48,16 +47,7 @@ web-debug: CXX := $(CXX_web) web-debug: CFLAGS := $(CFLAGS_web_debug) web-debug: all -native: all - -all: $(TARGETS) - -$(JS_TARGETS): %.js : %.cpp # $(WEB)/%.h - $(CXX_web) $(CFLAGS_web) $< -o $@ - - -debug-%: $*.cpp - $(CXX_native) $(CFLAGS_native) $< -o $@ +default: web clean: rm -f debug-* $(JS_TARGETS) *.js.map *.js.mem *.wasm *.wasm.map *.wast *~ source/*.o source/*/*.o diff --git a/include/emp/config/config.hpp b/include/emp/config/config.hpp index b75852e86b..ecaca5de81 100644 --- a/include/emp/config/config.hpp +++ b/include/emp/config/config.hpp @@ -187,8 +187,10 @@ namespace emp { ~ConfigGroup() { ; } size_t GetSize() const { return entry_set.size(); } - std::string GetName() const {return name;} - std::string GetDesc() const {return desc;} + + std::string GetName() const { return name; } + std::string GetDesc() const { return desc; } + ConfigEntry * GetEntry(size_t id) { return entry_set[id]; } ConfigEntry * GetLastEntry() { emp_assert(GetSize() > 0); return entry_set.back(); } @@ -675,6 +677,11 @@ namespace emp { std::bind(&ConfigManager::UseObject, new_manager, _1) ); } + /// Access group_set using this method since it is protected + emp::vector GetGroupSet(){ + return group_set; + } + }; } diff --git a/include/emp/prefab/Card.hpp b/include/emp/prefab/Card.hpp new file mode 100644 index 0000000000..c01a29e391 --- /dev/null +++ b/include/emp/prefab/Card.hpp @@ -0,0 +1,113 @@ +#ifndef EMP_CARD_HPP +#define EMP_CARD_HPP + +#include "../tools/string_utils.hpp" +#include "../web/Div.hpp" + +#include "Collapse.hpp" +#include "FontAwesomeIcon.hpp" + +namespace emp { +namespace prefab { + /// Use Card class to create Bootstrap style cards. + class Card : public web::Div { + private: + // ID of card Div to be used in ID of associated card sub components + std::string card_base = this->GetID(); + // all header content will be added here + web::Div card_header{emp::to_string(card_base, "_card_header")}; + // all body content will be added here + web::Div card_body{emp::to_string(card_base, "_card_body")}; + // Asssigns classes to card elements for styling + void AddBootstrap() { + this->SetAttr("class", "card"); + card_header.SetAttr("class", "card-header"); + card_body.SetAttr("class", "card-body"); + } + + public: + /** + * @param state indicate whether card should be STAITC, INIT_OPEN, or INIT_CLOSED (default STAITC) + * @param show_glyphs should toggle icons show in collapsible card header? (default true) + * @param id user defined ID for card Div, (default emscripten generated) + */ + Card( + const std::string & state="STATIC", + const bool & show_glyphs=true, + const std::string & id="" + ): web::Div(id) { + + AddBootstrap(); + if (state == "STATIC") { // static card with no toggle enabled + *this << card_header; + *this << card_body; + } else { + // card is collapsible, make the collapse link the head of the card + prefab::CollapseCoupling accordion(card_header, + card_body, + state == "INIT_OPEN", + emp::to_string(card_base+ "_card_collapse") + ); + *this << accordion.GetControllerDiv(); + *this << accordion.GetTargetDiv(); + + if (show_glyphs) { // by default add glyphs to a collapsible card + prefab::FontAwesomeIcon up("fa-angle-double-up"); + prefab::FontAwesomeIcon down("fa-angle-double-down"); + card_header << up; + card_header << down; + up.AddAttr("class", "toggle_glyph"); + down.AddAttr("class", "toggle_glyph"); + } + card_header.AddAttr("class", "collapse_toggle_card_header"); + } + } + + /** + * Add content to header section of card + * + * @param val content to be added to header, can be a web element or primitive type + * @param link_content indicates whether the content should have Bootstrap link properties (default false) + */ + template + void AddHeaderContent(T val, const bool link_content=false) { + /* + * Note: val can be a controller of a target area (made with CollapseCoupling class) + * but when added to header of the card, it will also trigger the card + * to collapse/expand + */ + if (link_content) { + /* + * Add bootstrap link properities to content (hover, underline, etc.), + * but does not set a target or href because it is assumed that + * this content will control the card collapse, which is done in the + * constructor. + */ + web::Div btn_link; + btn_link.SetAttr("class", "btn-link"); + card_header << btn_link << val; + } else { + card_header << val; + } + } + + /** + * Add content to body section of card + * + * @param val can be a web element or primitive type + */ + template + void AddBodyContent(T val) { + card_body << val; + } + + /* + * TODO: override operator<< so that it streams into body of card + * Methods tried so far (without success) are listed in + * Issue #345 (https://github.com/devosoft/Empirical/issues/345) + */ + }; +} +} + +#endif diff --git a/include/emp/prefab/CodeBlock.hpp b/include/emp/prefab/CodeBlock.hpp new file mode 100644 index 0000000000..07b4f8fe43 --- /dev/null +++ b/include/emp/prefab/CodeBlock.hpp @@ -0,0 +1,59 @@ +#ifndef EMP_CODE_BLOCK_HPP +#define EMP_CODE_BLOCK_HPP + +#include "../base/errors.hpp" +#include "../tools/string_utils.hpp" +#include "../web/Element.hpp" +#include "../web/Widget.hpp" + +namespace emp { +namespace prefab { + /** + * Use CodeBlock class to easily add code to a web + * application that is highlighted based on the language used. + */ + class CodeBlock: public web::Element { + private: + // code element to hold user's code + web::Element code{emp::to_string("code")}; + + public: + /** + * List of all supported languages: https://highlightjs.org/static/demo/ + * + * @note Due to JavaScript callback functions, can only set code for block + * in constructor. + * + * @param code_block string of the code to be placed in code block + * @param lang programming language to base syntax highlighting + */ + CodeBlock(const std::string code_block, const std::string lang, const std::string & id="") + : web::Element("pre", id) { + this->SetAttr("class", lang); + // trigger HighlightJS library to apply syntax highlighting + this->RegisterUpdateJS([]() { + emscripten_run_script("hljs.initHighlighting.called = false; hljs.initHighlighting();"); + }); + /* + * make sure special characters (", ', &, <, >) appear as their symbol, + * not rendered as html on web page + */ + code << emp::to_web_safe_string(code_block); + *this << code; + } + + /* + * TODO: << operator throws error + * The method below throws an error when trying to stream + * a code block into anything else (web element, html). + * + * template + * void operator<<(T invalid) { + * emp::LibraryError("Not allowed to add code to the code block after construction due to JavaScript callback order"); + * } + */ + }; +} +} + +#endif diff --git a/include/emp/prefab/Collapse.hpp b/include/emp/prefab/Collapse.hpp new file mode 100644 index 0000000000..1d4085a520 --- /dev/null +++ b/include/emp/prefab/Collapse.hpp @@ -0,0 +1,221 @@ +#ifndef EMP_COLLAPSE_HPP +#define EMP_COLLAPSE_HPP + +#include "../tools/string_utils.hpp" +#include "../web/Div.hpp" +#include "../web/Widget.hpp" +#include "../web/_FacetedWidget.hpp" + +namespace emp { +namespace prefab { + namespace internal { + + /** + * CollpaseController class adds necessary html attributes to controller + * to function as the controller for a group or groups of target areas. + * Only ever called by CollapseCoupling class. + */ + class CollapseController { + private: + web::Div inner_controller; // reference to controller passed to constructor + + public: + /** + * @param controller web element that cause target area(s) to expand/collapse when clicked + * @param expanded whether or not the target(s) are initially in an expanded/open state + */ + template + CollapseController( + T controller, + const std::string controls_class, + bool expanded, std::string id="" + ) : inner_controller(id) { + + inner_controller = controller; + inner_controller.SetAttr( + "role", "button", + "data-toggle", "collapse" + ); + inner_controller.AddAttr( + "data-target", "." + controls_class, + "aria-controls", "." + controls_class, + "class", "collapse_toggle" + ); + + inner_controller.SetAttr("aria-expanded", expanded ? "true" : "false"); + + if (!expanded) { + inner_controller.AddAttr("class", "collapsed"); + } + } + + web::Div & GetLinkDiv() { return inner_controller; } + }; + } + + /** + * CollapseCoupling class maintains a group of targets and controllers. + * When a controller is clicked on a web page, all the associated targets + * will change state (expand/collapse). + */ + class CollapseCoupling { + private: + // all web elements that expand/collapse with this coupling + emp::vector targets; + // all web elements that control expanding/collapsing with this coupling + emp::vector controllers; + // class associated with this + std::string target_class; + // counter used to generate unique class names, shared by all instances of this class + inline static int counter = 0; + + public: + /** + * Constructor which takes web elements as the controller and target. + * It will put each in a vector and call the constructor which takes + * vectors as controller and target parameters. + */ + CollapseCoupling( + web::Widget controller, + web::Widget target, + const bool expanded=false, + const std::string in_class="" + ) : CollapseCoupling(emp::vector{controller}, + emp::vector{target}, expanded, in_class) { ; } + + /** + * Constructor which takes vectors of web elements as the controller and target + * It will create the collapse coupling by adding necessary HTML attributes + * to all controllers and targets + */ + CollapseCoupling( + emp::vector controllers, + emp::vector targets, + const bool expanded=false, + const std::string in_class="" + ) { + /* + * if a class is defined by the user, use it + * Otherwise generate a unique class + */ + if (in_class == "") { + target_class = "emp__collapse_class_" + std::to_string(counter); + counter++; + } else { + target_class = in_class; + } + + // add controllers to this coupling + for (auto & widget : controllers) { + AddController(widget, expanded); + } + // add targets to this coupling + for (auto & widget : targets) { + AddTarget(widget, expanded); + } + } + + /* + * TODO: Ideally, this constructor will be templated and can handle any input that is + * not a Widget or vector of Widgets. + * When we tried this before, all input would go through to this constructor. + * This caused issues when it tried to stream widgets into a div but the widget + * already had another parent. + * + * Note: Maybe if we web::internal::FacetedWidget intead of web::Widget in the + * first constructor, templating this constructor will work? + */ + /** + * Constructor which takes strings as the controller and target + * It will put each in a Div element and call the constructor which takes + * web elements as controller and target parameters. + */ + CollapseCoupling( + const std::string controller, + const std::string target, + const bool expanded=false, + const std::string in_class="" + ): CollapseCoupling(web::Div{} << controller, web::Div{} << target, expanded, in_class) { ; } + + /** + * Adds a controller to the vector of controllers for this CollapseCouple + * + * @param controller new controller to add to coupling is of type Widget + * @param expaned initial state of the target(s), is it expaned or not? + */ + void AddController(web::Widget controller, const bool expanded) { + internal::CollapseController controller_widget(controller, target_class, expanded); + controllers.push_back(controller_widget.GetLinkDiv()); + } + + /** Adds a controller to the vector of controllers for this CollapseCouple. + * + * @param controller new controller to add to coupling is of type string + * @param expaned initial state of the target(s), is it expaned or not? + */ + + /* + * TODO: Ideally, this method would be templated, but running into same issues + * as when trying to do this with the constructor + */ + void AddController(const std::string controller, const bool expanded) { + AddController(web::Div{} << controller, expanded); + } + + /** + * Adds a target to the vector of targets for this CollapseCouple + * + * @param widget new target to add to coupling is a web element + * @param expaned initial state of the target(s), is it expaned or not? + */ + void AddTarget(web::internal::FacetedWidget widget, const bool expanded) { + if (expanded) { + widget.AddAttr("class", "collapse show"); + } else { + widget.AddAttr("class", "collapse"); + } + widget.AddAttr("class", target_class); + targets.push_back(widget); + } + + /** + * Adds a target to the vector of targets for this CollapseCouple + * + * @param widget new target to add to coupling is a string + * @param expaned initial state of the target(s), is it expaned or not? + */ + + /* + * If the target is not a web widget, place it in a div and call the other AddTarget function + * TODO: Ideally, this method would be templated, but running into same issues + * as when trying to do this with the constructor + */ + void AddTarget(const std::string target, const bool expanded) { + AddTarget(web::Div{} << target, expanded); + } + + /// Returns the target_class associated with this CollapseCouple + std::string GetTargetClass() { return target_class; } + + /* + * Functions used to retrieve controllers and targets + * TODO: In the future, add capability to call controllers and targets by name + * (Like dictionary accesses, key/value pairs) + */ + + /// Returns the vector of all controllers associated with this CollapseCouple + emp::vector & GetControllerDivs() { return controllers; } + + /// Returns the controller at the given index + web::Widget & GetControllerDiv(const int index=0) { return controllers[index]; } + + /// Returns the vector of all targets associated with this CollapseCouple + emp::vector & GetTargetDivs() { return targets; } + + /// Returns the target at the given index + web::Widget & GetTargetDiv(const int index=0) { return targets[index]; } + }; +} +} + +#endif diff --git a/include/emp/prefab/CommentBox.hpp b/include/emp/prefab/CommentBox.hpp new file mode 100644 index 0000000000..57418afede --- /dev/null +++ b/include/emp/prefab/CommentBox.hpp @@ -0,0 +1,78 @@ +#ifndef EMP_COMMENT_BOX_HPP +#define EMP_COMMENT_BOX_HPP + +#include "../tools/string_utils.hpp" +#include "../web/Div.hpp" + +/* + * TODO: When prefab tools for adding mobile only and desktop only + * content are created, remove AddMobileContent(), desktop_content + * and mobile_content divs, and ConfigPanel as a friend class. + * AddConent() should stream into all_content div. + */ + +namespace emp { +namespace prefab { + class ConfigPanel; + /** + * Use CommentBox class to create a light grey "comment bubble". + * Optionally, it can contain text and other web elements. + */ + class CommentBox: public web::Div { + friend prefab::ConfigPanel; + private: + // ID for the comment box Div + std::string box_base = this->GetID(); + // Provides stylized "carrot" for box + web::Div triangle{emp::to_string(box_base, "_triangle")}; + // Contains Divs for mobile and desktop content + web::Div all_content{emp::to_string(box_base, "_all_content")}; + // Content that shows on all screen sizes + web::Div desktop_content{emp::to_string(box_base, "_desktop_content")}; + // Content that shows only on mobile devices + web::Div mobile_content{emp::to_string(box_base, "_mobile_content")}; + + protected: + /// Add content only to be shown on small screens + + /* + * TODO: In the future, remove this method and instead + * have a prefab tool that can add mobile content to + * web element. + */ + template + void AddMobileContent(T val) { + mobile_content << val; + } + + public: + CommentBox(const std::string id="") : web::Div(id) { + *this << triangle; + *this << all_content; + all_content << desktop_content; + all_content << mobile_content; + + triangle.SetAttr("class", "commentbox_triangle"); + all_content.SetAttr("class", "commentbox_content"); + mobile_content.SetAttr("class", "mobile_commentbox"); + } + + /** + * Take input of any type and add it to the comment box. + * Content will show on all screen sizes. + */ + + /* + * TODO: override the input operator to stream into the correct div + * See issue #345 (https://github.com/devosoft/Empirical/issues/345) + * for methods tried already. + */ + template + void AddContent(T val) { + desktop_content << val; + } + }; +} +} + +#endif diff --git a/include/emp/prefab/ConfigPanel.hpp b/include/emp/prefab/ConfigPanel.hpp new file mode 100644 index 0000000000..65ed8c695a --- /dev/null +++ b/include/emp/prefab/ConfigPanel.hpp @@ -0,0 +1,481 @@ +#ifndef EMP_CONFIG_WEB_INTERFACE_HPP +#define EMP_CONFIG_WEB_INTERFACE_HPP + +#include +#include + +#include "../datastructs/set_utils.hpp" +#include "../tools/string_utils.hpp" + +#include "../config/config.hpp" +#include "../web/Div.hpp" +#include "../web/Element.hpp" +#include "../web/Input.hpp" + +// Prefab elements +#include "Card.hpp" +#include "CommentBox.hpp" +#include "FontAwesomeIcon.hpp" +#include "Collapse.hpp" +#include "ToggleSwitch.hpp" + +/* + * TODO: Find a way for input callbacks to remain active + * after a ConfigPanel object goes out of scope. + */ + +namespace emp { +namespace prefab { + + namespace internal { + /** + * Shared pointer held by instances of ConfigPanel class representing + * the same conceptual ConfigPanel DOM object. + * Contains state that should persist while ConfigPanel DOM object + * persists. + */ + class ConfigPanelInfo : public web::internal::DivInfo { + + public: + using on_change_fun_t = std::function; + + private: + on_change_fun_t on_change_fun{ [](const std::string & val) { ; } }; + + public: + /** + * Construct a shared pointer to manage ConfigPanel state. + * + * @param in_id HTML ID of ConfigPanel div + */ + ConfigPanelInfo( const std::string & in_id="" ) + : web::internal::DivInfo(in_id) { ; } + + /** + * Get current on-update callback for a ConfigPanel. + * + * @return current callback function handle + */ + on_change_fun_t & GetOnChangeFun() { return on_change_fun; } + + /** + * Set on-update callback for a ConfigPanel. + * + * @param fun callback function handle + */ + void SetOnChangeFun(const on_change_fun_t & fun) { + on_change_fun = fun; + } + + }; + + } + + /** + * Use the ConfigPanel class to easily add a dynamic configuration + * panel to your web app. Users can interact with the config panel + * by updating values. + */ + class ConfigPanel : public web::Div { + public: + using on_change_fun_t = internal::ConfigPanelInfo::on_change_fun_t; + + private: + /** + * Type of shared pointer shared among instances of ConfigPanel + * representing the same conceptual DOM element. + */ + using INFO_TYPE = internal::ConfigPanelInfo; + + /** + * Get shared info pointer, cast to ConfigPanel-specific type. + * + * @return cast pointer + */ + INFO_TYPE * Info() { + return dynamic_cast(info); + } + + /** + * Get shared info pointer, cast to const ConfigPanel-specific type. + * + * @return cast pointer + */ + const INFO_TYPE * Info() const { + return dynamic_cast(info); + } + + inline static std::set numeric_types = {"int", "double", "float", "uint32_t", "uint64_t", "size_t"}; + Config & config; + web::Div settings_div; + std::set exclude; + std::map group_divs; + std::map input_divs; + + std::function format_label_fun = [](std::string name) { + emp::vector sliced = slice(name, '_'); + return to_titlecase(join(sliced, " ")); + }; + + /** + * For ints and doubles, there are three inputs: + * a number input and two slider inputs. + * SyncForm updates two inputs with the new value in the third input. + * + * @param val new value to be update the inputs with + * @param input1 ID of one input that needs its value updated + * @param input2 ID of the second input that needs its value updated + */ + void SyncForm(const std::string val, const std::string input1, const std::string input2) { + emp::web::Input div1(settings_div.Find(input1)); + div1.Value(val); + emp::web::Input div2(settings_div.Find(input2)); + div2.Value(val); + div1.Redraw(); + div2.Redraw(); + } + + /** + * Get current on-update callback. + * + * @return current callback function handle + */ + on_change_fun_t& GetOnChangeFun() { + return Info()->GetOnChangeFun(); + }; + + /** + * Run on-update callback. + * + * @param val TODO what is this? + */ + void DoOnChangeFun(const std::string & val) { + Info()->GetOnChangeFun()(val); + }; + + /** + * Add toggle glyphs and title for setting + * + * @param name setting's name + * @param setting_element Div for all of setting's contents + * @param title Button for setting's glyphs and title + */ + void AddSettingLabel( + const std::string & name, + web::Div & setting_element, + web::Element & title + ) { + input_divs[name] << setting_element; + setting_element.SetAttr("class", "setting_element"); + title.AddAttr("class", "btn btn-link"); + + prefab::FontAwesomeIcon arrow_right_for_dropdown("fa-angle-double-right"); + title << arrow_right_for_dropdown; + prefab::FontAwesomeIcon arrow_up_for_dropdown("fa-angle-double-up"); + title << arrow_up_for_dropdown; + title << format_label_fun(name); + + arrow_right_for_dropdown.AddAttr("class", "toggle_icon_right_margin"); + arrow_up_for_dropdown.AddAttr("class", "toggle_icon_right_margin"); + } + + /** + * Add numeric specific inputs to setting element. + * + * @param name setting's name + * @param setting_element Div for all of setting's contents + * @param box CommentBox for setting's dropdown section + * @param type string representing the settting's type (int, double, float) + * @param value string representing the numerical value of the setting + */ + void AddNumericSetting( + const std::string & name, + web::Div & setting_element, + prefab::CommentBox & box, + const std::string & type, + const std::string & value + ){ + const std::string name_input_slider = name + "_input_slider"; + const std::string name_input_number = name + "_input_number"; + const std::string name_input_mobile_slider = name + "_input_mobile_slider"; + web::Input slider( [](std::string x) { + std::cout << "empty slider function" << std::endl;}, + "range", + "", + name_input_slider + ); + setting_element << slider; + + web::Input number([](std::string val) { + std::cout << "empty number function" << std::endl; + }, + "number", + "", + name_input_number + ); + setting_element << number; + web::Input mobile_slider([](std::string val) { + std::cout << "empty mobile slider function" << std::endl; + }, + "range", + "", + name_input_mobile_slider + ); + box.AddMobileContent("
"); + box.AddMobileContent(mobile_slider); + + // Set onchange behavior for inputs + slider.Callback( + [this,name, name_input_number, name_input_mobile_slider](std::string val) { + config.Set(name, val); + SyncForm(val, name_input_number, name_input_mobile_slider); + }); + number.Callback( + [this,name, name_input_slider, name_input_mobile_slider](std::string val) { + config.Set(name, val); + SyncForm(val, name_input_slider, name_input_mobile_slider); + }); + mobile_slider.Callback( + [this,name, name_input_number, name_input_slider](std::string val) { + config.Set(name, val); + SyncForm(val, name_input_number, name_input_slider); + }); + // Set initial values + slider.Value(config.Get(name)); + number.Value(config.Get(name)); + mobile_slider.Value(config.Get(name)); + slider.SetAttr("class", "input_slider"); + number.SetAttr("class", "input_number"); + + + SetDefaultNumericInputs(type, value, slider, number, mobile_slider); + } + + /** + * Sets the desktop slider, mobile slider, and number + * input for numeric settings to the default value + * provided by the linked config file. + * + * @param type string representing the settting's type (int, double, float) + * @param value string representing the numerical value of the setting + * @param slider reference setting's desktop slider input + * @param number reference setting's number input + * @param mobile_slider reference setting's mobile slider input + */ + void SetDefaultNumericInputs( + const std::string & type, + const std::string & value, + web::Input & slider, + web::Input & number, + web::Input & mobile_slider + ){ + // Attempt to have intelligent defaults + if (type == "double") { + SetDefaultRangeFloatingPoint(slider, emp::from_string(value)); + SetDefaultRangeFloatingPoint(number, emp::from_string(value)); + SetDefaultRangeFloatingPoint(mobile_slider, emp::from_string(value)); + } else if (type == "float") { + SetDefaultRangeFloatingPoint(slider, emp::from_string(value)); + SetDefaultRangeFloatingPoint(number, emp::from_string(value)); + SetDefaultRangeFloatingPoint(mobile_slider, emp::from_string(value)); + } else { + // TODO: Correctly handle all types (although I'm not sure it actually matters?) + SetDefaultRangeFixedPoint(slider, emp::from_string(value)); + SetDefaultRangeFixedPoint(number, emp::from_string(value)); + SetDefaultRangeFixedPoint(mobile_slider, emp::from_string(value)); + } + } + + /** + * Add prefab ToggleSwitch to boolean setting element. + * + * @param name setting's name + * @param setting_element Div for all of setting's contents + * @param value string representing the numerical value of the setting + */ + void AddBoolSetting( + const std::string & name, + web::Div & setting_element, + const std::string & value + ){ + // Bootstrap Toggle Switch + emp::prefab::ToggleSwitch toggle_switch( + [this, name](std::string val) { + config.Set(name, val); + DoOnChangeFun(val); + }, + "", + emp::from_string(value), + name + "_input_checkbox" + ); + setting_element << toggle_switch; + toggle_switch.AddAttr("class", "input_bool"); + } + + /** + * Add text input box to text setting element. + * + * @param name setting's name + * @param setting_element Div for all of setting's contents + */ + void AddTextSetting( + const std::string & name, + web::Div & setting_element + ){ + web::Input text_input( + [this, name](std::string val) { + config.Set(name, val); + DoOnChangeFun(val); + }, + "text", "", name + "_input_textbox" + ); + setting_element << text_input; + text_input.SetAttr( + "class", "input_text", + "type", "text" + ); + text_input.Value(config.Get(name)); + } + + + public: + /// @param c config panel to construct prefab ConfigPanel for + ConfigPanel( + Config & c, + const std::string & div_name = "settings_div" + ) : config(c) + { info = new internal::ConfigPanelInfo(div_name); } + + /** + * Sets on-update callback for a ConfigPanel. + * + * @param fun callback function handle + */ + void SetOnChangeFun(const on_change_fun_t& fun) { + Info()->SetOnChangeFun(fun); + } + + template + void SetDefaultRangeFloatingPoint(web::Input & input, T val) { + if (val > 0 && val < 1) { + // This is a common range for numbers to be in + input.Min(0); + if (val > .1) { + input.Max(1); + } else { + input.Max(val * 100); + } + input.Step(val/10.0); + } else if (val > 0) { + // Assume this is a positive number + input.Min(0); + input.Max(val * 10); + input.Step(val/10.0); + } else if (val < 0) { + input.Min(val * 10); // since val is negative + input.Max(val * -10); + input.Step(val/-10.0); // A negative step would be confusing + } + + // Otherwise val is 0 and we have nothing to go on + } + + void SetDefaultRangeFixedPoint(web::Input & input, const int val) { + // Default step is 1, which should be fine for fixed point + + if (val > 0) { + // Assume this is a positive number + input.Min(0); + input.Max(val * 10); + } else if (val < 0) { + input.Min(val * 10); // since val is negative + input.Max(val * -10); + } + + // Otherwise val is 0 and we have nothing to go on + } + + void ExcludeConfig(const std::string setting) { + exclude.insert(setting); + } + + /** + * Arranges config panel based configuration pass to constructor + * + * @param id_prefix string appended to id for each setting group Div + */ + void Setup(const std::string & id_prefix = "settings_") { + for (auto group : config.GetGroupSet()) { + std::string group_name = group->GetName(); + group_divs[group_name] = web::Div(id_prefix + group_name); + settings_div << group_divs[group_name]; + + // Prefab Card + prefab::Card card("INIT_OPEN"); + group_divs[group_name] << card; + + // Header content + web::Div setting_heading; + card.AddHeaderContent(setting_heading); + setting_heading << "

" + group->GetDesc() + "

"; + setting_heading.SetAttr("class", "setting_heading"); + + for (size_t i = 0; i < group->GetSize(); i++) { + std::string name = group->GetEntry(i)->GetName(); + if (Has(exclude, name)) { + continue; + } + std::string type = group->GetEntry(i)->GetType(); + std::string value = group->GetEntry(i)->GetValue(); + + card.AddBodyContent(input_divs[name]); + + // Setting element label + web::Div setting_element(name + "_row"); + web::Element title("button"); + title.SetAttr("class", "title_area"); + AddSettingLabel(name, setting_element, title); + + // Prefab Dropdown Box + prefab::CommentBox box; + box.AddContent(group->GetEntry(i)->GetDescription()); + + // Prefab Collapse/toggle for setting element + prefab::CollapseCoupling title_toggle(title, box, false, name + "_dropdown"); + input_divs[name] << title_toggle.GetTargetDiv(0); + setting_element << title_toggle.GetControllerDiv(0); + + /* + * There are 3 categories of settings that can be + * added to a Config Panel: numeric, boolean, and text values. + * Each category has unique components that should be + * added for a setting of that type. + * + * Numeric values (ints, doubles, floats) should have a slider + * for the desktop and mobile view as well as a numerical + * input for the user to type their value. + * + * Boolean values should have a prefab toggle switch. + * + * Text values should have a textbox for the user to + * type their input. + */ + if (Has(numeric_types, type)) { + AddNumericSetting(name, setting_element, box, type, value); + } else if (type == "bool") { + AddBoolSetting(name, setting_element, value); + } else { + AddTextSetting(name, setting_element); + } + } + } + } + + /// @return Div containing the entire config panel + web::Div & GetConfigPanelDiv() { return settings_div; } + + }; +} +} + +#endif diff --git a/include/emp/prefab/DefaultPrefabStyles.less b/include/emp/prefab/DefaultPrefabStyles.less new file mode 100644 index 0000000000..28da9a7d76 --- /dev/null +++ b/include/emp/prefab/DefaultPrefabStyles.less @@ -0,0 +1,139 @@ +/* + * These are the default styles for prefab elements. You can + * override any of these settings in your own CSS file as long + * as it's linked after the this default CSS file in the + * head of the HTML document. + * + * Using LESS CSS in this file. It uses syntax very similar to + * CSS, but allows us to reduce duplicate code by adding variables + * and compound classes (mixins). More cool features of LESS can + * be found here: http://lesscss.org/ + * + * NOTE: If you modify this file, run the purge_script.sh script + * to see your changes take effect in the browser. + * (Empirical/include/emp/prefab/purge_script.sh) + */ + +@CardHeaderTextColor: #3a3939; +@CardHeaderHoverColor: #C0C0C0; +@CommentBoxColor: #ede9e8; + +/* Glyphicon Toggle */ +.collapse_toggle[aria-expanded=true] .fa-angle-double-down { + display: none; +} +.collapse_toggle[aria-expanded=true] .fa-angle-double-right { + display: none; +} +.collapse_toggle[aria-expanded=false] .fa-angle-double-up { + display: none; +} +.toggle_icon_right_margin { + margin-right: 10px; +} + +.collapse_toggle { + .fa:hover { + text-decoration: underline; + } +} + +.toggle_glyph { + .collapse_toggle(); + .setting_heading(); + float: right; +} + +/* Card */ +.card{ + margin-bottom: 10px; +} + +/* Card Header */ +.setting_heading{ + color: @CardHeaderTextColor; +} + +/* Change background of card header on hover to convey collapsibility */ +.collapse_toggle_card_header:hover { + background-color: @CardHeaderHoverColor; +} + +/* Card Body */ +.setting_element { + width: 100%; +} +.title_area { + width: 38%; + display: inline-block; +} +.btn { + text-align: left; +} +/* Form Inputs */ +.input_slider { + width: 38%; + margin-right: 10px; +} +.input_number { + width: 18%; + right: 10px; +} +.input_text { + width: 57%; +} +.input_bool { + width: 12%; + float: right; +} + +/* Comment Box */ +.commentbox_triangle { + width: 0; + height: 0; + border-left: 12px solid transparent; + border-right: 12px solid transparent; + + border-bottom: 12px solid @CommentBoxColor; + margin-left: 15px; +} +.commentbox_content { + padding: 10px; + background-color: @CommentBoxColor; + border-radius: 5px; + margin-bottom: 10px; + width: 95%; +} +.mobile_commentbox { + display: none; +} + +/* Loading modal */ +.bd-example-modal-lg .modal-dialog { + display: table; + position: relative; + margin: 0 auto; + top: calc(50% - 24px); +} + +.bd-example-modal-lg .modal-dialog .modal-content { + background-color: transparent; + border: none; +} + +/* Mobile Version */ +@media screen and (max-width: 992px) { + .input_slider { + display: none; + } + /* Dropdown Bubble */ + .mobile_commentbox { + display: inline; + } + .title_area { + width: 80%; + } + .input_bool { + width: 18%; + } +} diff --git a/include/emp/prefab/FontAwesomeIcon.hpp b/include/emp/prefab/FontAwesomeIcon.hpp new file mode 100644 index 0000000000..d2d7836593 --- /dev/null +++ b/include/emp/prefab/FontAwesomeIcon.hpp @@ -0,0 +1,43 @@ +#ifndef EMP_FONT_AWESOME_ICON_HPP +#define EMP_FONT_AWESOME_ICON_HPP + +#include "../tools/string_utils.hpp" +#include "../web/Element.hpp" +#include "../web/Div.hpp" +#include "../web/Widget.hpp" + +namespace emp { +namespace prefab { + /** + * Use FontAwesomeIcon class to add a glyph from the + * FontAwesome library to your web app. + * (https://fontawesome.com/v4.7.0/icons/) + */ + class FontAwesomeIcon: public web::Element { + public: + /** + * @param fa_name the font awesome class of the desired icon + * @param id optional unique id the icon can be referenced by + */ + + FontAwesomeIcon(const std::string fa_name, const std::string & id="") + : web::Element("span", id) { + std::string full_class = emp::to_string("fa ", fa_name); + this->SetAttr("class", full_class); + } + + /* + * TODO: Prevent user from streaming content into the icon, throw error + * The method below throws an error when trying to stream + * a code block into anything else (web element, html). + * + * template + * void operator<<(T invalid) { + * emp::LibraryError("Not allowed to add code to the FontAwesome icon"); + * } + */ + }; +} +} + +#endif diff --git a/include/emp/prefab/HighlightJS.js b/include/emp/prefab/HighlightJS.js new file mode 100644 index 0000000000..e2f4f43fe5 --- /dev/null +++ b/include/emp/prefab/HighlightJS.js @@ -0,0 +1 @@ +hljs.initHighlightingOnLoad(); \ No newline at end of file diff --git a/include/emp/prefab/LoadingIcon.hpp b/include/emp/prefab/LoadingIcon.hpp new file mode 100644 index 0000000000..8407d20bf8 --- /dev/null +++ b/include/emp/prefab/LoadingIcon.hpp @@ -0,0 +1,45 @@ +#ifndef EMP_LOADING_ICON_HPP +#define EMP_LOADING_ICON_HPP + +#include "../base/errors.hpp" +#include "../tools/string_utils.hpp" +#include "../web/Element.hpp" +#include "../web/Div.hpp" +#include "../web/Widget.hpp" + +namespace emp { +namespace prefab { + /** + * Use LoadingIcon class to add a loading glyph from the + * FontAwesome library to your web app. + * (https://fontawesome.com/v4.7.0/icon/spinner) + */ + class LoadingIcon: public web::Element { + private: + web::Element icon{emp::to_string("span")}; // Spinning icon, necessary classes will be added + web::Element text{emp::to_string("span")}; // Alternative text + + public: + LoadingIcon(const std::string & id=""): web::Element("span", id) { + *this << icon; + *this << text; + icon.SetAttr("class", "fa fa-spinner fa-pulse fa-3x fa-fw"); + text.SetAttr("class", "sr-only"); + text << "Loading..."; + } + + /* + * TODO: Prevent user from streaming content into the icon, throw error + * The method below throws an error when trying to stream + * a code block into anything else (web element, html). + * + * template + * void operator<<(T invalid) { + * emp::LibraryError("Not allowed to add code to the loading icon"); + * } + */ + }; +} +} + +#endif diff --git a/include/emp/prefab/LoadingModal.hpp b/include/emp/prefab/LoadingModal.hpp new file mode 100644 index 0000000000..3fa4e819f5 --- /dev/null +++ b/include/emp/prefab/LoadingModal.hpp @@ -0,0 +1,34 @@ +#ifndef EMP_LOADING_MODAL_HPP +#define EMP_LOADING_MODAL_HPP + +/** + * To add a loading modal to your web page, you must + * link the LoadingModal.js script directly after the + * body tag in the HTML doc. To close the script, call + * CloseLoadingModal() at the end of your .cc file, or + * at the point which you wish to loading modal to disappear. + * + * See https://devosoft.github.io/empirical-prefab-demo/empirical-prefab-demo + * for more details. + */ +#include "../tools/string_utils.hpp" +#include "../web/Element.hpp" +#include "../web/Div.hpp" +#include "../web/Widget.hpp" + +namespace emp { +namespace prefab { + /** + * This method does not belong to a class, but it is used + * to close a loading modal that is added with the + * LoadingModal.js script. See the prefab demo site + * for more details on how to implement the Loading Modal + * in your web app. https://devosoft.github.io/empirical-prefab-demo + */ + void CloseLoadingModal() { + emscripten_run_script("CloseLoadingModal();"); + } +} +} + +#endif diff --git a/include/emp/prefab/LoadingModal.js b/include/emp/prefab/LoadingModal.js new file mode 100644 index 0000000000..b13a92669f --- /dev/null +++ b/include/emp/prefab/LoadingModal.js @@ -0,0 +1,46 @@ +/** + * To add a loading modal to your web page while the content is rendering, + * place this script just below the opening body tag in your HTML file: + * + * Don't forget to close the loading modal with the emp::prefab::CloseLoadingModal() function. + * + * See https://devosoft.github.io/empirical-prefab-demo/empirical-prefab-demo for more details. + * + * @note If you modify this file, run the purge_script.sh script + * to see your changes take effect in the browser + */ + +// Loading modal html +const modal_html = '