diff --git a/.gitattributes b/.gitattributes index c7d9f3332a950355d5a77d85000f05e6f45435ea..f2ef9cddf9b5be424ea927f222586efe437708fd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -27,8 +27,12 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.tar.* filter=lfs diff=lfs merge=lfs -text *.tflite filter=lfs diff=lfs merge=lfs -text *.tgz filter=lfs diff=lfs merge=lfs -text -*.wasm filter=lfs diff=lfs merge=lfs -text *.xz filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +*.whl filter=lfs diff=lfs merge=lfs -text +*.data filter=lfs diff=lfs merge=lfs -text +*.asm.wasm filter=lfs diff=lfs merge=lfs -text +*.wasm filter=lfs diff=lfs merge=lfs -text +*asm.wasm filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..f5e96dbfaec8bd23554e839a582259cf17837f26 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +venv \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..c980515027c92c7a705d1458a864a736d4ddc976 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +# read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker +# you will also find guides on how best to write your Dockerfile + +FROM python:3.9 + +WORKDIR /code + +COPY . . + +RUN pip install --no-cache-dir --upgrade -r requirements.txt + + +# Change the port number of our Wave app to 7860 +# which is default in Hugging Face Spaces. +ENV H2O_WAVE_LISTEN=":7860" +ENV H2O_WAVE_ADDRESS='http://127.0.0.1:7860' + +CMD ["wave", "run", "examples.tour", "--no-reload"] \ No newline at end of file diff --git a/examples/.DS_Store b/examples/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 Binary files /dev/null and b/examples/.DS_Store differ diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..0b84df0f0253112e776e30cc1b7f1b7b9182e8b6 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1 @@ +*.html \ No newline at end of file diff --git a/examples/.python-version b/examples/.python-version new file mode 100644 index 0000000000000000000000000000000000000000..d20cc2bf020ea4d4e6b4237229024d03130d0203 --- /dev/null +++ b/examples/.python-version @@ -0,0 +1 @@ +3.8.10 diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/examples/__pycache__/__init__.cpython-37.pyc b/examples/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a206cd01abc541956ced0329dd3b0f11b766e224 Binary files /dev/null and b/examples/__pycache__/__init__.cpython-37.pyc differ diff --git a/examples/__pycache__/__init__.cpython-39.pyc b/examples/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c93051bc147e18348d4354c1be1686a6c0deabc Binary files /dev/null and b/examples/__pycache__/__init__.cpython-39.pyc differ diff --git a/examples/__pycache__/audio_annotator.cpython-39.pyc b/examples/__pycache__/audio_annotator.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0f9ffb0e90ac05dd2fee348ff8e2fdb2f470cb4e Binary files /dev/null and b/examples/__pycache__/audio_annotator.cpython-39.pyc differ diff --git a/examples/__pycache__/button.cpython-39.pyc b/examples/__pycache__/button.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4830e00c0942b7593b5df0c9a821d845fa120dfc Binary files /dev/null and b/examples/__pycache__/button.cpython-39.pyc differ diff --git a/examples/__pycache__/chatbot.cpython-38.pyc b/examples/__pycache__/chatbot.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..52c9f50a706aac0ad30e14e6b7e5e4844b88d00a Binary files /dev/null and b/examples/__pycache__/chatbot.cpython-38.pyc differ diff --git a/examples/__pycache__/chatbot.cpython-39.pyc b/examples/__pycache__/chatbot.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..14e1612709ad07f01a325347bf66056ab057b405 Binary files /dev/null and b/examples/__pycache__/chatbot.cpython-39.pyc differ diff --git a/examples/__pycache__/chatbot_stream.cpython-39.pyc b/examples/__pycache__/chatbot_stream.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8647ed19a6d8836be33de1b8eff749618447cb39 Binary files /dev/null and b/examples/__pycache__/chatbot_stream.cpython-39.pyc differ diff --git a/examples/__pycache__/choice_group.cpython-39.pyc b/examples/__pycache__/choice_group.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4539373f2cfe7c3d703b0c35435e62bdaef4b7e Binary files /dev/null and b/examples/__pycache__/choice_group.cpython-39.pyc differ diff --git a/examples/__pycache__/color_picker.cpython-39.pyc b/examples/__pycache__/color_picker.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6688d15dbb1c4cc67b3a8df3426676f80bfaab11 Binary files /dev/null and b/examples/__pycache__/color_picker.cpython-39.pyc differ diff --git a/examples/__pycache__/combobox.cpython-37.pyc b/examples/__pycache__/combobox.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a82e1bbd5dae2c29b377f2f720ba0ae913f08c48 Binary files /dev/null and b/examples/__pycache__/combobox.cpython-37.pyc differ diff --git a/examples/__pycache__/combobox.cpython-39.pyc b/examples/__pycache__/combobox.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46df061462ec3f06f276a91f5fb5e9c5edc5401f Binary files /dev/null and b/examples/__pycache__/combobox.cpython-39.pyc differ diff --git a/examples/__pycache__/copyable_text.cpython-39.pyc b/examples/__pycache__/copyable_text.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f3e0923f95cc52181ac81c0d1d07721a82b2ceb Binary files /dev/null and b/examples/__pycache__/copyable_text.cpython-39.pyc differ diff --git a/examples/__pycache__/counter_broadcast.cpython-39.pyc b/examples/__pycache__/counter_broadcast.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..821a771fdbc8d4baf876ac49cf7a93700dd26287 Binary files /dev/null and b/examples/__pycache__/counter_broadcast.cpython-39.pyc differ diff --git a/examples/__pycache__/counter_multicast.cpython-39.pyc b/examples/__pycache__/counter_multicast.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a372e3c645a976540739ec87dabf52f9ff051914 Binary files /dev/null and b/examples/__pycache__/counter_multicast.cpython-39.pyc differ diff --git a/examples/__pycache__/counter_unicast.cpython-39.pyc b/examples/__pycache__/counter_unicast.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..69bbe09a7d7967a8d75cb9445c94d33a0dce4021 Binary files /dev/null and b/examples/__pycache__/counter_unicast.cpython-39.pyc differ diff --git a/examples/__pycache__/demo.cpython-39.pyc b/examples/__pycache__/demo.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b91bdcfdb4ed0c2b1ed4040acfd2043167ca469a Binary files /dev/null and b/examples/__pycache__/demo.cpython-39.pyc differ diff --git a/examples/__pycache__/dropdown.cpython-37.pyc b/examples/__pycache__/dropdown.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a790f322358ef17e7060ef91814bd980224635d Binary files /dev/null and b/examples/__pycache__/dropdown.cpython-37.pyc differ diff --git a/examples/__pycache__/dropdown.cpython-39.pyc b/examples/__pycache__/dropdown.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..733e75eb2088b6d244ab9a7b3f6fee6cfc07391c Binary files /dev/null and b/examples/__pycache__/dropdown.cpython-39.pyc differ diff --git a/examples/__pycache__/file_stream.cpython-39.pyc b/examples/__pycache__/file_stream.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c0285d42b0697a32e98c01715e5194a69046dc9 Binary files /dev/null and b/examples/__pycache__/file_stream.cpython-39.pyc differ diff --git a/examples/__pycache__/file_upload.cpython-37.pyc b/examples/__pycache__/file_upload.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1299c15cd7f8aa947c823334db24d7ade8da96df Binary files /dev/null and b/examples/__pycache__/file_upload.cpython-37.pyc differ diff --git a/examples/__pycache__/file_upload.cpython-39.pyc b/examples/__pycache__/file_upload.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1fd7cdc5f288e8be9e551037b283bd336f6631d0 Binary files /dev/null and b/examples/__pycache__/file_upload.cpython-39.pyc differ diff --git a/examples/__pycache__/file_upload_compact.cpython-39.pyc b/examples/__pycache__/file_upload_compact.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf78bbfdb13f28e4b17f3e1ea2508b43e7001da3 Binary files /dev/null and b/examples/__pycache__/file_upload_compact.cpython-39.pyc differ diff --git a/examples/__pycache__/form_menu.cpython-39.pyc b/examples/__pycache__/form_menu.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cdd947556c04a2b723d9909b5124e8c59b8330c3 Binary files /dev/null and b/examples/__pycache__/form_menu.cpython-39.pyc differ diff --git a/examples/__pycache__/form_visibility.cpython-39.pyc b/examples/__pycache__/form_visibility.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..228cb6d3d88f2cdaf71884517140beb75e42c77a Binary files /dev/null and b/examples/__pycache__/form_visibility.cpython-39.pyc differ diff --git a/examples/__pycache__/graphics_path.cpython-39.pyc b/examples/__pycache__/graphics_path.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1375e7384c528c903716af55962b7851563b6031 Binary files /dev/null and b/examples/__pycache__/graphics_path.cpython-39.pyc differ diff --git a/examples/__pycache__/hash_routing.cpython-39.pyc b/examples/__pycache__/hash_routing.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e74d50e32935eef932c4685fa66ea6482151a14 Binary files /dev/null and b/examples/__pycache__/hash_routing.cpython-39.pyc differ diff --git a/examples/__pycache__/header.cpython-39.pyc b/examples/__pycache__/header.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0edab5f657efacd58fc5414bcf2d9d13757922a8 Binary files /dev/null and b/examples/__pycache__/header.cpython-39.pyc differ diff --git a/examples/__pycache__/http_client.cpython-39.pyc b/examples/__pycache__/http_client.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..898189481900b01cae45657bf968a997b72ca9ff Binary files /dev/null and b/examples/__pycache__/http_client.cpython-39.pyc differ diff --git a/examples/__pycache__/image_annotator.cpython-37.pyc b/examples/__pycache__/image_annotator.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..540cc49efa9b60a6ac4d734851a3767974d9401a Binary files /dev/null and b/examples/__pycache__/image_annotator.cpython-37.pyc differ diff --git a/examples/__pycache__/image_annotator.cpython-39.pyc b/examples/__pycache__/image_annotator.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c931e02cc6355b810caabcab359244b612ed5627 Binary files /dev/null and b/examples/__pycache__/image_annotator.cpython-39.pyc differ diff --git a/examples/__pycache__/image_popup.cpython-39.pyc b/examples/__pycache__/image_popup.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..28f4a4f7cb50383a24c346739cd0602e02803dc9 Binary files /dev/null and b/examples/__pycache__/image_popup.cpython-39.pyc differ diff --git a/examples/__pycache__/inline.cpython-39.pyc b/examples/__pycache__/inline.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..634ef91c3d29ddae9129473652dae8d07aed25ba Binary files /dev/null and b/examples/__pycache__/inline.cpython-39.pyc differ diff --git a/examples/__pycache__/issue_tracker.cpython-39.pyc b/examples/__pycache__/issue_tracker.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b61b19dfee97c8b60ec1e9b020956f13ff3e3fd Binary files /dev/null and b/examples/__pycache__/issue_tracker.cpython-39.pyc differ diff --git a/examples/__pycache__/link.cpython-39.pyc b/examples/__pycache__/link.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40fb2acfede3ccc09621c77ed329281ae61f26d0 Binary files /dev/null and b/examples/__pycache__/link.cpython-39.pyc differ diff --git a/examples/__pycache__/markdown_submit_text.cpython-39.pyc b/examples/__pycache__/markdown_submit_text.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..447a1c3ea469419cd303866ad37d7840fa98979f Binary files /dev/null and b/examples/__pycache__/markdown_submit_text.cpython-39.pyc differ diff --git a/examples/__pycache__/menu.cpython-37.pyc b/examples/__pycache__/menu.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c32837701e0a44b8d5bbaf5b0b6ab7bdd79ca9c Binary files /dev/null and b/examples/__pycache__/menu.cpython-37.pyc differ diff --git a/examples/__pycache__/menu.cpython-39.pyc b/examples/__pycache__/menu.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24a00e62cfac62852eb231c3c6a73f79b60bcc8a Binary files /dev/null and b/examples/__pycache__/menu.cpython-39.pyc differ diff --git a/examples/__pycache__/meta_dialog.cpython-39.pyc b/examples/__pycache__/meta_dialog.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b07fe337086c5214ad035f129f4350824366a20a Binary files /dev/null and b/examples/__pycache__/meta_dialog.cpython-39.pyc differ diff --git a/examples/__pycache__/meta_dialog_closable.cpython-39.pyc b/examples/__pycache__/meta_dialog_closable.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..926ad89df7bbbbdc03306a6e076eb40a965e27e2 Binary files /dev/null and b/examples/__pycache__/meta_dialog_closable.cpython-39.pyc differ diff --git a/examples/__pycache__/meta_notification_bar.cpython-39.pyc b/examples/__pycache__/meta_notification_bar.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9a4a8d4d1bd8552a6566429e9f8e698540f30e5 Binary files /dev/null and b/examples/__pycache__/meta_notification_bar.cpython-39.pyc differ diff --git a/examples/__pycache__/meta_notification_bar_closable.cpython-39.pyc b/examples/__pycache__/meta_notification_bar_closable.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a60e5e279f00432bdecb78c31108a7acf67e090 Binary files /dev/null and b/examples/__pycache__/meta_notification_bar_closable.cpython-39.pyc differ diff --git a/examples/__pycache__/meta_theme.cpython-39.pyc b/examples/__pycache__/meta_theme.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bea88592bca23fe479cce1760157818225ec4c2e Binary files /dev/null and b/examples/__pycache__/meta_theme.cpython-39.pyc differ diff --git a/examples/__pycache__/nav.cpython-39.pyc b/examples/__pycache__/nav.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6da5a0ed9492a9ad9417e24d8047e56515bd1a13 Binary files /dev/null and b/examples/__pycache__/nav.cpython-39.pyc differ diff --git a/examples/__pycache__/persona.cpython-39.pyc b/examples/__pycache__/persona.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b966350f4cf8bd92da2f2563dc63f02f53c2afee Binary files /dev/null and b/examples/__pycache__/persona.cpython-39.pyc differ diff --git a/examples/__pycache__/plot_app.cpython-39.pyc b/examples/__pycache__/plot_app.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d382fe965dbebd482d12bf4ca5bee0f3ca703b3 Binary files /dev/null and b/examples/__pycache__/plot_app.cpython-39.pyc differ diff --git a/examples/__pycache__/plot_events.cpython-39.pyc b/examples/__pycache__/plot_events.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22edbeb54e9e13e2442a9a0f21625636cffea41f Binary files /dev/null and b/examples/__pycache__/plot_events.cpython-39.pyc differ diff --git a/examples/__pycache__/plot_form.cpython-39.pyc b/examples/__pycache__/plot_form.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..828f4ad6412b55f35c2fa120e608fa13a9f2b907 Binary files /dev/null and b/examples/__pycache__/plot_form.cpython-39.pyc differ diff --git a/examples/__pycache__/plot_line.cpython-39.pyc b/examples/__pycache__/plot_line.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a355dd0c36d5ab052752c9cdf658c07b2d84d57b Binary files /dev/null and b/examples/__pycache__/plot_line.cpython-39.pyc differ diff --git a/examples/__pycache__/plot_matplotlib.cpython-37.pyc b/examples/__pycache__/plot_matplotlib.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e3335f3431b06a99cfbf55f3efdec3cc91dece4 Binary files /dev/null and b/examples/__pycache__/plot_matplotlib.cpython-37.pyc differ diff --git a/examples/__pycache__/plot_plotly.cpython-39.pyc b/examples/__pycache__/plot_plotly.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d67da54e35dd23b25c25e70d1ca9ef2f77beb6a Binary files /dev/null and b/examples/__pycache__/plot_plotly.cpython-39.pyc differ diff --git a/examples/__pycache__/plot_theme.cpython-39.pyc b/examples/__pycache__/plot_theme.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ab7fe60fb47a5ec5cb296649d97f945dea82897 Binary files /dev/null and b/examples/__pycache__/plot_theme.cpython-39.pyc differ diff --git a/examples/__pycache__/plot_vegalite_update.cpython-39.pyc b/examples/__pycache__/plot_vegalite_update.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08537deab13c276cbe6a88096b2601b6b0b2eed9 Binary files /dev/null and b/examples/__pycache__/plot_vegalite_update.cpython-39.pyc differ diff --git a/examples/__pycache__/pycon.cpython-39.pyc b/examples/__pycache__/pycon.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..249d5d6e30468362eafcd972b74fa1be81abd4a3 Binary files /dev/null and b/examples/__pycache__/pycon.cpython-39.pyc differ diff --git a/examples/__pycache__/site_async.cpython-39.pyc b/examples/__pycache__/site_async.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7437ce43c31af8d2b9858ebcc5ee5b75e315fb5 Binary files /dev/null and b/examples/__pycache__/site_async.cpython-39.pyc differ diff --git a/examples/__pycache__/slider.cpython-39.pyc b/examples/__pycache__/slider.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2f8809e798e70f361eb73de97a482a86ad940dbf Binary files /dev/null and b/examples/__pycache__/slider.cpython-39.pyc differ diff --git a/examples/__pycache__/spinbox.cpython-39.pyc b/examples/__pycache__/spinbox.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5cb22952ff848de2c670132843da260b77c2d642 Binary files /dev/null and b/examples/__pycache__/spinbox.cpython-39.pyc differ diff --git a/examples/__pycache__/stepper.cpython-37.pyc b/examples/__pycache__/stepper.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df2d1c4d7ddce1be236bbb6bd730e7f48b2f406d Binary files /dev/null and b/examples/__pycache__/stepper.cpython-37.pyc differ diff --git a/examples/__pycache__/swatch_picker.cpython-39.pyc b/examples/__pycache__/swatch_picker.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c71383f77ecc6307af0867ea68d600e3871d1dc5 Binary files /dev/null and b/examples/__pycache__/swatch_picker.cpython-39.pyc differ diff --git a/examples/__pycache__/synth.cpython-37.pyc b/examples/__pycache__/synth.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c11ee1231409eeaa9df315e50319e3d0aecaacf Binary files /dev/null and b/examples/__pycache__/synth.cpython-37.pyc differ diff --git a/examples/__pycache__/synth.cpython-39.pyc b/examples/__pycache__/synth.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9cc2d1b60b721c5213470ec0f765932c3d614c8 Binary files /dev/null and b/examples/__pycache__/synth.cpython-39.pyc differ diff --git a/examples/__pycache__/tab.cpython-39.pyc b/examples/__pycache__/tab.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2bbe658e0152e38a2fdfeee593af541ae5eabcb Binary files /dev/null and b/examples/__pycache__/tab.cpython-39.pyc differ diff --git a/examples/__pycache__/table.cpython-37.pyc b/examples/__pycache__/table.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15e4196178122a8081793b789db11737be7b1564 Binary files /dev/null and b/examples/__pycache__/table.cpython-37.pyc differ diff --git a/examples/__pycache__/table.cpython-39.pyc b/examples/__pycache__/table.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..577105b9c8560d455d82aa7c94656268b0bd4f8a Binary files /dev/null and b/examples/__pycache__/table.cpython-39.pyc differ diff --git a/examples/__pycache__/table_column_alignment.cpython-39.pyc b/examples/__pycache__/table_column_alignment.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c34bd15dd5d7726fb7132c51379ee19fa53a6c8 Binary files /dev/null and b/examples/__pycache__/table_column_alignment.cpython-39.pyc differ diff --git a/examples/__pycache__/table_download.cpython-39.pyc b/examples/__pycache__/table_download.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33146e91c3656bfe4e2ba79215e5aa1ecd3cf8c5 Binary files /dev/null and b/examples/__pycache__/table_download.cpython-39.pyc differ diff --git a/examples/__pycache__/table_filter.cpython-39.pyc b/examples/__pycache__/table_filter.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a05c340e5b3cadd58223b49b25d2915316f24aac Binary files /dev/null and b/examples/__pycache__/table_filter.cpython-39.pyc differ diff --git a/examples/__pycache__/table_groupby.cpython-39.pyc b/examples/__pycache__/table_groupby.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a17f99373d2bab65c80644a4abcb4052d0fa611b Binary files /dev/null and b/examples/__pycache__/table_groupby.cpython-39.pyc differ diff --git a/examples/__pycache__/table_markdown.cpython-37.pyc b/examples/__pycache__/table_markdown.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b896623607da04c9bb1b219447f98f4b23ad14e0 Binary files /dev/null and b/examples/__pycache__/table_markdown.cpython-37.pyc differ diff --git a/examples/__pycache__/table_markdown.cpython-39.pyc b/examples/__pycache__/table_markdown.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26715bdda9478d4bd7e65a36829d3e29312ccf3c Binary files /dev/null and b/examples/__pycache__/table_markdown.cpython-39.pyc differ diff --git a/examples/__pycache__/table_markdown_overflow.cpython-39.pyc b/examples/__pycache__/table_markdown_overflow.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5fa06e38f573a3ffb63b80e2cb81bfdf1689c138 Binary files /dev/null and b/examples/__pycache__/table_markdown_overflow.cpython-39.pyc differ diff --git a/examples/__pycache__/table_menu.cpython-39.pyc b/examples/__pycache__/table_menu.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..762a7b03a2c4fa2ebf71c56a39b11eddb7a7bcac Binary files /dev/null and b/examples/__pycache__/table_menu.cpython-39.pyc differ diff --git a/examples/__pycache__/table_pagination.cpython-39.pyc b/examples/__pycache__/table_pagination.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca9555c861ffefa80c0ea09ca9944acb27e1b25c Binary files /dev/null and b/examples/__pycache__/table_pagination.cpython-39.pyc differ diff --git a/examples/__pycache__/table_pagination_download.cpython-39.pyc b/examples/__pycache__/table_pagination_download.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2cdad0136ee8a53c4c3a744da75b216b403063d2 Binary files /dev/null and b/examples/__pycache__/table_pagination_download.cpython-39.pyc differ diff --git a/examples/__pycache__/table_pagination_groups.cpython-39.pyc b/examples/__pycache__/table_pagination_groups.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a53939c172b98672484c63bea06760047cbb2e26 Binary files /dev/null and b/examples/__pycache__/table_pagination_groups.cpython-39.pyc differ diff --git a/examples/__pycache__/table_pagination_wavedb.cpython-37.pyc b/examples/__pycache__/table_pagination_wavedb.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f22d71fdba8c08381bac22ee5c6fcf2ee1eb4a65 Binary files /dev/null and b/examples/__pycache__/table_pagination_wavedb.cpython-37.pyc differ diff --git a/examples/__pycache__/table_search.cpython-39.pyc b/examples/__pycache__/table_search.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a5a896df9caed1d23d01e4318a511fbe02c44c5c Binary files /dev/null and b/examples/__pycache__/table_search.cpython-39.pyc differ diff --git a/examples/__pycache__/table_search_groupable.cpython-39.pyc b/examples/__pycache__/table_search_groupable.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db19233fd156d48120a36b95589f7cf225fc4292 Binary files /dev/null and b/examples/__pycache__/table_search_groupable.cpython-39.pyc differ diff --git a/examples/__pycache__/table_select_single.cpython-39.pyc b/examples/__pycache__/table_select_single.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4976ddc53030ba2354cd3508ba74a45e58c2401f Binary files /dev/null and b/examples/__pycache__/table_select_single.cpython-39.pyc differ diff --git a/examples/__pycache__/table_tags.cpython-39.pyc b/examples/__pycache__/table_tags.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e08be0bfc225098a63acded5a7f40443b29fb6a9 Binary files /dev/null and b/examples/__pycache__/table_tags.cpython-39.pyc differ diff --git a/examples/__pycache__/tabs.cpython-39.pyc b/examples/__pycache__/tabs.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6dd7dfc34131180dd3067a30e876c7fc6604ebca Binary files /dev/null and b/examples/__pycache__/tabs.cpython-39.pyc differ diff --git a/examples/__pycache__/test.cpython-39.pyc b/examples/__pycache__/test.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db99fdcccb837d4bed4faa3e3d3e5d8ad9326638 Binary files /dev/null and b/examples/__pycache__/test.cpython-39.pyc differ diff --git a/examples/__pycache__/text_annotator.cpython-37.pyc b/examples/__pycache__/text_annotator.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..56bfaa9ab4f5f3e5444d34e04f8816fb2eabb9e3 Binary files /dev/null and b/examples/__pycache__/text_annotator.cpython-37.pyc differ diff --git a/examples/__pycache__/text_annotator.cpython-39.pyc b/examples/__pycache__/text_annotator.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da6eb77cef27a8c97f1342501b8bc1d586a2f679 Binary files /dev/null and b/examples/__pycache__/text_annotator.cpython-39.pyc differ diff --git a/examples/__pycache__/textbox.cpython-37.pyc b/examples/__pycache__/textbox.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6959599451913042c748b53d65f4e7a9c87003ba Binary files /dev/null and b/examples/__pycache__/textbox.cpython-37.pyc differ diff --git a/examples/__pycache__/textbox.cpython-39.pyc b/examples/__pycache__/textbox.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b3951b0620e676a962b5a5c6e5b6862891793cbf Binary files /dev/null and b/examples/__pycache__/textbox.cpython-39.pyc differ diff --git a/examples/__pycache__/theme_generator.cpython-39.pyc b/examples/__pycache__/theme_generator.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15f8de1539ac315a0ddb2975cfac135eb18b7f75 Binary files /dev/null and b/examples/__pycache__/theme_generator.cpython-39.pyc differ diff --git a/examples/__pycache__/time_picker.cpython-37.pyc b/examples/__pycache__/time_picker.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfd7020b5d0e16d9b8ab14676a8d73d6531c5d63 Binary files /dev/null and b/examples/__pycache__/time_picker.cpython-37.pyc differ diff --git a/examples/__pycache__/time_picker.cpython-39.pyc b/examples/__pycache__/time_picker.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1e325388d2675f6d0fac14d7ff13342dd5059f7 Binary files /dev/null and b/examples/__pycache__/time_picker.cpython-39.pyc differ diff --git a/examples/__pycache__/todo.cpython-39.pyc b/examples/__pycache__/todo.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38865d8038d74c3530942b25bf4c45a29fa69a24 Binary files /dev/null and b/examples/__pycache__/todo.cpython-39.pyc differ diff --git a/examples/__pycache__/token.cpython-39.pyc b/examples/__pycache__/token.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c6d148bfa86a58a46c6f12f29b06eb4a32575754 Binary files /dev/null and b/examples/__pycache__/token.cpython-39.pyc differ diff --git a/examples/__pycache__/tour.cpython-37.pyc b/examples/__pycache__/tour.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c656205eb289b665b6fa926234cc7b01c6ad4655 Binary files /dev/null and b/examples/__pycache__/tour.cpython-37.pyc differ diff --git a/examples/__pycache__/tour.cpython-39.pyc b/examples/__pycache__/tour.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2f4460bcdf47d069c1cfbbdce22ee0ddffb68d03 Binary files /dev/null and b/examples/__pycache__/tour.cpython-39.pyc differ diff --git a/examples/__pycache__/wizard.cpython-37.pyc b/examples/__pycache__/wizard.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cafce89d7ae7ae65b2333864e32d447d907c6174 Binary files /dev/null and b/examples/__pycache__/wizard.cpython-37.pyc differ diff --git a/examples/__pycache__/wizard.cpython-38.pyc b/examples/__pycache__/wizard.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e53355c0af89cbb0e8c5901d923669398707f9a Binary files /dev/null and b/examples/__pycache__/wizard.cpython-38.pyc differ diff --git a/examples/__pycache__/wizard.cpython-39.pyc b/examples/__pycache__/wizard.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..18f717a176f5a15a074f7ed99d0d7ee9440cd736 Binary files /dev/null and b/examples/__pycache__/wizard.cpython-39.pyc differ diff --git a/examples/article.py b/examples/article.py new file mode 100644 index 0000000000000000000000000000000000000000..7d89b0cce539d38b3374bad60a8b4d376027a69e --- /dev/null +++ b/examples/article.py @@ -0,0 +1,33 @@ +# Article +# Create an article card for longer texts. +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +page.add('example', ui.article_card( + box='1 1 4 6', + title='Title', + items=[ + ui.mini_buttons([ + ui.mini_button(name='like', label='4', icon='Heart'), + ui.mini_button(name='comment', label='2', icon='Blog'), + ui.mini_button(name='share', label='1', icon='Relationship'), + ]) + ], + content=''' +Duis porttitor tincidunt justo ac semper. Vestibulum et molestie lectus. Proin vel eros a ex condimentum aliquam. +Sed accumsan tellus sit amet nulla ullamcorper. Suspendisse bibendum tristique sem, quis lacinia ex pulvinar quis. +Nam elementum accumsan porta. Sed eget aliquam elit, sed luctus lorem. Nulla gravida malesuada purus eu eleifend. +Maecenas in ante interdum, hendrerit velit at, tempus eros. Nullam convallis tempor libero at viverra. + +## Heading 2 + +Duis porttitor tincidunt justo ac semper. Vestibulum et molestie lectus. Proin vel eros a ex condimentum aliquam. +Sed accumsan tellus sit amet nulla ullamcorper. Suspendisse bibendum tristique sem, quis lacinia ex pulvinar quis. +Nam elementum accumsan porta. Sed eget aliquam elit, sed luctus lorem. Nulla gravida malesuada purus eu eleifend. +Maecenas in ante interdum, hendrerit velit at, tempus eros. Nullam convallis tempor libero at viverra. +''' +)) + +page.save() diff --git a/examples/background.py b/examples/background.py new file mode 100644 index 0000000000000000000000000000000000000000..e85a31830d146c916520adcde7257a891278efa1 --- /dev/null +++ b/examples/background.py @@ -0,0 +1,34 @@ +# Background Tasks +# Use q.run() to execute functions in the background, in-process. +# #background_tasks +# --- +import time +import random +from h2o_wave import main, app, Q, ui + + +def blocking_function(secs) -> str: + time.sleep(secs) # Blocks! + return f'Done waiting for {secs} seconds!' + + +@app('/demo') +async def serve(q: Q): + if q.args.start: + q.page['form'] = ui.form_card(box='1 1 6 2', items=[ui.progress('Running...')]) + await q.page.save() + + seconds = random.randint(1, 6) + + # DON'T DO THIS! + # This will make your app unresponsive for some time: + # message = blocking_function(seconds) + + # Do this instead: + message = await q.run(blocking_function, seconds) + + q.page['form'] = ui.form_card(box='1 1 6 1', items=[ui.message_bar('info', message)]) + await q.page.save() + else: + q.page['form'] = ui.form_card(box='1 1 2 1', items=[ui.button(name='start', label='Start')]) + await q.page.save() diff --git a/examples/background_executor.py b/examples/background_executor.py new file mode 100644 index 0000000000000000000000000000000000000000..c831f2cde29052efca06659f462b08d449170fba --- /dev/null +++ b/examples/background_executor.py @@ -0,0 +1,40 @@ +# Background Tasks / Executor +# Use q.exec() to execute background functions using a thread-pool or process-pool. +# #background_tasks #executor +# --- +import time +import random +import concurrent.futures +from h2o_wave import main, app, Q, ui + + +def blocking_function(secs) -> str: + time.sleep(secs) # Blocks! + return f'Done waiting for {secs} seconds!' + + +@app('/demo') +async def serve(q: Q): + if q.args.start: + q.page['form'] = ui.form_card(box='1 1 6 2', items=[ui.progress('Running...')]) + await q.page.save() + + seconds = random.randint(1, 6) + + # DON'T DO THIS! + # This will make your app unresponsive for some time: + # message = blocking_function(seconds) + + # Do this instead: + with concurrent.futures.ThreadPoolExecutor() as pool: + message = await q.exec(pool, blocking_function, seconds) + + # You can also pass a ProcessPoolExecutor, like this: + # with concurrent.futures.ProcessPoolExecutor() as pool: + # message = await q.exec(pool, blocking_function, seconds) + + q.page['form'] = ui.form_card(box='1 1 6 1', items=[ui.message_bar('info', message)]) + await q.page.save() + else: + q.page['form'] = ui.form_card(box='1 1 2 1', items=[ui.button(name='start', label='Start')]) + await q.page.save() diff --git a/examples/background_progress.py b/examples/background_progress.py new file mode 100644 index 0000000000000000000000000000000000000000..f418828ca442257efaf2d2f8878ba6ad3dfba229 --- /dev/null +++ b/examples/background_progress.py @@ -0,0 +1,55 @@ +# Background Tasks / Progress +# Execute background functions while incrementing a #progress bar. +# #background_tasks +# --- +import time +import asyncio +import concurrent.futures +from h2o_wave import main, app, Q, ui + + +# A long-running that performs a blocking operation, in this case time.sleep() +def blocking_function(secs) -> str: + time.sleep(secs) # Blocks! + return 'Download completed!' + + +# An async function that displays a progress bar +async def display_progress_bar(q: Q, form, seconds: int): + for i in range(seconds): + progress_value = (i + 1.0) / seconds + form.items = [ + ui.progress( + label='Downloading the interwebs...', + caption=f'{int(progress_value * 100)}%', + value=progress_value, + ) + ] + await q.page.save() + await q.sleep(1) + + +@app('/demo') +async def serve(q: Q): + if q.args.start: # The button named 'start' was clicked + seconds = 5 + + # Grab a reference to the form + form = q.page['form'] + + # Start incrementing the progress bar in the background + future = asyncio.ensure_future(display_progress_bar(q, form, seconds)) + + # Execute our long-running function in the background + with concurrent.futures.ThreadPoolExecutor() as pool: + message = await q.exec(pool, blocking_function, seconds) + + # Stop the progress bar (optional unless we used a infinite while loop in display_progress_bar()). + future.cancel() + + # At this point, we're done with the long-running function; so display a completion message + form.items = [ui.message_bar('info', message)] + await q.page.save() + else: + q.page['form'] = ui.form_card(box='1 1 2 2', items=[ui.button(name='start', label='Start')]) + await q.page.save() diff --git a/examples/breadcrumbs.py b/examples/breadcrumbs.py new file mode 100644 index 0000000000000000000000000000000000000000..751fe472ac9892709ec54a7d73726af374428301 --- /dev/null +++ b/examples/breadcrumbs.py @@ -0,0 +1,43 @@ +# Breadcrumbs +# #Breadcrumbs should be used as a navigational aid in your app or site. +# They indicate the current page’s location within a hierarchy and help +# the user understand where they are in relation to the rest of that hierarchy. +# They also afford one-click access to higher levels of that hierarchy. +# Breadcrumbs are typically placed, in horizontal form, under the masthead +# or #navigation of an experience, above the primary content area. +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + blurb_items = [ui.button(name='#submenu', label='Go to submenu', link=True)] + blurb_title = 'Welcome to Menu' + breadcrumbs = [ui.breadcrumb(name='#menu', label='Menu')] + + if q.args['#'] == 'menu': + blurb_items = [ui.button(name='#submenu', label='Go to submenu', link=True)] + blurb_title = 'Welcome to Menu!' + breadcrumbs = [ + ui.breadcrumb(name='#menu', label='Menu'), + ] + elif q.args['#'] == 'submenu': + blurb_items = [ui.button(name='#subsubmenu', label='Go to subsubmenu', link=True)] + blurb_title = 'Welcome to Submenu!' + breadcrumbs = [ + ui.breadcrumb(name='#menu', label='Menu'), + ui.breadcrumb(name='#submenu', label='Submenu'), + ] + elif q.args['#'] == 'subsubmenu': + blurb_items = [ui.text('You cannot go deeper, click on Breadcrumbs above to navigate back')] + blurb_title = 'Welcome to Subsubmenu!' + breadcrumbs = [ + ui.breadcrumb(name='#menu', label='Menu'), + ui.breadcrumb(name='#submenu', label='Submenu'), + ui.breadcrumb(name='#subsubmenu', label='Subsubmenu'), + ] + + q.page['blurb'] = ui.form_card(box='1 2 3 2', title=blurb_title, items=blurb_items) + q.page['breadcrumbs'] = ui.breadcrumbs_card(box='1 1 3 1', items=breadcrumbs) + + await q.page.save() diff --git a/examples/button.py b/examples/button.py new file mode 100644 index 0000000000000000000000000000000000000000..2ed1bf4c1d50c79099840ddb4f631f284faf132d --- /dev/null +++ b/examples/button.py @@ -0,0 +1,53 @@ +# Form / Button +# Use #buttons to enable a user to commit a change or complete steps in a task. +# #form +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if 'basic_button' in q.args: + q.page['example'].items = [ + ui.text(f'basic_button={q.args.basic_button}'), + ui.text(f'primary_button={q.args.primary_button}'), + ui.text(f'link_button={q.args.link_button}'), + ui.text(f'basic_disabled_button={q.args.basic_disabled_button}'), + ui.text(f'primary_disabled_button={q.args.primary_disabled_button}'), + ui.text(f'link_disabled_button={q.args.link_disabled_button}'), + ui.text(f'basic_caption_button={q.args.basic_caption_button}'), + ui.text(f'primary_caption_button={q.args.primary_caption_button}'), + ui.text(f'basic_caption_disabled_button={q.args.basic_caption_disabled_button}'), + ui.text(f'primary_caption_disabled_button={q.args.primary_caption_disabled_button}'), + ui.text(f'button_with_icon={q.args.button_with_icon}'), + ui.text(f'icon_button={q.args.icon_button}'), + ui.text(f'external_path_button={q.args.external_path_button}'), + ui.text(f'command_button={q.args.command_button}'), + ui.text(f'command_button:first_command={q.args.first_command}'), + ui.text(f'command_button:second_command={q.args.second_command}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 10', items=[ + ui.button(name='basic_button', label='Basic'), + ui.button(name='primary_button', label='Primary', primary=True), + ui.button(name='link_button', label='Link', link=True), + ui.button(name='basic_disabled_button', label='Basic (Disabled)', disabled=True), + ui.button(name='primary_disabled_button', label='Primary (Disabled)', primary=True, disabled=True), + ui.button(name='link_disabled_button', label='Link (Disabled)', link=True, disabled=True), + ui.button(name='basic_caption_button', label='Basic', caption='Caption Button.'), + ui.button(name='primary_caption_button', label='Primary', caption='Caption Button', primary=True), + ui.button(name='basic_caption_disabled_button', label='Basic (Disabled)', caption='Caption Button', + disabled=True), + ui.button(name='primary_caption_disabled_button', label='Primary (Disabled)', caption='Caption Button', + primary=True, disabled=True), + ui.button(name='button_with_icon', label='Button with an icon', icon='Search'), + ui.button(name='icon_button', icon='Heart', caption='Tooltip text'), + ui.button(name='external_path_button', label='External', path='https://h2o.ai/'), + ui.button(name='command_button', label='Button with commands', primary=True, commands=[ + ui.command(name='first_command', label='First command'), + ui.command(name='second_command', label='Second command'), + ] + ), + ]) + await q.page.save() diff --git a/examples/buttons.py b/examples/buttons.py new file mode 100644 index 0000000000000000000000000000000000000000..9c4411d94d0d5ec63d4a6967e1899b392cfb4cb0 --- /dev/null +++ b/examples/buttons.py @@ -0,0 +1,25 @@ +# Form / Buttons +# Use the `ui.buttons()` function to group related #buttons. +# #form +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if 'standard_button' in q.args: + q.page['example'].items = [ + ui.text(f'primary_button={q.args.primary_button}'), + ui.text(f'standard_button={q.args.standard_button}'), + ui.text(f'standard_disabled_button={q.args.standard_disabled_button}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 5', items=[ + ui.buttons([ + ui.button(name='primary_button', label='Primary', primary=True), + ui.button(name='standard_button', label='Standard'), + ui.button(name='standard_disabled_button', label='Standard', disabled=True), + ]), + ]) + await q.page.save() diff --git a/examples/canvas.py b/examples/canvas.py new file mode 100644 index 0000000000000000000000000000000000000000..e13b25f0a24223c07939e385e068f5750da33eb3 --- /dev/null +++ b/examples/canvas.py @@ -0,0 +1,19 @@ +# Canvas +# A card that displays a freeform drawing canvas. +# A canvas card can synchronize its state with other canvas cards at the same URL. +# Open `/demo` in multiple browsers and watch them synchronize in realtime. +# #collaboration +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] +page.drop() + +page.add('example', ui.canvas_card( + box='1 1 4 7', + title='Sample Canvas', + width=500, + height=500, + data=dict(), +)) +page.save() diff --git a/examples/card_menu.py b/examples/card_menu.py new file mode 100644 index 0000000000000000000000000000000000000000..2018d6d7edb814b61ec375055d15c056065343ca --- /dev/null +++ b/examples/card_menu.py @@ -0,0 +1,71 @@ +# Context Menu +# Display a context menu on a card. +# #context_menu +# --- +import json +from h2o_wave import main, app, Q, ui, data + +# Vega lite spec for a bar plot, defaults to linear scale. +spec_linear_scale = json.dumps(dict( + mark='bar', + encoding=dict( + x=dict(field='a', type='ordinal'), + y=dict(field='b', type='quantitative') + ) +)) + +# Vega lite spec for a bar plot, log scaled. +spec_log_scale = json.dumps(dict( + mark='bar', + encoding=dict( + x=dict(field='a', type='ordinal'), + y=dict(field='b', type='quantitative', scale=dict(type='log')) + ) +)) + +# Data for our plot. +plot_data = data(fields=["a", "b"], rows=[ + ["A", 28], ["B", 55], ["C", 43], + ["D", 91], ["E", 81], ["F", 53], + ["G", 19], ["H", 87], ["I", 52] +]) + +# Create a couple of context menu commands. +log_scale_command = ui.command( + name='to_log_scale', + label='Log Scale', + icon='LineChart', +) +linear_scale_command = ui.command( + name='to_linear_scale', + label='Linear Scale', + icon='LineChart', +) + + +@app('/demo') +async def serve(q: Q): + if q.client.plot_added: # Have we already added a plot? + example = q.page['example'] + if q.args.to_log_scale: + # Change to log scale + example.title = 'Plot (Log Scale)', + example.specification = spec_log_scale + example.commands = [linear_scale_command] + else: + # Change to linear scale + example.title = 'Plot (Linear Scale)', + example.specification = spec_linear_scale + example.commands = [log_scale_command] + else: # Add a new plot + q.page['example'] = ui.vega_card( + box='1 1 2 4', + title='Plot (Linear Scale)', + specification=spec_linear_scale, + data=plot_data, + commands=[log_scale_command], + ) + # Flag to indicate that we've added a plot + q.client.plot_added = True + + await q.page.save() diff --git a/examples/chat_room.py b/examples/chat_room.py new file mode 100644 index 0000000000000000000000000000000000000000..21af5fe119af6e44a7d7ecf4331312da07be71af --- /dev/null +++ b/examples/chat_room.py @@ -0,0 +1,17 @@ +# Chat room +# A card that displays a chat room. +# A chat room card can synchronize its state with other chat room cards at the same URL. +# Open `/demo` in multiple browsers and watch them synchronize in realtime. +# #collaboration +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] +page.drop() + +page.add('example', ui.chat_card( + box='1 1 4 6', + title='Chat room', + data=dict(), +)) +page.save() diff --git a/examples/chatbot.py b/examples/chatbot.py new file mode 100644 index 0000000000000000000000000000000000000000..23fdb5e0584c41668ff3ee51f9d0cb607ecbf538 --- /dev/null +++ b/examples/chatbot.py @@ -0,0 +1,27 @@ +# Chatbot +# Use this card for chat interactions. +# #chat +# --- +from h2o_wave import main, app, Q, ui, data + + +MAX_MESSAGES = 500 + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + # Cyclic buffer drops oldest messages when full. Must have exactly 2 columns - msg and fromUser. + cyclic_buffer = data(fields='msg fromUser', size=-MAX_MESSAGES) + q.page['example'] = ui.chatbot_card(box='1 1 5 5', data=cyclic_buffer, name='chatbot') + q.page['meta'] = ui.meta_card(box='', theme='h2o-dark') + q.client.initialized = True + + # A new message arrived. + if q.args.chatbot: + # Append user message. + q.page['example'].data[-1] = [q.args.chatbot, True] + # Append bot response. + q.page['example'].data[-1] = ['I am a fake chatbot. Sorry, I cannot help you.', False] + + await q.page.save() diff --git a/examples/checkbox.py b/examples/checkbox.py new file mode 100644 index 0000000000000000000000000000000000000000..f632437747ab36c97ae8e2542b61619d8a1bad8a --- /dev/null +++ b/examples/checkbox.py @@ -0,0 +1,31 @@ +# Form / Checkbox +# Use checkboxes to switch between two mutually exclusive options. +# #form #checkbox +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'checkbox_unchecked={q.args.checkbox_unchecked}'), + ui.text(f'checkbox_checked={q.args.checkbox_checked}'), + ui.text(f'checkbox_indeterminate={q.args.checkbox_indeterminate}'), + ui.text(f'checkbox_unchecked_disabled={q.args.checkbox_unchecked_disabled}'), + ui.text(f'checkbox_checked_disabled={q.args.checkbox_checked_disabled}'), + ui.text(f'checkbox_indeterminate_disabled={q.args.checkbox_indeterminate_disabled}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 7', items=[ + ui.checkbox(name='checkbox_unchecked', label='Not checked'), + ui.checkbox(name='checkbox_checked', label='Checked', value=True), + ui.checkbox(name='checkbox_indeterminate', label='Indeterminate', indeterminate=True), + ui.checkbox(name='checkbox_unchecked_disabled', label='Not checked (Disabled)', disabled=True), + ui.checkbox(name='checkbox_checked_disabled', label='Checked (Disabled)', value=True, disabled=True), + ui.checkbox(name='checkbox_indeterminate_disabled', label='Indeterminate (Disabled)', indeterminate=True, + disabled=True), + ui.button(name='show_inputs', label='Submit', primary=True), + ]) + await q.page.save() diff --git a/examples/checklist.py b/examples/checklist.py new file mode 100644 index 0000000000000000000000000000000000000000..f17822594c6c471680b9de086fbd551583909250 --- /dev/null +++ b/examples/checklist.py @@ -0,0 +1,21 @@ +# Form / Checklist +# Use a #checklist to group a set of related checkboxes. +# #form +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'selected={q.args.checklist}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 7', items=[ + ui.checklist(name='checklist', label='Choices', + choices=[ui.choice(name=x, label=x) for x in ['Egg', 'Bacon', 'Spam']]), + ui.button(name='show_inputs', label='Submit', primary=True), + ]) + await q.page.save() diff --git a/examples/checklist_inline.py b/examples/checklist_inline.py new file mode 100644 index 0000000000000000000000000000000000000000..91952c96d4416eef44e7686d0b1bdb9cdcd00e7e --- /dev/null +++ b/examples/checklist_inline.py @@ -0,0 +1,24 @@ +# Form / Checklist / Inline +# Use a #checklist to group a set of related checkboxes. +# #form +# --- +from h2o_wave import main, app, Q, ui + + +choices = ['Egg', 'Bacon', 'Spam', 'Hamburger', 'Banana', 'Orange', 'Strawberry', 'Apple'] + + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'selected={q.args.checklist}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 6', items=[ + ui.checklist(name='checklist', label='Choices', inline=True, + choices=[ui.choice(name=x, label=x) for x in choices]), + ui.button(name='show_inputs', label='Submit', primary=True), + ]) + await q.page.save() diff --git a/examples/checkpoint.py b/examples/checkpoint.py new file mode 100644 index 0000000000000000000000000000000000000000..12201e409e7f3f0cf897baa9b3846982a54e99e2 --- /dev/null +++ b/examples/checkpoint.py @@ -0,0 +1,25 @@ +# Checkpoint +# Set the H2O_WAVE_CHECKPOINT_DIR environment variable to enable saving and reloading application and session state. +# For example, run `checkpoint.py` using `$ H2O_WAVE_CHECKPOINT_DIR=./temp wave run examples.checkpoint`. +# You will observe that every time you stop and restart the application, the counts are preserved. +# #checkpoint +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.increment_app: + q.app.count = (q.app.count or 0) + 1 + elif q.args.increment_user: + q.user.count = (q.user.count or 0) + 1 + elif q.args.increment_client: + q.client.count = (q.client.count or 0) + 1 + + q.page['example'] = ui.form_card(box='1 1 12 10', items=[ + ui.button(name='increment_app', label=f'App Count={q.app.count or 0}'), + ui.button(name='increment_user', label=f'User Count={q.user.count or 0}'), + ui.button(name='increment_client', label=f'Client Count={q.client.count or 0}'), + ]) + + await q.page.save() diff --git a/examples/choice_group.py b/examples/choice_group.py new file mode 100644 index 0000000000000000000000000000000000000000..cb300ed0c90896a214a4e234148ca4489aab3ec5 --- /dev/null +++ b/examples/choice_group.py @@ -0,0 +1,27 @@ +# Form / Choice Group +# Use #choice groups to let users select one option from two or more choices. +# #form #choice_group +# --- +from h2o_wave import main, app, Q, ui + +choices = [ + ui.choice('A', 'Option A'), + ui.choice('B', 'Option B'), + ui.choice('C', 'Option C', disabled=True), + ui.choice('D', 'Option D'), +] + + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'selected={q.args.choice_group}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 7', items=[ + ui.choice_group(name='choice_group', label='Pick one', value='B', required=True, choices=choices), + ui.button(name='show_inputs', label='Submit', primary=True), + ]) + await q.page.save() diff --git a/examples/choice_group_inline.py b/examples/choice_group_inline.py new file mode 100644 index 0000000000000000000000000000000000000000..a5d87e842910521da132e4034fe7c4e26b3ce637 --- /dev/null +++ b/examples/choice_group_inline.py @@ -0,0 +1,32 @@ +# Form / Choice Group / Inline +# Use #choice groups to let users select one option from two or more choices and inline to present the choices horizontally. +# #form #choice_group #inline +# --- +from h2o_wave import main, app, Q, ui + +choices = [ + ui.choice('A', 'Option A'), + ui.choice('B', 'Option B'), + ui.choice('C', 'Option C', disabled=True), + ui.choice('D', 'Option D'), + ui.choice('E', 'Option E'), + ui.choice('F', 'Option F'), + ui.choice('G', 'Option H'), + ui.choice('I', 'Option I'), +] + + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'selected={q.args.choice_group}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 10', items=[ + ui.choice_group(name='choice_group', inline=True, label='Pick one', + value='B', required=True, choices=choices), + ui.button(name='show_inputs', label='Submit', primary=True), + ]) + await q.page.save() diff --git a/examples/color_picker.py b/examples/color_picker.py new file mode 100644 index 0000000000000000000000000000000000000000..3a1d9e37848f07fbcd7b4e242b5589213ed9ace8 --- /dev/null +++ b/examples/color_picker.py @@ -0,0 +1,20 @@ +# Form / Color Picker +# Use a color picker to allow a user to select a color. +# #form #color_picker +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'color={q.args.color}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 7', items=[ + ui.color_picker(name='color', label='Pick a color', value='#F25F5C'), + ui.button(name='show_inputs', label='Submit', primary=True), + ]) + await q.page.save() diff --git a/examples/combobox.py b/examples/combobox.py new file mode 100644 index 0000000000000000000000000000000000000000..d9f59208e08a689eaff6832acd0c6c1e4f04f2ff --- /dev/null +++ b/examples/combobox.py @@ -0,0 +1,35 @@ +# Form / Combobox +# Use comboboxes to allow users to either choose between available choices or indicate a choice by free-form editing. +# #form #combobox +# --- +from h2o_wave import main, app, Q, ui + +combobox_choices = ['Cyan', 'Magenta', 'Yellow', 'Black'] + + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'combobox={q.args.combobox}'), + ui.text(f'combobox_required={q.args.combobox_required}'), + ui.text(f'combobox_disabled={q.args.combobox_disabled}'), + ui.text(f'combobox_error={q.args.combobox_error}'), + ui.text(f'combobox_multivalued={q.args.combobox_multivalued}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 7', items=[ + ui.combobox(name='combobox', label='Enter or choose a color', placeholder='Color...', value='Blue', + choices=combobox_choices), + ui.combobox(name='combobox_required', label='Enter or choose a color', placeholder='Color...', value='Blue', + choices=combobox_choices, required=True), + ui.combobox(name='combobox_disabled', label='Enter or choose a color', placeholder='Color...', value='Blue', + choices=combobox_choices, disabled=True), + ui.combobox(name='combobox_error', label='Enter or choose a color', placeholder='Color...', value='Blue', + choices=combobox_choices, error='This combobox has an error!'), + ui.combobox(name='combobox_multivalued', label='Enter or choose a color (multi select)', placeholder='Color...', values=['Blue', 'Magenta'], + choices=combobox_choices), + ui.button(name='show_inputs', label='Submit', primary=True), + ]) + await q.page.save() diff --git a/examples/combobox_trigger.py b/examples/combobox_trigger.py new file mode 100644 index 0000000000000000000000000000000000000000..d73c5fa17a4c07ed3bb07e609409cfba96505986 --- /dev/null +++ b/examples/combobox_trigger.py @@ -0,0 +1,26 @@ +# Form / Combobox / Trigger +# To handle live changes to a #combobox, enable the `trigger` attribute. +# #combobox #trigger +# --- +from h2o_wave import main, app, Q, ui + +combobox_choices = ['Cyan', 'Magenta', 'Yellow', 'Black'] + + +def get_form_items(choice: str): + return [ + ui.combobox(name='combobox', trigger=True, label='Enter or choose a color', placeholder='Color...', value='Blue', + choices=combobox_choices), + ui.label('Sent to server'), + ui.text(choice), + ] + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['example'] = ui.form_card(box='1 1 4 10', items=get_form_items('')) + q.client.initialized = True + if q.args.combobox is not None: + q.page['example'].items = get_form_items(q.args.combobox) + await q.page.save() diff --git a/examples/copyable_text.py b/examples/copyable_text.py new file mode 100644 index 0000000000000000000000000000000000000000..c49303477ab883d83ed90d4ffc869be58a8e4ccb --- /dev/null +++ b/examples/copyable_text.py @@ -0,0 +1,28 @@ + +# Form / Copyable Text +# Use copyable text component to enable user quick text copy pasting. +# #copyable_text +# --- +from h2o_wave import site, ui + + +multiline_content = '''Wave is truly awesome. +You should try all the features!''' +big_multiline_content = '''Wave is truly awesome. +You should try all the features! +Like having a big height textbox! +... +... +... +... +Woohoo!''' +page = site['/demo'] + +page['hello'] = ui.form_card(box='1 1 3 10', items=[ + ui.copyable_text(label='Copyable text', value='Hello world!'), + ui.copyable_text(label='Multiline Copyable text', value=multiline_content, multiline=True), + ui.copyable_text(label='Multiline Copyable text with height=200px', value=big_multiline_content, multiline=True, height='200px'), + ui.copyable_text(label='Multiline Copyable text filling remaining card space', value=big_multiline_content, multiline=True, height='1'), +]) + +page.save() diff --git a/examples/counter_broadcast.py b/examples/counter_broadcast.py new file mode 100644 index 0000000000000000000000000000000000000000..76b23110c96867cd5464f7dae3d38b037a6beedf --- /dev/null +++ b/examples/counter_broadcast.py @@ -0,0 +1,23 @@ +# Mode / Broadcast +# Launch the server in #broadcast #mode to synchronize browser state across users. +# Open `/demo` in multiple browsers and watch them synchronize in realtime. +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo', mode='broadcast') +async def serve(q: Q): + if not q.client.initialized: + if q.app.count is None: + q.app.count = 0 + + q.page['example'] = ui.form_card(box='1 1 2 1', items=[ + ui.button(name='increment', label=f'Count={q.app.count}') + ]) + q.client.initialized = True + + if q.args.increment: + q.app.count += 1 + q.page['example'].increment.label = f'Count={q.app.count}' + + await q.page.save() diff --git a/examples/counter_global.py b/examples/counter_global.py new file mode 100644 index 0000000000000000000000000000000000000000..c56ee164913187a8e910ef7d4d81980d6225d9d5 --- /dev/null +++ b/examples/counter_global.py @@ -0,0 +1,25 @@ +# Mode / Broadcast / Global +# Launch the server in #broadcast #mode to synchronize browser state across users. +# Global variables can be used to manage state. +# Open `/demo` in multiple browsers and watch them synchronize in realtime. +# --- +from h2o_wave import main, app, Q, ui, pack + +count = 0 + + +@app('/demo', mode='broadcast') +async def serve(q: Q): + global count + if 'increment' in q.args: + count += 1 + + items = pack([ui.button(name='increment', label=f'Count={count}')]) + + if count > 0: + form = q.page['example'] + form.items = items + else: + q.page['example'] = ui.form_card(box='1 1 2 1', items=items) + + await q.page.save() diff --git a/examples/counter_multicast.py b/examples/counter_multicast.py new file mode 100644 index 0000000000000000000000000000000000000000..139c1ddc408c24062891946bcd60ebaa6593705d --- /dev/null +++ b/examples/counter_multicast.py @@ -0,0 +1,23 @@ +# Mode / Multicast +# Launch the server in #multicast #mode to synchronize browser state across a user's clients. +# Open `/demo` in multiple browsers and watch them synchronize in realtime. +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo', mode='multicast') +async def serve(q: Q): + if not q.client.initialized: + if q.user.count is None: + q.user.count = 0 + + q.page['example'] = ui.form_card(box='1 1 2 1', items=[ + ui.button(name='increment', label=f'Count={q.user.count}') + ]) + q.client.initialized = True + + if q.args.increment: + q.user.count += 1 + q.page['example'].increment.label = f'Count={q.user.count}' + + await q.page.save() diff --git a/examples/counter_unicast.py b/examples/counter_unicast.py new file mode 100644 index 0000000000000000000000000000000000000000..a8defb6cbca151105d835d14f21e21cb3677dbe8 --- /dev/null +++ b/examples/counter_unicast.py @@ -0,0 +1,20 @@ +# Mode / Unicast +# Launch the server in #unicast #mode and use `q.client` to manage client-local state. +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.client.count = 0 + q.page['example'] = ui.form_card(box='1 1 2 1', items=[ + ui.button(name='increment', label=f'Count={q.client.count}') + ]) + q.client.initialized = True + + if q.args.increment: + q.client.count += 1 + q.page['example'].increment.label = f'Count={q.client.count}' + + await q.page.save() diff --git a/examples/dashboard.py b/examples/dashboard.py new file mode 100644 index 0000000000000000000000000000000000000000..7eb466072cdd3d1673b5764da85d3c2e332f826a --- /dev/null +++ b/examples/dashboard.py @@ -0,0 +1,265 @@ +# Dashboard +# Make a #dashboard using a multitude of cards and update them live. +# --- +from faker import Faker +import time +from h2o_wave import site, data, ui +from synth import FakePercent, FakeCategoricalSeries + +fake = Faker() + +light_theme_colors = '$red $pink $purple $violet $indigo $blue $azure $cyan $teal $mint $green $amber $orange $tangerine'.split() # noqa: E501 +dark_theme_colors = '$red $pink $blue $azure $cyan $teal $mint $green $lime $yellow $amber $orange $tangerine'.split() + +_color_index = -1 +colors = dark_theme_colors + + +def next_color(): + global _color_index + _color_index += 1 + return colors[_color_index % len(colors)] + + +_curve_index = -1 +curves = 'linear smooth step step-after step-before'.split() + + +def next_curve(): + global _curve_index + _curve_index += 1 + return curves[_curve_index % len(curves)] + + +def create_dashboard(update_freq=0.0): + page = site['/demo'] + simples = [] + for i in range(1, 7): + f = FakePercent() + val, pc = f.next() + c = page.add(f'a{i}', ui.small_stat_card( + box=f'{i} 1 1 1', + title=fake.cryptocurrency_name(), + value=f'${val:.2f}', + )) + simples.append((f, c)) + + simples_colored = [] + for i in range(1, 7): + f = FakeCategoricalSeries() + cat, val, pc = f.next() + c = page.add(f'aa{i}', ui.small_series_stat_card( + box=f'{6 + i} 1 1 1', + title=fake.cryptocurrency_code(), + value='=${{intl qux minimum_fraction_digits=2 maximum_fraction_digits=2}}', + data=dict(qux=val, quux=pc / 100), + plot_category='foo', + plot_value='qux', + plot_color=next_color(), + plot_data=data('foo qux', -15), + plot_zero_value=0, + plot_curve=next_curve(), + )) + simples_colored.append((f, c)) + + lines = [] + for i in range(1, 13, 2): + f = FakeCategoricalSeries() + cat, val, pc = f.next() + c = page.add(f'b{i}', ui.wide_series_stat_card( + box=f'{i} 2 2 1', + title=fake.cryptocurrency_name(), + value='=${{intl qux minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value='={{intl quux style="percent" minimum_fraction_digits=1 maximum_fraction_digits=1}}', + data=dict(qux=val, quux=pc / 100), + plot_category='foo', + plot_value='qux', + plot_color=next_color(), + plot_data=data('foo qux', -15), + plot_zero_value=0, + plot_curve=next_curve(), + )) + lines.append((f, c)) + + bars = [] + for i in range(1, 13, 2): + f = FakeCategoricalSeries() + cat, val, pc = f.next() + c = page.add(f'c{i}', ui.wide_series_stat_card( + box=f'{i} 3 2 1', + title=fake.cryptocurrency_name(), + value='=${{intl qux minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value='={{intl quux style="percent" minimum_fraction_digits=1 maximum_fraction_digits=1}}', + data=dict(qux=val, quux=pc), + plot_type='interval', + plot_category='foo', + plot_value='qux', + plot_color=next_color(), + plot_data=data('foo qux', -25), + plot_zero_value=0 + )) + bars.append((f, c)) + + large_pcs = [] + for i in range(1, 13): + f = FakePercent() + val, pc = f.next() + c = page.add(f'd{i}', ui.tall_gauge_stat_card( + box=f'{i} 4 1 2', + title=fake.cryptocurrency_name(), + value='=${{intl foo minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value='={{intl bar style="percent" minimum_fraction_digits=2 maximum_fraction_digits=2}}', + plot_color=next_color(), + progress=pc, + data=dict(foo=val, bar=pc / 100), + )) + large_pcs.append((f, c)) + + large_lines = [] + for i in range(1, 13): + f = FakeCategoricalSeries() + cat, val, pc = f.next() + c = page.add(f'e{i}', ui.tall_series_stat_card( + box=f'{i} 6 1 2', + title=fake.cryptocurrency_name(), + value='=${{intl qux minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value='={{intl quux style="percent" minimum_fraction_digits=1 maximum_fraction_digits=1}}', + data=dict(qux=val, quux=pc), + plot_type='area', + plot_category='foo', + plot_value='qux', + plot_color=next_color(), + plot_data=data('foo qux', -15), + plot_zero_value=0, + plot_curve=next_curve(), + )) + large_lines.append((f, c)) + + small_pcs = [] + for i in range(1, 7, 2): + f = FakePercent() + val, pc = f.next() + c = page.add(f'f{i}', ui.wide_gauge_stat_card( + box=f'{i} 8 2 1', + title=fake.cryptocurrency_name(), + value='=${{intl foo minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value='={{intl bar style="percent" minimum_fraction_digits=2 maximum_fraction_digits=2}}', + plot_color=next_color(), + progress=pc, + data=dict(foo=val, bar=pc / 100), + )) + small_pcs.append((f, c)) + + small_pbs = [] + for i in range(7, 13, 2): + f = FakePercent() + val, pc = f.next() + c = page.add(f'f{i}', ui.wide_bar_stat_card( + box=f'{i} 8 2 1', + title=fake.cryptocurrency_name(), + value='=${{intl foo minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value='={{intl bar style="percent" minimum_fraction_digits=2 maximum_fraction_digits=2}}', + plot_color=next_color(), + progress=pc, + data=dict(foo=val, bar=pc / 100), + )) + small_pbs.append((f, c)) + + large_cards = [] + for i in range(1, 7, 2): + f = FakePercent() + val, pc = f.next() + c = page.add(f'g{i}', ui.large_stat_card( + box=f'{i} 9 2 2', + title=fake.cryptocurrency_name(), + value='=${{intl qux minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value='={{intl quux style="percent" minimum_fraction_digits=1 maximum_fraction_digits=1}}', + data=dict(qux=val, quux=pc / 100), + caption=' '.join(fake.sentences()), + )) + large_cards.append((f, c)) + + large_pbs = [] + for i in range(7, 13, 2): + f = FakePercent() + val, pc = f.next() + c = page.add(f'g{i}', ui.large_bar_stat_card( + box=f'{i} 9 2 2', + title=fake.cryptocurrency_name(), + value='=${{intl foo minimum_fraction_digits=2 maximum_fraction_digits=2}}', + value_caption='This Month', + aux_value='={{intl bar style="percent" minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value_caption='Previous Month', + plot_color=next_color(), + progress=pc, + data=dict(foo=val, bar=pc / 100), + caption=' '.join(fake.sentences(2)), + )) + large_pbs.append((f, c)) + + page.save() + + while update_freq > 0: + time.sleep(update_freq) + + for f, c in simples: + val, pc = f.next() + c.value = f'${val:.2f}', + + for f, c in simples_colored: + cat, val, pc = f.next() + c.data.qux = val + c.data.quux = pc / 100 + c.plot_data[-1] = [cat, val] + + for f, c in lines: + cat, val, pc = f.next() + c.data.qux = val + c.data.quux = pc / 100 + c.plot_data[-1] = [cat, val] + + for f, c in bars: + cat, val, pc = f.next() + c.data.qux = val + c.data.quux = pc / 100 + c.plot_data[-1] = [cat, val] + + for f, c in large_lines: + cat, val, pc = f.next() + c.data.qux = val + c.data.quux = pc / 100 + c.plot_data[-1] = [cat, val] + + for f, c in large_pcs: + val, pc = f.next() + c.data.foo = val + c.data.bar = pc + c.progress = pc + + for f, c in small_pcs: + val, pc = f.next() + c.data.foo = val + c.data.bar = pc + c.progress = pc + + for f, c in small_pbs: + val, pc = f.next() + c.data.foo = val + c.data.bar = pc + c.progress = pc + + for f, c in large_cards: + val, pc = f.next() + c.data.qux = val + c.data.quux = pc + + for f, c in large_pbs: + val, pc = f.next() + c.data.foo = val + c.data.bar = pc + c.progress = pc + + page.save() + + +create_dashboard(update_freq=0.25) diff --git a/examples/date_picker.py b/examples/date_picker.py new file mode 100644 index 0000000000000000000000000000000000000000..1ebccc29ed6a4264a4748fc1a7680486d258bb51 --- /dev/null +++ b/examples/date_picker.py @@ -0,0 +1,26 @@ +# Form / Date Picker +# Use date pickers to allow users to pick dates. +# #form #date_picker +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'date={q.args.date}'), + ui.text(f'date_placeholder={q.args.date_placeholder}'), + ui.text(f'date_disabled={q.args.date_disabled}'), + ui.text(f'date_boundaries={q.args.date_boundaries}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 7', items=[ + ui.date_picker(name='date', label='Standard date picker', value='2017-10-19'), + ui.date_picker(name='date_placeholder', label='Date picker with placeholder', placeholder='Pick a date'), + ui.date_picker(name='date_disabled', label='Disabled date picker', value='2017-10-19', disabled=True), + ui.date_picker(name='date_boundaries', label='Date with boundaries', value='2017-10-19', min="2017-10-19", max="2017-10-25"), + ui.button(name='show_inputs', label='Submit', primary=True), + ]) + await q.page.save() diff --git a/examples/date_picker_trigger.py b/examples/date_picker_trigger.py new file mode 100644 index 0000000000000000000000000000000000000000..923fe1e79cbc34f6dd3a265ea8c8034e2a09f535 --- /dev/null +++ b/examples/date_picker_trigger.py @@ -0,0 +1,23 @@ +# Form / Date Picker / Trigger +# To handle live changes to a date picker, enable the `trigger` attribute. +# #form #date_picker #trigger +# --- +from typing import Optional +from h2o_wave import main, app, Q, ui + + +def get_form_items(value: Optional[str]): + return [ + ui.text(f'date_trigger={value}'), + ui.date_picker(name='date_trigger', label='Pick a date', trigger=True), + ] + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['example'] = ui.form_card(box='1 1 4 5', items=get_form_items(None)) + q.client.initialized = True + else: + q.page['example'].items = get_form_items(q.args.date_trigger) + await q.page.save() diff --git a/examples/db.py b/examples/db.py new file mode 100644 index 0000000000000000000000000000000000000000..f952754bd02499b7b06144c85322f0d01f6bdc0f --- /dev/null +++ b/examples/db.py @@ -0,0 +1,86 @@ +# WaveDB +# How to use WaveDB, a simple sqlite3 database server that ships with Wave. +# --- + +# Before you run this example, start WaveDB (`wavedb`). +# By default, WaveDB listens on port 10100. +# +# To run this example, execute `python db.py` +# +# If your WaveDB instance is configured differently, you might want to set +# the following environment variables accordingly: +# H2O_WAVEDB_ADDRESS - the ip:port of the database server +# H2O_WAVEDB_ACCESS_KEY_ID - the API access key ID +# H2O_WAVEDB_ACCESS_KEY_SECRET - the API access key secret + +import asyncio +from h2o_wave import connect + + +async def main(): + # Create a database connection + connection = connect() + + # Access the 'employees' database. + # A new database is created automatically if it does not exist. + db = connection["employees"] + + # Execute some statements. + await db.exec("drop table if exists employee") + await db.exec("create table employee(empid integer, name text, title text)") + + # Execute a statement and handle errors. + results, err = await db.exec("insert into employee values(?, ?, ?)", 101, 'Jeffrey Lebowski', 'Slacker') + if err: + raise ValueError(err) + + # Execute many statements. + insert_employee = "insert into employee values(?, ?, ?)" + await db.exec_many( + (insert_employee, 102, 'Walter Sobchak', 'Veteran'), + (insert_employee, 103, 'Donny Kerabatsos', 'Sidekick'), + (insert_employee, 104, 'Jesus Quintana', 'Bowler'), + (insert_employee, 105, 'Uli Kunkel', 'Nihilist'), + ) + + # Execute many statements as a transaction. + await db.exec_atomic( + (insert_employee, 106, 'Brandt', 'Butler'), + (insert_employee, 107, 'Maude Lebowski', 'Artist'), + (insert_employee, 108, 'Franz', 'Nihilist'), + (insert_employee, 109, 'Kieffer', 'Nihilist'), + ) + + # Read records. + rows, err = await db.exec("select * from employee") + if err: + raise ValueError(err) + + print(rows) + + # Prints: + # [ + # [101, 'Jeffrey Lebowski', 'Slacker'], + # [102, 'Walter Sobchak', 'Veteran'], + # [103, 'Donny Kerabatsos', 'Sidekick'], + # [104, 'Jesus Quintana', 'Bowler'], + # [105, 'Uli Kunkel', 'Nihilist'], + # [106, 'Brandt', 'Butler'], + # [107, 'Maude Lebowski', 'Artist'], + # [108, 'Franz', 'Nihilist'], + # [109, 'Kieffer', 'Nihilist'] + # ] + + # Clean up. + await db.exec("drop table employee") + + # Drop the database entirely. Warning: A database is irrecoverable once dropped. + await db.drop() + + # Close connection. + await connection.close() + + +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) +loop.run_until_complete(main()) diff --git a/examples/db_todo.py b/examples/db_todo.py new file mode 100644 index 0000000000000000000000000000000000000000..4af3ff8135d6b68f86e3fda2f2b756a9c1aeb62d --- /dev/null +++ b/examples/db_todo.py @@ -0,0 +1,109 @@ +# WaveDB / To-do App +# A multi-user To-do list application using WaveDB for data management. +# This example is very similar to the todo.py example, except that this +# example uses WaveDB instead of an in-memory list. +# --- +from h2o_wave import main, app, Q, ui, connect, WaveDB, expando_to_dict + + +# A simple class that represents a to-do item. +class TodoItem: + def __init__(self, id, label, done): + self.id = id + self.label = label + self.done = done + + +async def setup_db() -> WaveDB: + db = connect()['todo'] + _, err = await db.exec_atomic( + """ + create table if not exists todo ( + id integer primary key, + user text not null, + label text not null, + done integer not null default 0 + ) + """ + ) + if err: + raise RuntimeError(f'Failed setting up database: {err}') + return db + + +@app('/demo') +async def serve(q: Q): + if q.app.db is None: + q.app.db = await setup_db() + + if q.args.new_todo: # Display an input form. + await new_todo(q) + elif q.args.add_todo: # Add an item. + await add_todo(q) + else: # Show all items. + await show_todos(q) + + +async def show_todos(q: Q): + # Get items for this user. + db: WaveDB = q.app.db + + # Check if we have any updates, i.e. the user has checked/unchecked any item. + updates = [] + for key, done in expando_to_dict(q.args).items(): + # We've named each todo item `todo_{id}' (e.g. todo_42, todo_43, and so on) + # So identify the todo items from their 'todo_' prefix, then extract the ids from the names. + if key.startswith('todo_'): + _, id = key.split('_', 1) + updates.append(('update todo set done=? where id=?', 1 if done else 0, int(id))) + + # If we have updates, update our database. + if len(updates): + _, err = await db.exec_many(*updates) + if err: + raise RuntimeError(f'Failed updating todos: {err}') + + # Fetch latest todos for our user + rows, err = await db.exec('select id, label, done from todo where user=?', q.auth.subject) + if err: + raise RuntimeError(f'Failed fetching todos: {err}') + todos = [TodoItem(id, label, done) for id, label, done in rows] + + # Create done/not-done checkboxes. + done = [ui.checkbox(name=f'todo_{todo.id}', label=todo.label, value=True, trigger=True) for todo in todos if + todo.done] + not_done = [ui.checkbox(name=f'todo_{todo.id}', label=todo.label, trigger=True) for todo in todos if not todo.done] + + # Display list + q.page['form'] = ui.form_card(box='1 1 4 10', items=[ + ui.text_l('To Do'), + ui.button(name='new_todo', label='Add To Do...', primary=True), + *not_done, + *([ui.separator('Done')] if len(done) else []), + *done, + ]) + await q.page.save() + + +async def add_todo(q: Q): + # Insert a new item + db: WaveDB = q.app.db + _, err = await db.exec('insert into todo (user, label) values (? , ?)', q.auth.subject, q.args.label or 'Untitled') + if err: + raise RuntimeError(f'Failed inserting todo: {err}') + + # Go back to our list. + await show_todos(q) + + +async def new_todo(q: Q): + # Display an input form + q.page['form'] = ui.form_card(box='1 1 4 10', items=[ + ui.text_l('Add To Do'), + ui.textbox(name='label', label='What needs to be done?', multiline=True), + ui.buttons([ + ui.button(name='add_todo', label='Add', primary=True), + ui.button(name='show_todos', label='Back'), + ]), + ]) + await q.page.save() diff --git a/examples/dropdown.py b/examples/dropdown.py new file mode 100644 index 0000000000000000000000000000000000000000..4a150c6da475a30e72c3d7b8ec41e8fab4ce3497 --- /dev/null +++ b/examples/dropdown.py @@ -0,0 +1,44 @@ +# Form / Dropdown +# Use dropdowns to allow users to choose between available choices. +# #form #dropdown #choice +# --- +from h2o_wave import main, app, Q, ui + +choices = [ + ui.choice('A', 'Option A'), + ui.choice('B', 'Option B'), + ui.choice('C', 'Option C', disabled=True), + ui.choice('D', 'Option D'), +] + +choices_dialog = [ui.choice(str(i), f'Option {i}') for i in range(1, 102)] + + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'dropdown={q.args.dropdown}'), + ui.text(f'dropdown_multi={q.args.dropdown_multi}'), + ui.text(f'dropdown_disabled={q.args.dropdown_disabled}'), + ui.text(f'dropdown_dialog={q.args.dropdown_dialog}'), + ui.text(f'dropdown_popup_always={q.args.dropdown_popup_always}'), + ui.text(f'dropdown_popup_never={q.args.dropdown_popup_never}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 7', items=[ + ui.dropdown(name='dropdown', label='Pick one', value='B', required=True, choices=choices), + ui.dropdown(name='dropdown_multi', label='Pick multiple', values=['B', 'D'], required=True, + choices=choices), + ui.dropdown(name='dropdown_disabled', label='Pick one (Disabled)', value='B', choices=choices, + disabled=True), + ui.dropdown(name='dropdown_dialog', label='Pick multiple in dialog (>100 choices)', values=['1'], + required=True, choices=choices_dialog), + ui.dropdown(name='dropdown_popup_always', label='Always show popup even when choices < 100', value='A', + required=True, choices=choices, popup='always'), + ui.dropdown(name='dropdown_popup_never', label='Never show popup even when choices > 100', value='1', + required=True, choices=choices_dialog, popup='never'), + ui.button(name='show_inputs', label='Submit', primary=True), + ]) + await q.page.save() diff --git a/examples/facepile.py b/examples/facepile.py new file mode 100644 index 0000000000000000000000000000000000000000..60401d4ce7d2609cc5f6630df0e34f7eae2a21b8 --- /dev/null +++ b/examples/facepile.py @@ -0,0 +1,42 @@ +# Form / Facepile +# A face pile displays a list of personas. Each circle represents a person and contains their image or initials. +# Often this control is used when sharing who has access to a specific view or file. +# #form +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.facepile: + q.page['example'].items = [ + ui.text_m(f'q.args.facepile={q.args.facepile}'), + ui.button(name='back', label='Back', primary=True), + ] + elif q.args.facepile_value: + q.page['example'].items = [ + ui.text_m(f'q.args.facepile_value={q.args.facepile_value}'), + ui.button(name='back', label='Back', primary=True), + ] + else: + image = 'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&h=750&w=1260' + q.page['example'] = ui.form_card(box='1 1 2 2', items=[ + ui.text(content='Add button sends true'), + ui.facepile(name='facepile', max=4, items=[ + ui.persona(title='John Doe', image=image), + ui.persona(title='John Doe', image=image), + ui.persona(title='John Doe'), + ui.persona(title='John Doe', image=image), + ui.persona(title='John Doe', image=image), + ]), + ui.text(content='Add button sends set value'), + ui.facepile(name='facepile_value', value='submitted value', max=4, items=[ + ui.persona(title='John Doe', image=image), + ui.persona(title='John Doe', image=image), + ui.persona(title='John Doe'), + ui.persona(title='John Doe', image=image), + ui.persona(title='John Doe', image=image), + ]), + ]) + + await q.page.save() diff --git a/examples/file_stream.py b/examples/file_stream.py new file mode 100644 index 0000000000000000000000000000000000000000..8a9271c71ae53621b19dc3b919b072c6ebe01465 --- /dev/null +++ b/examples/file_stream.py @@ -0,0 +1,41 @@ +# Image / Stream +# Display an image and continuously update it in real time. +# --- +import io +import time +import uuid + +import cv2 +from h2o_wave import app, Q, ui, main +import numpy as np + +frame_count = 256 + + +def create_random_image(): + frame = (np.random.rand(100, 100, 3) * 255).astype(np.uint8) + _, img = cv2.imencode('.jpg', frame) + return io.BytesIO(img) + + +@app('/demo') +async def serve(q: Q): + # Mint a unique name for our image stream + stream_name = f'stream/demo/{uuid.uuid4()}.jpeg' + + # Send image + endpoint = await q.site.uplink(stream_name, 'image/jpeg', create_random_image()) + + # Display image + q.page['qux'] = ui.form_card(box='1 1 5 5', items=[ui.image('Image Stream', path=endpoint)]) + await q.page.save() + + t0 = time.time() + # Update image in a loop + for i in range(frame_count): + # Send image (use stream name as before). + await q.site.uplink(stream_name, 'image/jpeg', create_random_image()) + + await q.site.unlink(stream_name) + + print(f'{frame_count / (time.time() - t0)}fps') diff --git a/examples/file_upload.py b/examples/file_upload.py new file mode 100644 index 0000000000000000000000000000000000000000..b116d67e5139115daff8d036ad81aedfd7cd2ff4 --- /dev/null +++ b/examples/file_upload.py @@ -0,0 +1,23 @@ +# Form / File Upload +# Use a file #upload component to allow users to upload files. +# #form #file_upload +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if 'file_upload' in q.args: + q.page['example'] = ui.form_card(box='1 1 4 10', items=[ + ui.text(f'file_upload={q.args.file_upload}'), + ui.button(name='show_upload', label='Back', primary=True), + ]) + else: + q.page['example'] = ui.form_card( + box='1 1 4 7', + items=[ + ui.file_upload(name='file_upload', label='Upload!', multiple=True, + file_extensions=['csv', 'gz'], max_file_size=10, max_size=15) + ] + ) + await q.page.save() diff --git a/examples/file_upload_compact.py b/examples/file_upload_compact.py new file mode 100644 index 0000000000000000000000000000000000000000..7090652ecb29e20447036146457d6c097f85bccf --- /dev/null +++ b/examples/file_upload_compact.py @@ -0,0 +1,24 @@ +# Form / File Upload / Compact +# Use a compact file #upload component to take less form space. +# #form #file_upload #compact +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if 'file_upload' in q.args: + q.page['example'] = ui.form_card(box='1 1 4 10', items=[ + ui.text(f'file_upload={q.args.file_upload}'), + ui.button(name='show_upload', label='Back', primary=True), + ]) + else: + q.page['example'] = ui.form_card( + box='1 1 4 7', + items=[ + ui.file_upload(name='file_upload', label='Select one or more files to upload', compact=True, + multiple=True, file_extensions=['jpg', 'png'], max_file_size=1, max_size=15), + ui.button(name='submit', label='Submit', primary=True) + ] + ) + await q.page.save() diff --git a/examples/flex.py b/examples/flex.py new file mode 100644 index 0000000000000000000000000000000000000000..485604fdda24dac2adfb64e8ee6e54dddf8760cb --- /dev/null +++ b/examples/flex.py @@ -0,0 +1,28 @@ +# Flex +# Use a flex card to tile multiple child cards along one dimension, with optional wrapping. +# #flex +# --- +import random + +from faker import Faker + +from h2o_wave import site, ui, pack, data + +fake = Faker() + +page = site['/demo'] + +c = page.add('example', ui.flex_card( + box='1 1 1 1', + direction='horizontal', + justify='between', + wrap='between', + item_view='template', + item_props=pack(dict( + content='
' # noqa: E501 + )), + data=data('code loss', -10), +)) +c.data = [[fake.cryptocurrency_code(), random.randint(0, 1)] for _ in range(10)] + +page.save() diff --git a/examples/footer.py b/examples/footer.py new file mode 100644 index 0000000000000000000000000000000000000000..34b8c1eb76204c8572bad012795f162f12699fd8 --- /dev/null +++ b/examples/footer.py @@ -0,0 +1,36 @@ +# Footer +# Use a footer card to display a page #footer. +# --- +from h2o_wave import site, ui + +page = site['/demo'] +caption = ''' +![theme-generator](https://wave.h2o.ai/img/logo.svg) + +Made with 💛 by H2O Wave Team.''' +page['footer1'] = ui.footer_card(box='1 1 -1 1', caption='Made with 💛 by H2O Wave Team.') +page['footer2'] = ui.footer_card(box='1 2 -1 3', caption=caption) +page['footer3'] = ui.footer_card( + box='1 5 -1 3', + caption=caption, + items=[ + ui.inline(justify='end', items=[ + ui.links(label='First Column', width='200px', items=[ + ui.link(label='Sample link', path='https://www.h2o.ai/', target='_blank'), + ui.link(label='Sample link', path='https://www.h2o.ai/', target='_blank'), + ui.link(label='Sample link', path='https://www.h2o.ai/', target='_blank'), + ]), + ui.links(label='Second Column', width='200px', items=[ + ui.link(label='Sample link', path='https://www.h2o.ai/', target='_blank'), + ui.link(label='Sample link', path='https://www.h2o.ai/', target='_blank'), + ui.link(label='Sample link', path='https://www.h2o.ai/', target='_blank'), + ]), + ui.links(label='Third Column', width='200px', items=[ + ui.link(label='Sample link', path='https://www.h2o.ai/', target='_blank'), + ui.link(label='Sample link', path='https://www.h2o.ai/', target='_blank'), + ui.link(label='Sample link', path='https://www.h2o.ai/', target='_blank'), + ]), + ]), + ] +) +page.save() diff --git a/examples/form.py b/examples/form.py new file mode 100644 index 0000000000000000000000000000000000000000..eeabb695de54b2d1bfad3998a86b08db077af9ce --- /dev/null +++ b/examples/form.py @@ -0,0 +1,141 @@ +# Form +# Use a #form to collect data or show textual information. +# --- +from .synth import FakeCategoricalSeries +from h2o_wave import main, app, Q, ui, pack, data +import random + +html = ''' +
    +
  1. Spam
  2. +
  3. Ham
  4. +
  5. Eggs
  6. +
+''' +menu = ''' +
    +{{#each dishes}} +
  1. {{name}} costs {{price}}
  2. +{{/each}} +
+ + +

Hello World!

+ + +''' + +page = site['/demo'] + +page['example'] = ui.form_card( + box='1 1 2 2', + items=[ + ui.frame(content=html, height='100px') + ] +) + +page.save() diff --git a/examples/form_frame_path.py b/examples/form_frame_path.py new file mode 100644 index 0000000000000000000000000000000000000000..85b8632a46f472ea3e43cf4f4f3228ecf82b8f2b --- /dev/null +++ b/examples/form_frame_path.py @@ -0,0 +1,15 @@ +# Form / Frame / Path +# Use a #frame component in a #form card to display external web pages. +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +page['example'] = ui.form_card( + box='1 1 -1 -1', + items=[ + ui.frame(path='https://example.com', height='450px') + ] +) + +page.save() diff --git a/examples/form_markup.py b/examples/form_markup.py new file mode 100644 index 0000000000000000000000000000000000000000..bccbcc4953665cda85caac66b68761156434169f --- /dev/null +++ b/examples/form_markup.py @@ -0,0 +1,23 @@ +# Form / Markup +# Use a #markup component to display formatted content using #HTML. +# #form +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +menu = ''' +
    +
  1. Spam
  2. +
  3. Ham
  4. +
  5. Eggs
  6. +
+''' + +page['example'] = ui.form_card( + box='1 1 2 2', + items=[ + ui.markup(content=menu) + ] +) +page.save() diff --git a/examples/form_menu.py b/examples/form_menu.py new file mode 100644 index 0000000000000000000000000000000000000000..823c92ef6c8b0243bf580832a30d53279c1b97f4 --- /dev/null +++ b/examples/form_menu.py @@ -0,0 +1,71 @@ +# Form / Menu +# Display context menus inside forms. +# #form #context_menu +# --- +import json +from h2o_wave import main, app, Q, ui, data + +# Vega lite spec for a bar plot, defaults to linear scale. +spec_linear_scale = json.dumps(dict( + mark='bar', + encoding=dict( + x=dict(field='a', type='ordinal'), + y=dict(field='b', type='quantitative') + ) +)) + +# Vega lite spec for a bar plot, log scaled. +spec_log_scale = json.dumps(dict( + mark='bar', + encoding=dict( + x=dict(field='a', type='ordinal'), + y=dict(field='b', type='quantitative', scale=dict(type='log')) + ) +)) + +# Data for our plot. +plot_data = data(fields=["a", "b"], rows=[ + ["A", 28], ["B", 55], ["C", 43], + ["D", 91], ["E", 81], ["F", 53], + ["G", 19], ["H", 87], ["I", 52] +], pack=True) + +# Create a couple of context menu commands. +log_scale_command = ui.command( + name='to_log_scale', + label='Log Scale', + icon='LineChart', +) +linear_scale_command = ui.command( + name='to_linear_scale', + label='Linear Scale', + icon='LineChart', +) + + +@app('/demo') +async def serve(q: Q): + if q.client.plot_added: # Have we already added a plot? + example = q.page['example'] + if q.args.to_log_scale: + # Change to log scale + example.text.content = 'Plot (Log Scale)' + example.text.commands = [linear_scale_command] + example.visualization.specification = spec_log_scale + else: + # Change to linear scale + example.text.content = 'Plot (Linear Scale)' + example.text.commands = [log_scale_command] + example.visualization.specification = spec_linear_scale + else: # Add a new plot + q.page['example'] = ui.form_card( + box='1 1 2 8', + items=[ + ui.text_l(name='text', content='Plot (Linear Scale)', commands=[log_scale_command]), + ui.vega_visualization(name='visualization', specification=spec_linear_scale, data=plot_data, height='300px'), + ], + ) + # Flag to indicate that we've added a plot + q.client.plot_added = True + + await q.page.save() diff --git a/examples/form_template.py b/examples/form_template.py new file mode 100644 index 0000000000000000000000000000000000000000..ca51e51e834707e8a4476225d95d757a42a257c1 --- /dev/null +++ b/examples/form_template.py @@ -0,0 +1,30 @@ +# Form / Template +# Use a template component to render dynamic content using a #HTML #template. +# #form +# --- +from h2o_wave import site, pack, ui + +page = site['/demo'] +page.drop() + +menu = ''' +
    +{{#each dishes}} +
  1. {{name}} costs {{price}}
  2. +{{/each}} +
+ + +

Hello World!

+ + +''' + +page = site['/demo'] + +page['example'] = ui.frame_card( + box='1 1 2 2', + title='Example', + content=html, +) + +page.save() diff --git a/examples/frame_path.py b/examples/frame_path.py new file mode 100644 index 0000000000000000000000000000000000000000..c4a0fbd30c0c277699883ea174a70d124d7d40fb --- /dev/null +++ b/examples/frame_path.py @@ -0,0 +1,14 @@ +# Frame / Path +# Use a #frame card to display external web pages. +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +page['example'] = ui.frame_card( + box='1 1 -1 -1', + title='Example', + path='https://example.com', +) + +page.save() diff --git a/examples/glider_gun.py b/examples/glider_gun.py new file mode 100644 index 0000000000000000000000000000000000000000..3d9cf748842e392f9c1c943b635a817688386f54 --- /dev/null +++ b/examples/glider_gun.py @@ -0,0 +1,161 @@ +# Graphics / Glider Gun +# Use the #graphics API to play Conway's Game of Life - Gosper's Glider Gun +# https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life +# --- +import time +from copy import deepcopy +from h2o_wave import site, ui, graphics as g + + +def get_neighbors(row, col): + neighbors = [ + (r, c) for r in range(row - 1, row + 2) for c in range(col - 1, col + 2) + ] + neighbors.remove((row, col)) + return neighbors + + +def get_num_living(grid_state, neighbors): + return sum([grid_state.get(x, 0) for x in neighbors]) + + +def evaluate_grid(grid_state): + new_grid_state = deepcopy(grid_state) + for cell, state in grid_state.items(): + neighbors = get_neighbors(*cell) + n_living = get_num_living(grid_state, neighbors) + if state == 0 and n_living == 3: + new_grid_state[cell] = 1 + elif state == 1: + if not 1 < n_living < 4: + new_grid_state[cell] = 0 + return new_grid_state + + +def get_empty_state(n_rows, n_cols): + return {(r, c): 0 for r in range(n_rows) for c in range(n_cols)} + + +def apply_start_state(grid_state, pattern): + for x in pattern: + grid_state[x] = 1 + return grid_state + + +def update_grid(page, grid_state, n_rows, n_cols, background): + scene = page['game'].scene + for row in range(n_rows): + for col in range(n_cols): + if grid_state[(row, col)] == 1: + g.draw(scene[f'cell_{row}_{col}'], fill='black') + else: + g.draw(scene[f'cell_{row}_{col}'], fill=background) + page.save() + + +def create_grid(n_rows, n_cols, fill, width, height, stroke, stroke_width): + grid = {} + for row in range(n_rows): + for col in range(n_cols): + grid[f'cell_{row}_{col}'] = g.rect( + x=col * width, + y=row * height, + width=width, + height=height, + fill=fill, + stroke=stroke, + stroke_width=stroke_width, + ) + return grid + + +def render(pattern): + page = site['/demo'] + + page_cols = 4 + page_rows = 5 + + box_width = 134 + box_height = 76 + gap = 15 + + max_width = box_width * page_cols + (page_cols - 1) * gap + max_height = box_height * page_rows + (page_rows - 1) * gap + + width = 10 + height = 10 + + grid_cols = max_width // width + grid_rows = max_height // height + + background = 'whitesmoke' + stroke = 'gainsboro' + stroke_width = 1 + + grid = create_grid( + grid_rows, grid_cols, background, width, height, stroke, stroke_width + ) + page['game'] = ui.graphics_card( + box=f'1 1 {page_cols} {page_rows}', + view_box=f'0 0 {max_width} {max_height}', + width='100%', + height='100%', + scene=g.scene(**grid), + ) + + grid_state = get_empty_state(grid_rows, grid_cols) + update_grid(page, grid_state, grid_rows, grid_cols, background) + + grid_state = apply_start_state(grid_state, pattern) + + update_grid(page, grid_state, grid_rows, grid_cols, background) + + while True: + time.sleep(0.1) + new_grid_state = evaluate_grid(grid_state) + update_grid(page, new_grid_state, grid_rows, grid_cols, background) + grid_state = new_grid_state + + +def make_glider_gun(r, c): + return [ + (r, c + 24), + (r + 1, c + 22), + (r + 1, c + 24), + (r + 2, c + 12), + (r + 2, c + 13), + (r + 2, c + 20), + (r + 2, c + 21), + (r + 2, c + 34), + (r + 2, c + 35), + (r + 3, c + 11), + (r + 3, c + 15), + (r + 3, c + 20), + (r + 3, c + 21), + (r + 3, c + 34), + (r + 3, c + 35), + (r + 4, c + 0), + (r + 4, c + 1), + (r + 4, c + 10), + (r + 4, c + 16), + (r + 4, c + 20), + (r + 4, c + 21), + (r + 5, c + 0), + (r + 5, c + 1), + (r + 5, c + 10), + (r + 5, c + 14), + (r + 5, c + 16), + (r + 5, c + 17), + (r + 5, c + 22), + (r + 5, c + 24), + (r + 6, c + 10), + (r + 6, c + 16), + (r + 6, c + 24), + (r + 7, c + 11), + (r + 7, c + 15), + (r + 8, c + 12), + (r + 8, c + 13), + ] + + +render(make_glider_gun(2, 2)) diff --git a/examples/graphics_clock.py b/examples/graphics_clock.py new file mode 100644 index 0000000000000000000000000000000000000000..a4d1495ff687f7c7bb8a48d539fb8ef9df531202 --- /dev/null +++ b/examples/graphics_clock.py @@ -0,0 +1,36 @@ +# Graphics / Clock +# Use the #graphics API to make a clock. +# Source: https://codepen.io/dudleystorey/pen/HLBki +# --- +import time +import datetime +from h2o_wave import site, ui, graphics as g + +page = site['/demo'] +page['example'] = ui.graphics_card( + box='1 1 2 3', view_box='0 0 100 100', width='100%', height='100%', + stage=g.stage( + face=g.circle(cx='50', cy='50', r='45', fill='#111', stroke_width='2px', stroke='#f55'), + ), + scene=g.scene( + hour=g.rect(x='47.5', y='12.5', width='5', height='40', rx='2.5', fill='#333', stroke='#555'), + min=g.rect(x='48.5', y='12.5', width='3', height='40', rx='2', fill='#333', stroke='#555'), + sec=g.line(x1='50', y1='50', x2='50', y2='16', stroke='#f55', stroke_width='1px'), + ), +) + +page.save() + + +def rotate(deg): + return f'rotate({deg} 50 50)' + + +scene = page['example'].scene +while True: + time.sleep(1) + now = datetime.datetime.now() + g.draw(scene.hour, transform=rotate(30 * (now.hour % 12) + now.minute / 2)) + g.draw(scene.min, transform=rotate(6 * now.minute)) + g.draw(scene.sec, transform=rotate(6 * now.second)) + page.save() diff --git a/examples/graphics_hilbert.py b/examples/graphics_hilbert.py new file mode 100644 index 0000000000000000000000000000000000000000..bc8d907e744698012bc82c96dbde34dbf7dffa4c --- /dev/null +++ b/examples/graphics_hilbert.py @@ -0,0 +1,54 @@ +# Graphics / Hilbert +# Use turtle #graphics recursively to draw Hilbert curves. +# --- +from h2o_wave import ui, main, app, Q, graphics as g + + +def hilbert(t: g.Turtle, width: float, depth: int, reverse=False): # recursive + angle = -90 if reverse else 90 + + if depth == 0: + t.f(width).r(angle).f(width).r(angle).f(width) + return + + side = width * ((2 ** depth) - 1) / float((2 ** (depth + 1)) - 1) + edge = width - 2 * side + + t.r(angle) + hilbert(t, side, depth - 1, not reverse) + t.r(angle).f(edge) + hilbert(t, side, depth - 1, reverse) + t.l(angle).f(edge).l(angle) + hilbert(t, side, depth - 1, reverse) + t.f(edge).r(angle) + hilbert(t, side, depth - 1, not reverse) + t.r(angle) + + +def make_hilbert_curve(width: float, depth: int): + t = g.turtle().f(0).pd() + hilbert(t, width, depth) + return t.d() + + +@app('/demo') +async def serve(q: Q): + hilbert_curve = make_hilbert_curve(300, q.args.depth or 5) + + if not q.client.initialized: + q.page['curve'] = ui.graphics_card( + box='1 1 4 6', view_box='0 0 300 300', width='100%', height='100%', + scene=g.scene( + hilbert_curve=g.path(d=hilbert_curve, fill='none', stroke='#333') + ), + ) + q.page['form'] = ui.form_card( + box='1 7 4 1', items=[ + ui.slider(name='depth', label='Play with this Hilbert curve!', min=1, max=6, value=5, trigger=True), + ], + ) + q.client.initialized = True + else: + g.draw(q.page['curve'].scene.hilbert_curve, d=hilbert_curve) + + await q.page.save() diff --git a/examples/graphics_path.py b/examples/graphics_path.py new file mode 100644 index 0000000000000000000000000000000000000000..5eb12a3c37f2730bf5d576718fa1d4d745ef0651 --- /dev/null +++ b/examples/graphics_path.py @@ -0,0 +1,15 @@ +# Graphics / Path +# Use the #graphics API to draw a red square. +# --- +from h2o_wave import site, ui, graphics as g + +# Use relative coords to draw a square, sort of like Python's turtle package. +red_square = g.p().m(25, 25).h(50).v(50).h(-50).z().path(fill='red') + +page = site['/demo'] +page['example'] = ui.graphics_card( + box='1 1 2 3', view_box='0 0 100 100', width='100%', height='100%', + scene=g.scene(foo=red_square), +) + +page.save() diff --git a/examples/graphics_primitives.py b/examples/graphics_primitives.py new file mode 100644 index 0000000000000000000000000000000000000000..6a86166666207b7263f9e0a5338111ed891cb546 --- /dev/null +++ b/examples/graphics_primitives.py @@ -0,0 +1,58 @@ +# Graphics / Primitives +# Use the #graphics module to render and update shapes. +# --- + +from h2o_wave import site, ui, graphics as g + +# Create some shapes +arc = g.arc(r1=25, r2=50, a1=90, a2=180) +circle = g.circle(cx=25, cy=25, r=25) +ellipse = g.ellipse(cx=25, cy=25, rx=25, ry=20) +image = g.image(width=50, height=50, href='https://www.python.org/static/community_logos/python-powered-h-140x182.png') +line = g.line(x1=0, y1=0, x2=50, y2=50) +path = g.path(d='M 0,0 L 50,50 L 50,0 L 0,50 z') +path2 = g.path(d=g.p().M(0, 0).L(50, 50).L(50, 0).L(0, 50).z().d()) # same effect as above, but programmable. +path3 = g.p().M(0, 0).L(50, 50).L(50, 0).L(0, 50).z().path() # same effect as above, but a tad more concise. +polygon = g.polygon(points='0,0 50,50 50,0 0,50') +polyline = g.polyline(points='0,0 50,50 50,0 0,50') +rect = g.rect(x=0, y=0, width=50, height=50) +rounded_rect = g.rect(x=0, y=0, width=50, height=50, rx=10) +text = g.text(x=0, y=48, text='Z', font_size='4em') + +# Collect 'em all +shapes = [arc, circle, ellipse, image, line, path, path2, path3, polygon, polyline, rect, rounded_rect, text] + +# Apply fill/stroke for each shape +for shape in shapes: + shape.fill = 'none' if g.type_of(shape) == 'polyline' else 'crimson' + shape.stroke = 'darkred' + shape.stroke_width = 5 + +# Lay out shapes vertically +y = 10 +for shape in shapes: + shape.transform = f'translate(10,{y})' + y += 60 + +# Add shapes to the graphics card +page = site['/demo'] +page['example'] = ui.graphics_card( + box='1 1 1 10', view_box='0 0 70 800', width='100%', height='100%', + stage=g.stage( + arc=arc, + circle=circle, + ellipse=ellipse, + image=image, + line=line, + path=path, + path2=path2, + path3=path3, + polygon=polygon, + polyline=polyline, + rect=rect, + rounded_rect=rounded_rect, + text=text, + ), +) + +page.save() diff --git a/examples/graphics_spline.py b/examples/graphics_spline.py new file mode 100644 index 0000000000000000000000000000000000000000..4b218ce7f13f9d20f568de236d10cee0c6ea4553 --- /dev/null +++ b/examples/graphics_spline.py @@ -0,0 +1,73 @@ +# Graphics / Spline +# Use the #graphics module to render splines. +# --- + +import random +from h2o_wave import site, ui, graphics as g + +x = [i * 20 for i in range(50)] +y = [ + 88, 100, 116, 128, 126, 128, 118, 108, 121, 120, 99, 113, 117, 103, 98, 90, 104, 98, 82, 102, 104, 89, 87, 69, + 88, 97, 91, 105, 98, 86, 90, 107, 97, 107, 108, 128, 144, 148, 126, 106, 89, 99, 78, 70, 69, 64, 45, 29, 27, 38 +] +y0 = [v - random.randint(5, min(y)) for v in y] + +line_style = dict(fill='none', stroke='crimson', stroke_width=4) +area_style = dict(fill='crimson') + +splines = [ + # Lines + g.spline(x=x, y=y, **line_style), # same as curve='linear' + g.spline(x=x, y=y, curve='basis', **line_style), + g.spline(x=x, y=y, curve='basis-closed', **line_style), + g.spline(x=x, y=y, curve='basis-open', **line_style), + g.spline(x=x, y=y, curve='cardinal', **line_style), + g.spline(x=x, y=y, curve='cardinal-closed', **line_style), + g.spline(x=x, y=y, curve='cardinal-open', **line_style), + g.spline(x=x, y=y, curve='smooth', **line_style), + g.spline(x=x, y=y, curve='smooth-closed', **line_style), + g.spline(x=x, y=y, curve='smooth-open', **line_style), + g.spline(x=x, y=y, curve='linear', **line_style), + g.spline(x=x, y=y, curve='linear-closed', **line_style), + g.spline(x=x, y=y, curve='monotone-x', **line_style), + g.spline(x=x, y=y, curve='monotone-y', **line_style), + g.spline(x=x, y=y, curve='natural', **line_style), + g.spline(x=x, y=y, curve='step', **line_style), + g.spline(x=x, y=y, curve='step-after', **line_style), + g.spline(x=x, y=y, curve='step-before', **line_style), + # Areas + g.spline(x=x, y=y, y0=y0, **area_style), # same as curve='linear' + g.spline(x=x, y=y, y0=y0, curve='basis', **area_style), + g.spline(x=x, y=y, y0=[], curve='basis', **area_style), + g.spline(x=x, y=y, y0=y0, curve='basis-open', **area_style), + g.spline(x=x, y=y, y0=y0, curve='cardinal', **area_style), + g.spline(x=x, y=y, y0=[], curve='cardinal', **area_style), + g.spline(x=x, y=y, y0=y0, curve='cardinal-open', **area_style), + g.spline(x=x, y=y, y0=y0, curve='smooth', **area_style), + g.spline(x=x, y=y, y0=[], curve='smooth', **area_style), + g.spline(x=x, y=y, y0=y0, curve='smooth-open', **area_style), + g.spline(x=x, y=y, y0=y0, curve='linear', **area_style), + g.spline(x=x, y=y, y0=[], curve='linear', **area_style), + g.spline(x=x, y=y, y0=y0, curve='monotone-x', **area_style), + g.spline(x=x, y=y, y0=y0, curve='monotone-y', **area_style), + g.spline(x=x, y=y, y0=y0, curve='natural', **area_style), + g.spline(x=x, y=y, y0=y0, curve='step', **area_style), + g.spline(x=x, y=y, y0=y0, curve='step-after', **area_style), + g.spline(x=x, y=y, y0=y0, curve='step-before', **area_style), +] + +page = site['/demo'] +row, col = 1, 1 +for spline in splines: + page[f'spline_{col}_{row}'] = ui.graphics_card( + box=f'{col} {row} 3 1', view_box='0 0 1000 150', width='100%', height='100%', + stage=g.stage( + text=g.text(text=spline.curve or '', y=40, font_size=40), + spline=spline, + ), + ) + col += 3 + if col > 11: + row, col = row + 1, 1 + +page.save() diff --git a/examples/graphics_turtle.py b/examples/graphics_turtle.py new file mode 100644 index 0000000000000000000000000000000000000000..5e304f5761da146837645d0517fd9d3c083d16ca --- /dev/null +++ b/examples/graphics_turtle.py @@ -0,0 +1,18 @@ +# Graphics / Turtle +# Use turtle #graphics to draw paths. +# Original example: https://docs.python.org/3/library/turtle.html +# --- +from h2o_wave import site, ui, graphics as g + +t = g.turtle().f(100).r(90).pd() +for _ in range(36): + t.f(200).l(170) +spirograph = t.pu(1).path(stroke='red', fill='yellow') + +page = site['/demo'] +page['example'] = ui.graphics_card( + box='1 1 2 3', view_box='0 0 220 220', width='100%', height='100%', + scene=g.scene(foo=spirograph), +) + +page.save() diff --git a/examples/grid.py b/examples/grid.py new file mode 100644 index 0000000000000000000000000000000000000000..19e773df9867c6dbc6984a4865540da2c0b4fe8b --- /dev/null +++ b/examples/grid.py @@ -0,0 +1,29 @@ +# Grid +# Use a #grid card to lay out multiple child cards in table form. +# --- +import random + +from faker import Faker + +from h2o_wave import site, ui, pack, data + +fake = Faker() + +page = site['/demo'] + +c = page.add('example', ui.grid_card( + box='1 1 3 4', + title='Currency Trades', + cells=pack([ + dict(title='Currency', value='=currency'), + dict(title='Trades', + view='list_item1', + props=dict(title='=code', caption='1 hour ago', value='=trades', aux_value='=returns')), + ]), + data=data('currency code trades returns', -15), +)) +c.data = [ + [fake.cryptocurrency_name(), fake.cryptocurrency_code(), random.randint(100, 1000), random.randint(10, 100)] for + _ in range(15)] + +page.save() diff --git a/examples/hash_routing.py b/examples/hash_routing.py new file mode 100644 index 0000000000000000000000000000000000000000..4fa6d79ebc145c6a014586b1e0e7a89b9215c5e4 --- /dev/null +++ b/examples/hash_routing.py @@ -0,0 +1,39 @@ +# Routing / Hash +# Use the browser's [location hash](https://developer.mozilla.org/en-US/docs/Web/API/Location/hash) +# for #routing using URLs. +# +# The location hash can be accessed using `q.args['#']`. +# #location_hash +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.client.initialized = True + q.page['nav'] = ui.markdown_card( + box='1 1 4 2', + title='Links!', + content='[Spam](#menu/spam) / [Ham](#menu/ham) / [Eggs](#menu/eggs) / [About](#about)', + ) + q.page['blurb'] = ui.markdown_card( + box='1 3 4 2', + title='Store', + content='Welcome to our store!', + ) + + hash = q.args['#'] + blurb = q.page['blurb'] + if hash == 'menu/spam': + blurb.content = "Sorry, we're out of spam!" + elif hash == 'menu/ham': + blurb.content = "Sorry, we're out of ham!" + elif hash == 'menu/eggs': + blurb.content = "Sorry, we're out of eggs!" + elif hash == 'about': + blurb.content = 'Everything here is gluten-free!' + else: + blurb.content = 'Welcome to our store!' + + await q.page.save() diff --git a/examples/hash_routing_parameters.py b/examples/hash_routing_parameters.py new file mode 100644 index 0000000000000000000000000000000000000000..c2329d1522623d956e6f1480ce26ceed075255e9 --- /dev/null +++ b/examples/hash_routing_parameters.py @@ -0,0 +1,67 @@ +# Routing / Hash / Parameters +# Use the browser's [location hash](https://developer.mozilla.org/en-US/docs/Web/API/Location/hash) +# for #routing using URLs, with parameters. +# --- +from h2o_wave import main, app, Q, ui, on, handle_on + +air_passengers_fields = ['Year', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'] +air_passengers_rows = [ + ['1949', '112', '118', '132', '129', '121', '135'], + ['1950', '115', '126', '141', '135', '125', '149'], + ['1951', '145', '150', '178', '163', '172', '178'], + ['1952', '171', '180', '193', '181', '183', '218'], + ['1953', '196', '196', '236', '235', '229', '243'], + ['1954', '204', '188', '235', '227', '234', '264'], + ['1955', '242', '233', '267', '269', '270', '315'], + ['1956', '284', '277', '317', '313', '318', '374'], + ['1957', '315', '301', '356', '348', '355', '422'], + ['1958', '340', '318', '362', '348', '363', '435'], + ['1959', '360', '342', '406', '396', '420', '472'], + ['1960', '417', '391', '419', '461', '472', '535'], +] + + +def make_markdown_row(values): + return f"| {' | '.join([str(x) for x in values])} |" + + +def make_markdown_table(fields, rows): + return '\n'.join([ + make_markdown_row(fields), + make_markdown_row('-' * len(fields)), + '\n'.join([make_markdown_row(row) for row in rows]), + ]) + + +def add_links_to_cells(rows): + return [[f'[{cell}](#row{i + 1}/col{j + 1})' for j, cell in enumerate(row)] for i, row in enumerate(rows)] + + +@on('#row{row:int}/col{col:int}') +async def print_clicked_cell(q: Q, row: int, col: int): + q.page['message'].content = f'You clicked on row {row}, column {col}!' + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.client.initialized = True + q.page['table'] = ui.form_card( + box='1 1 4 7', + items=[ + ui.text_l('Airline Passenger Counts'), + ui.text(make_markdown_table( + fields=air_passengers_fields, + rows=add_links_to_cells(air_passengers_rows), + )), + ], + ) + q.page['message'] = ui.markdown_card( + box='1 8 4 1', + title='', + content='Click on a cell in the table above!', + ) + + await handle_on(q) + + await q.page.save() diff --git a/examples/header.py b/examples/header.py new file mode 100644 index 0000000000000000000000000000000000000000..c8fa21499e6854230ae6a6c5290f1fa0fea97e19 --- /dev/null +++ b/examples/header.py @@ -0,0 +1,64 @@ +# Header +# Use a header card to display a page #header. +# --- +from h2o_wave import site, ui + +image = 'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&h=750&w=1260' +commands = [ + ui.command(name='profile', label='Profile', icon='Contact'), + ui.command(name='preferences', label='Preferences', icon='Settings'), + ui.command(name='logout', label='Logout', icon='SignOut'), +] +page = site['/demo'] +page['header1'] = ui.header_card( + box='1 1 9 1', + title='Transparent header', + subtitle='And now for something completely different!', + image='https://wave.h2o.ai/img/h2o-logo.svg', + items=[ + ui.button(name='btn1', label='Button 1'), + ui.button(name='btn2', label='Button 2'), + ui.button(name='btn3', label='Button 3'), + ], + secondary_items=[ui.textbox(name='search', icon='Search', width='300px', placeholder='Search...')], + color='transparent' +) +page['header2'] = ui.header_card( + box='1 2 9 1', + title='Card color header', + subtitle='And now for something completely different!', + items=[ui.menu(image=image, items=commands)], + secondary_items=[ + ui.button(name='btn1', label='Link 1', link=True), + ui.button(name='btn2', label='Link 2', link=True), + ui.button(name='btn3', label='Link 3', link=True), + ], + nav=[ + ui.nav_group('Menu', items=[ + ui.nav_item(name='#menu/spam', label='Spam'), + ui.nav_item(name='#menu/ham', label='Ham'), + ui.nav_item(name='#menu/eggs', label='Eggs'), + ]), + ui.nav_group('Help', items=[ + ui.nav_item(name='#about', label='About'), + ui.nav_item(name='#support', label='Support'), + ]) + ], + color='card', +) +page['header3'] = ui.header_card( + box='1 3 9 1', + title='Primary color header', + subtitle='And now for something completely different!', + icon='Cycling', + icon_color='$card', + items=[ui.menu(icon='Add', items=commands)], + secondary_items=[ + ui.tabs(name='menu', value='email', link=True, items=[ + ui.tab(name='email', label='Mail', icon='Mail'), + ui.tab(name='events', label='Events', icon='Calendar'), + ui.tab(name='spam', label='Spam', icon='Heart'), + ]), + ] +) +page.save() diff --git a/examples/hello_world.py b/examples/hello_world.py new file mode 100644 index 0000000000000000000000000000000000000000..c63d89eccdb230d0e87bd71626c5ad76f2c885a3 --- /dev/null +++ b/examples/hello_world.py @@ -0,0 +1,21 @@ +# Hello World! +# A simple example to get you started with Wave. +# #hello_world +# --- +# Import `Site` and the `ui` module from the `h2o_wave` package +from h2o_wave import site, ui + +# Get the web page at route '/demo'. +# If you're running this example on your local machine, +# this page will refer to http://localhost:10101/demo. +page = site['/demo'] + +# Add a Markdown card named `hello` to the page. +page['hello'] = ui.markdown_card( + box='1 1 2 2', + title='Hello World!', + content='And now for something completely different!', +) + +# Finally, sync the page to send our changes to the server. +page.save() diff --git a/examples/http_client.py b/examples/http_client.py new file mode 100644 index 0000000000000000000000000000000000000000..c590107de8620e4d3e083697b62064c9d816bd17 --- /dev/null +++ b/examples/http_client.py @@ -0,0 +1,26 @@ +# HTTP / Client +# Use any http client to communicate with RESTful APIs. +# #http +# --- +from h2o_wave import main, app, Q, ui +import httpx + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['form'] = ui.form_card(box='1 1 4 6', items=[ + ui.buttons([ + ui.button(name='clear', label='Clear'), + ui.button(name='download', label='Download', primary=True) + ]), + ui.text_m(name='text', content='No data yet.'), + ]) + q.client.initialized = True + if q.args.download: + response = httpx.get('https://jsonplaceholder.typicode.com/posts/1') + q.page['form'].text.content = response.text + if q.args.clear: + q.page['form'].text.content = 'No data yet.' + + await q.page.save() diff --git a/examples/image.py b/examples/image.py new file mode 100644 index 0000000000000000000000000000000000000000..5d3d6177af5e3b5c0fd4b86fba0a2e7b68ed9ee3 --- /dev/null +++ b/examples/image.py @@ -0,0 +1,59 @@ +# Image +# Use an image card to display an image by specifying its URL or a data URL in case of the base64-encoded #image. +# --- +from h2o_wave import site, ui +import io +import base64 +import numpy as np +import matplotlib.pyplot as plt + +n = 25 +plt.figure(figsize=(3, 3)) +plt.scatter( + [0.7003673, 0.74275081, 0.70928001, 0.56674552, 0.97778533, 0.70633485, + 0.24791576, 0.15788335, 0.69769852, 0.71995667, 0.25774443, 0.34154678, + 0.96876117, 0.6945071, 0.46638326, 0.7028127, 0.51178587, 0.92874137, + 0.7397693, 0.62243903, 0.65154547, 0.39680761, 0.54323939, 0.79989953, + 0.72154473], + [0.29536398, 0.16094588, 0.20612551, 0.13432539, 0.48060502, 0.34252181, + 0.36296929, 0.97291764, 0.11094361, 0.38826409, 0.78306588, 0.97289726, + 0.48320961, 0.33642111, 0.56741904, 0.04794151, 0.38893703, 0.90630365, + 0.16101821, 0.74362113, 0.63297416, 0.32418002, 0.92237653, 0.23722644, + 0.82394557], + s=(30 * np.asarray([ + 0.75060714, 0.11378445, 0.84536125, 0.92393213, 0.22083679, 0.93305388, + 0.48899874, 0.47471864, 0.08916747, 0.22994818, 0.71593741, 0.49612616, + 0.76648938, 0.89679732, 0.77222302, 0.92717429, 0.61465203, 0.60906377, + 0.68468487, 0.25101297, 0.83783764, 0.11861562, 0.79723474, 0.94900427, + 0.14806288])) ** 2, + c=[0.90687198, 0.78837333, 0.76840584, 0.59849648, 0.44214562, 0.72303802, + 0.41661825, 0.2268104, 0.45422734, 0.84794375, 0.93665595, 0.95603618, + 0.39209432, 0.70832467, 0.12951583, 0.35379639, 0.40427152, 0.6485339, + 0.03307097, 0.53800936, 0.13171312, 0.52093493, 0.10248479, 0.15798038, + 0.92002965], + alpha=0.5, +) + +buf = io.BytesIO() +plt.savefig(buf, format='png') +buf.seek(0) +image = base64.b64encode(buf.read()).decode('utf-8') + +page = site['/demo'] +page['example1'] = ui.image_card( + box='1 1 2 4', + title='An image', + type='png', + image=image, +) + +# Another way to achieve the same result is to use a data URL for the path: +# The example below constructs the data URL from the base64-encoded +# used in the previous example. +page['example2'] = ui.image_card( + box='3 1 2 4', + title='An image', + path=f"data:image/png;base64,{image}", +) + +page.save() \ No newline at end of file diff --git a/examples/image_annotator.py b/examples/image_annotator.py new file mode 100644 index 0000000000000000000000000000000000000000..1401961d34a697080b2f13d5593e25c4299be1a1 --- /dev/null +++ b/examples/image_annotator.py @@ -0,0 +1,39 @@ +# Form / Image Annotator +# Use when you need to annotate images. +# #form #annotator #image +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.annotator is not None: + q.page['example'].items = [ + ui.text(f'annotator={q.args.annotator}'), + ui.button(name='back', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 5 8', items=[ + ui.image_annotator( + name='annotator', + title='Drag to annotate', + image='https://images.unsplash.com/photo-1535082623926-b39352a03fb7?auto=compress&cs=tinysrgb&w=1260&h=750&q=80', + image_height='450px', + tags=[ + ui.image_annotator_tag(name='v', label='Vehicle', color='$cyan'), + ui.image_annotator_tag(name='a', label='Animal', color='$blue'), + ], + items=[ + ui.image_annotator_item(shape=ui.image_annotator_rect(x1=657, y1=273, x2=848, y2=440), tag='v'), + ui.image_annotator_item(tag='a', shape=ui.image_annotator_polygon([ + ui.image_annotator_point(x=327, y=687), + ui.image_annotator_point(x=397, y=498), + ui.image_annotator_point(x=547, y=450), + ui.image_annotator_point(x=705, y=517), + ui.image_annotator_point(x=653, y=692) + ])), + ], + ), + ui.button(name='submit', label='Submit', primary=True) + ]) + await q.page.save() diff --git a/examples/image_popup.py b/examples/image_popup.py new file mode 100644 index 0000000000000000000000000000000000000000..847446606acd46b9d18fa9c36b0b13029415a32a --- /dev/null +++ b/examples/image_popup.py @@ -0,0 +1,14 @@ +# Image / Popup +# Display a popup with the large image after clicking on the #image. It's recommended to use a smaller image for `path` and larger (higher quality) image for the `path_popup`. +# --- +from h2o_wave import site, ui + +page = site['/demo'] +page['example'] = ui.image_card( + box='1 1 3 4', + title='Image popup', + path='https://via.placeholder.com/600x400', + path_popup='https://via.placeholder.com/1200x800' +) + +page.save() \ No newline at end of file diff --git a/examples/inline.py b/examples/inline.py new file mode 100644 index 0000000000000000000000000000000000000000..c20ee648415c4bc6dbdbe985387abe444873d8d0 --- /dev/null +++ b/examples/inline.py @@ -0,0 +1,57 @@ +# Inline +# Create an inline (horizontal) list of components. +# --- + +from h2o_wave import main, app, Q, ui + +all_justify = ['start', 'end', 'center', 'between', 'around'] +all_align = ['start', 'end', 'center'] + +justify_choices = [ui.choice(opt, opt) for opt in all_justify] +align_choices = [ui.choice(opt, opt) for opt in all_align] + + +@app('/demo') +async def serve(q: Q): + justify = q.args.justify if q.args.justify else 'start' + align = q.args.align if q.args.align else 'center' + + q.page['example'] = ui.form_card(box='1 1 -1 3', items=[ + ui.inline([ + ui.choice_group(name='justify', label='justify', value=justify, choices=justify_choices, trigger=True), + ui.choice_group(name='align', label='align', value=align, choices=align_choices, trigger=True), + ], justify=justify, align=align) + ]) + q.page['example2'] = ui.form_card(box='1 4 -1 3', items=[ + ui.text_xl('Header'), + ui.inline( + height='1', + justify='around', + align='center', + items=[ + ui.inline( + direction='column', + items=[ + ui.text_l(content='Sub title 1'), + ui.text(content='Lorem ipsum dolor sit amet'), + ] + ), + ui.inline( + direction='column', + items=[ + ui.text_l(content='Sub title 2'), + ui.text(content='Lorem ipsum dolor sit amet'), + ] + ), + ui.inline( + direction='column', + items=[ + ui.text_l(content='Sub title 3'), + ui.text(content='Lorem ipsum dolor sit amet'), + ] + ), + ] + ), + ui.text('Footer'), + ]) + await q.page.save() diff --git a/examples/issue_tracker.py b/examples/issue_tracker.py new file mode 100644 index 0000000000000000000000000000000000000000..21c5703bdab628daba511f2da71eef91dc3e058c --- /dev/null +++ b/examples/issue_tracker.py @@ -0,0 +1,138 @@ +# Issue Tracker +# Implement a simple issue tracker using a #table to create master-detail views. +# --- +from h2o_wave import main, app, Q, ui +from faker import Faker + +fake = Faker() + +_id = 0 + + +class Issue: + def __init__(self, text: str, status: str): + global _id + _id += 1 + self.id = f'I{_id}' + self.text = text + self.status = status + self.views = 0 + + +# Create some issues +issues = [Issue(text=fake.sentence(), status='Open') for i in range(12)] + +# Build a lookup of issues for convenience +issue_lookup = {issue.id: issue for issue in issues} + +# Create columns for our issue table. +columns = [ + ui.table_column(name='text', label='Issue'), + ui.table_column(name='status', label='Status'), + ui.table_column(name='views', label='Views'), +] + + +def make_issue_table(allow_multiple_selection=False): + return ui.table( + name='issues', + columns=columns, + rows=[ui.table_row(name=issue.id, cells=[issue.text, issue.status, str(issue.views)]) for issue in issues], + multiple=allow_multiple_selection + ) + + +async def edit_multiple(q: Q): + q.page['form'] = ui.form_card( + box='1 1 6 7', + items=[ + make_issue_table(allow_multiple_selection=True), # This time, allow multiple selections + ui.buttons([ + ui.button(name='reopen_issues', label='Reopen Selected', primary=True), + ui.button(name='close_issues', label='Close Selected', primary=True), + ui.button(name='back', label='Back to safety') + ]), + ] + ) + await q.page.save() + + +async def show_issues(q: Q): + q.page['form'] = ui.form_card( + box='1 1 4 7', + items=[ + make_issue_table(), + ui.buttons([ui.button(name='edit_multiple', label='Edit Multiple...', primary=True)]), + ] + ) + await q.page.save() + + +async def show_issue(q: Q, issue_id: str): + issue = issue_lookup[issue_id] + issue.views += 1 + + q.client.active_issue_id = issue_id + + q.page['form'] = ui.form_card( + box='1 1 4 4', + items=[ + ui.text_xl(f'Issue {issue.id}'), + ui.text(issue.text), + ui.text_xs(f'({issue.views} views)'), + ui.buttons([ + ui.button( + name='close_issue' if issue.status == 'Open' else 'reopen_issue', + label="Close Issue" if issue.status == 'Open' else "Reopen Issue", + primary=True, + ), + ui.button(name='back', label='Back'), + ]), + ] + ) + + await q.page.save() + + +async def close_issue(q: Q): + issue = issue_lookup[q.client.active_issue_id] + issue.status = 'Closed' + await show_issues(q) + + +async def close_issues(q: Q): + for issue_id in q.args.issues: + issue = issue_lookup[issue_id] + issue.status = 'Closed' + await show_issues(q) + + +async def reopen_issue(q: Q): + issue = issue_lookup[q.client.active_issue_id] + issue.status = 'Open' + await show_issues(q) + + +async def reopen_issues(q: Q): + for issue_id in q.args.issues: + issue = issue_lookup[issue_id] + issue.status = 'Open' + await show_issues(q) + + +@app('/demo') +async def serve(q: Q): + if q.args.edit_multiple: + await edit_multiple(q) + elif q.args.reopen_issues: + await reopen_issues(q) + elif q.args.close_issues: + await close_issues(q) + elif q.args.reopen_issue: + await reopen_issue(q) + elif q.args.close_issue: + await close_issue(q) + elif q.args.issues: # An issue was clicked on + await show_issue(q, q.args.issues[0]) + else: + await show_issues(q) diff --git a/examples/label.py b/examples/label.py new file mode 100644 index 0000000000000000000000000000000000000000..77e80ce46f3c1a54916e974b79dc0eed57270982 --- /dev/null +++ b/examples/label.py @@ -0,0 +1,17 @@ +# Form / Label +# Use labels to give a name to a component or a group of components in a #form. +# #label +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +page['example'] = ui.form_card( + box='1 1 4 7', + items=[ + ui.label(label='Standard Label'), + ui.label(label='Required Label', required=True), + ui.label(label='Disabled Label', disabled=True), + ] +) +page.save() diff --git a/examples/layout.py b/examples/layout.py new file mode 100644 index 0000000000000000000000000000000000000000..32db46267ed8267fa23be4f570d81bd26af95b37 --- /dev/null +++ b/examples/layout.py @@ -0,0 +1,30 @@ +# Layout / Position +# How to adjust the position of cards on a page. +# #layout +# --- + +from h2o_wave import site, ui + +# Every page has a grid system in place. +# The grid has 12 columns and 10 rows. +# A column is 134 pixels wide. +# A row is 76 pixels high. +# The gap between rows and columns is set to 15 pixels. + +# Cards have a `box` attribute that specifies its column, row, width and height. +# box = 'column row width height' +# They indicate the 1-based column/row to position the top-left corner of the card. + +# In this example, we place a 1x1 card in each column/row on a page +# to demonstrate their column/row values. + +page = site['/demo'] +columns = 12 +rows = 10 + +for column in range(1, columns + 1): + for row in range(1, rows + 1): + box = f'{column} {row} 1 1' + page[f'card_{column}_{row}'] = ui.markdown_card(box=box, title=box, content='') + +page.save() diff --git a/examples/layout_responsive.py b/examples/layout_responsive.py new file mode 100644 index 0000000000000000000000000000000000000000..b97e0f0becb57c79d05a5bd9e1d24bffdd4e081d --- /dev/null +++ b/examples/layout_responsive.py @@ -0,0 +1,127 @@ +# Layout / Responsive +# How to create a #responsive #layout. +# --- +from h2o_wave import site, ui + +page = site['/demo'] +page.drop() + +content = '![Wave University](https://raw.githubusercontent.com/h2oai/wave/master/assets/brand/wave-university-wide.png)' + +# The meta card's 'zones' attribute defines placeholder zones to lay out cards for different viewport sizes. +# We define three layout schemes here. +page['meta'] = ui.meta_card(box='', layouts=[ + ui.layout( + # If the viewport width >= 0: + breakpoint='xs', + zones=[ + # 80px high header + ui.zone('header', size='80px'), + # Use remaining space for content + ui.zone('content') + ] + ), + ui.layout( + # If the viewport width >= 768: + breakpoint='m', + zones=[ + # 80px high header + ui.zone('header', size='80px'), + # Use remaining space for body + ui.zone('body', direction=ui.ZoneDirection.ROW, zones=[ + # 250px wide sidebar + ui.zone('sidebar', size='250px'), + # Use remaining space for content + ui.zone('content'), + ]), + ui.zone('footer'), + ] + ), + ui.layout( + # If the viewport width >= 1200: + breakpoint='xl', + width='1200px', + zones=[ + # 80px high header + ui.zone('header', size='80px'), + # Use remaining space for body + ui.zone('body', direction=ui.ZoneDirection.ROW, zones=[ + # 300px wide sidebar + ui.zone('sidebar', size='300px'), + # Use remaining space for other widgets + ui.zone('other', zones=[ + # Use one half for charts + ui.zone('charts', direction=ui.ZoneDirection.ROW), + # Use other half for content + ui.zone('content'), + ]), + ]), + ui.zone('footer'), + ] + ) +]) + +page['header'] = ui.header_card( + # Place card in the header zone, regardless of viewport size. + box='header', + title='Lorem Ipsum', + subtitle='Excepteur sint occaecat cupidatat', + nav=[ + ui.nav_group('Menu', items=[ + ui.nav_item(name='#menu/spam', label='Spam'), + ui.nav_item(name='#menu/ham', label='Ham'), + ui.nav_item(name='#menu/eggs', label='Eggs'), + ]), + ui.nav_group('Help', items=[ + ui.nav_item(name='#about', label='About'), + ui.nav_item(name='#support', label='Support'), + ]) + ], +) +page['controls'] = ui.markdown_card( + # If the viewport width >= 0, place in content zone. + # If the viewport width >= 768, place in sidebar zone. + # If the viewport width >= 1200, place in sidebar zone. + box=ui.boxes('content', 'sidebar', 'sidebar'), + title='Controls', + content=content, +) +page['chart1'] = ui.markdown_card( + box=ui.boxes( + # If the viewport width >= 0, place as second item in content zone. + ui.box(zone='content', order=2), + # If the viewport width >= 768, place in content zone. + 'content', + # If the viewport width >= 1200, place as first item in charts zone, use 2 parts of available space. + ui.box(zone='charts', order=1, size=2), + ), + title='Chart 1', + content=content, +) +page['chart2'] = ui.markdown_card( + box=ui.boxes( + # If the viewport width >= 0, place as third item in content zone. + ui.box(zone='content', order=3), + # If the viewport width >= 768, place as second item in content zone. + ui.box(zone='content', order=2), + # If the viewport width >= 1200, place as second item in charts zone, use 1 part of available space. + ui.box(zone='charts', order=2, size=1), + ), + title='Chart 2', + content=content, +) +page['content'] = ui.markdown_card( + box=ui.boxes( + # If the viewport width >= 0, place as fourth item in content zone. + ui.box(zone='content', order=4), + # If the viewport width >= 768, place as third item in content zone. + ui.box(zone='content', order=3), + # If the viewport width >= 1200, place in content zone. + 'content', + ), + title='Content', + content=content, +) +page['footer'] = ui.footer_card(box='footer', caption='(c) 2020 Lowest Common Denominator, Inc. ') + +page.save() diff --git a/examples/layout_size.py b/examples/layout_size.py new file mode 100644 index 0000000000000000000000000000000000000000..75acd8e0c570091e45330eee50008631521f627f --- /dev/null +++ b/examples/layout_size.py @@ -0,0 +1,39 @@ +# Layout / Size +# How to adjust the size of cards on a page. #layout +# --- + +from h2o_wave import site, ui + +# Every page has a grid system in place. +# The grid has 12 columns and 10 rows. +# A column is 134 pixels wide. +# A row is 76 pixels high. +# The gap between rows and columns is set to 15 pixels. + +# Cards have a `box` attribute that specifies its column, row, width and height. +# box = 'column row width height' +# They indicate the 1-based column/row to position the top-left corner of the card. + +# In this example, we place multiple cards on a page to demonstrate their `box` values. + +page = site['/demo'] +boxes = [ + '1 1 1 1', + '2 1 2 1', + '4 1 3 1', + '7 1 4 1', + '11 1 2 2', + '1 2 1 9', + '2 2 1 4', + '3 2 1 2', + '2 6 1 5', + '3 4 1 7', + '4 2 7 9', + '11 9 2 2', + '11 3 2 6', +] + +for box in boxes: + page[f'card_{box.replace(" ", "_")}'] = ui.markdown_card(box=box, title=box, content='') + +page.save() diff --git a/examples/link.py b/examples/link.py new file mode 100644 index 0000000000000000000000000000000000000000..6f3b3bdb5b2ecc1017fd5de96503d23b29bfd00a --- /dev/null +++ b/examples/link.py @@ -0,0 +1,23 @@ +# Form / Link +# Use link to allow #navigation to internal and external URLs. +# #form #link +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +page['example'] = ui.form_card( + box='1 1 4 7', + items=[ + ui.link(label='Internal link', path='starred'), + ui.link(label='Internal link, new tab', path='starred', target=''), + ui.link(label='Internal link, new tab', path='starred', target='_blank'), # same as target='' + ui.link(label='Internal link, disabled', path='starred', disabled=True), + ui.link(label='External link', path='https://h2o.ai'), + ui.link(label='External link, new tab', path='https://h2o.ai', target=''), + ui.link(label='External link, new tab', path='https://h2o.ai', target='_blank'), # same as target='' + ui.link(label='External link, disabled', path='https://h2o.ai', disabled=True), + ui.link(label='Download link', path='http://localhost:10101/assets/brand/h2o-wave-b&w.png', download=True), + ] +) +page.save() diff --git a/examples/links.py b/examples/links.py new file mode 100644 index 0000000000000000000000000000000000000000..e3f23cc20570c397ce88563b76cbc97e7fb36e51 --- /dev/null +++ b/examples/links.py @@ -0,0 +1,26 @@ +# Form / Links +# Use links to allow #navigation to multiple internal and external URLs. +# #form #link +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +page['example'] = ui.form_card( + box='1 1 3 3', + items=[ + ui.text_l(content='**Vertical set of links with a label**'), + ui.links(label='Second Column', items=[ + ui.link(label='Sample link', path='https://www.h2o.ai/', target='_blank'), + ui.link(label='Sample link', path='https://www.h2o.ai/', target='_blank'), + ui.link(label='Sample link', path='https://www.h2o.ai/', target='_blank'), + ]), + ui.text_l(content='**Horizontal set of links**'), + ui.links(inline=True, items=[ + ui.link(label='Sample link', path='https://www.h2o.ai/', target='_blank'), + ui.link(label='Sample link', path='https://www.h2o.ai/', target='_blank'), + ui.link(label='Sample link', path='https://www.h2o.ai/', target='_blank'), + ]), + ] +) +page.save() diff --git a/examples/list.py b/examples/list.py new file mode 100644 index 0000000000000000000000000000000000000000..6530d38a62ff820026f6ad394b3592dbb0d31f22 --- /dev/null +++ b/examples/list.py @@ -0,0 +1,24 @@ +# Lists +# Use list cards to lay out multiple child cards in the form of a #list. +# --- +import random + +from faker import Faker + +from h2o_wave import site, ui, pack, data + +fake = Faker() + +page = site['/demo'] + +c = page.add('example', ui.list_card( + box='1 1 2 4', + item_view='list_item1', + item_props=pack(dict(title='=code', caption='=currency', value='=trades', aux_value='=returns')), + title='Exchange Rates', + data=data('currency code trades returns', -15), +)) +c.data = [[fake.cryptocurrency_name(), fake.cryptocurrency_code(), random.randint(100, 1000), random.randint(10, 100)] + for _ in range(15)] + +page.save() diff --git a/examples/markdown.py b/examples/markdown.py new file mode 100644 index 0000000000000000000000000000000000000000..8ef9b34da15072226cb72f84fee55f3a652a0035 --- /dev/null +++ b/examples/markdown.py @@ -0,0 +1,46 @@ +# Markdown +# Use a markdown card to display formatted content using #markdown. +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +sample_markdown = '''= +The **quick** _brown_ fox jumped over the lazy dog. + +Block quote: + +> The quick brown fox jumped over the lazy dog. + +Unordered list: + +- The quick brown fox jumped over the lazy dog. +- The quick brown fox jumped over the lazy dog. +- The quick brown fox jumped over the lazy dog. + +Ordered list: + +1. The quick brown fox jumped over the lazy dog. +1. The quick brown fox jumped over the lazy dog. +1. The quick brown fox jumped over the lazy dog. + +Image: + +![Monty Python](https://upload.wikimedia.org/wikipedia/en/c/cb/Flyingcircus_2.jpg) + +Table: + +| Column 1 | Column 2 | Column 3 | +| -------- | -------- | -------- | +| Item 1 | Item 2 | Item 3 | +| Item 1 | Item 2 | Item 3 | +| Item 1 | Item 2 | Item 3 | + +''' + +page['example'] = ui.markdown_card( + box='1 1 3 10', + title='I was made using markdown!', + content=sample_markdown, +) +page.save() diff --git a/examples/markdown_data.py b/examples/markdown_data.py new file mode 100644 index 0000000000000000000000000000000000000000..e60e2ce580f06afca4b64756018f6362e7aad0e0 --- /dev/null +++ b/examples/markdown_data.py @@ -0,0 +1,28 @@ +# Markdown / Data +# Display dynamic formatted content using #markdown. +# --- +import time +from h2o_wave import site, ui + +page = site['/demo'] + +beer_verse = ''' +{{before}} bottles of beer on the wall, {{before}} bottles of beer. + +Take one down, pass it around, {{after}} bottles of beer on the wall... +''' + +beer_card = page.add('example', ui.markdown_card( + box='1 1 4 2', + title='99 Bottles of Beer', + content='=' + beer_verse, # Make the verse a template expression by prefixing a '='. + data=dict(before='99', after='98'), +)) + +page.save() + +for i in range(98, 2, -1): + time.sleep(1) + beer_card.data.before = str(i) + beer_card.data.after = str(i - 1) + page.save() diff --git a/examples/markdown_submit_text.py b/examples/markdown_submit_text.py new file mode 100644 index 0000000000000000000000000000000000000000..d53b016f23a54a4388e59b26fabc76a8987a4753 --- /dev/null +++ b/examples/markdown_submit_text.py @@ -0,0 +1,25 @@ +# Markdown / Submit / Text +# Use "?" to prefix the desired q.args.key that you want to have submitted after clicking a phrase. +# --- + +from h2o_wave import main, app, Q, ui + + +def get_form_items(clicked: str): + return [ + ui.text(content='The quick brown [fox](?fox) jumps over the lazy [dog](?dog)'), + ui.text(content=f'Clicked: {clicked}'), + ] + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['example'] = ui.form_card(box='1 1 3 2', items=get_form_items('Nothing')) + q.client.initialized = True + + if q.args.fox: + q.page['example'].items = get_form_items('fox') + if q.args.dog: + q.page['example'].items = get_form_items('dog') + await q.page.save() diff --git a/examples/markdown_table.py b/examples/markdown_table.py new file mode 100644 index 0000000000000000000000000000000000000000..3c514b555e7e90bc9a98d9973dc5929ac2df4728 --- /dev/null +++ b/examples/markdown_table.py @@ -0,0 +1,47 @@ +# Markdown / Table +# Display a #table using #markdown. That's different than having a table with Markdown content. +# --- +from h2o_wave import site, ui + +air_passengers_fields = ['Year', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'] +air_passengers_rows = [ + ['1949', '112', '118', '132', '129', '121', '135'], + ['1950', '115', '126', '141', '135', '125', '149'], + ['1951', '145', '150', '178', '163', '172', '178'], + ['1952', '171', '180', '193', '181', '183', '218'], + ['1953', '196', '196', '236', '235', '229', '243'], + ['1954', '204', '188', '235', '227', '234', '264'], + ['1955', '242', '233', '267', '269', '270', '315'], + ['1956', '284', '277', '317', '313', '318', '374'], + ['1957', '315', '301', '356', '348', '355', '422'], + ['1958', '340', '318', '362', '348', '363', '435'], + ['1959', '360', '342', '406', '396', '420', '472'], + ['1960', '417', '391', '419', '461', '472', '535'], +] + + +def make_markdown_row(values): + return f"| {' | '.join([str(x) for x in values])} |" + + +def make_markdown_table(fields, rows): + return '\n'.join([ + make_markdown_row(fields), + make_markdown_row('-' * len(fields)), + '\n'.join([make_markdown_row(row) for row in rows]), + ]) + + +page = site['/demo'] + +v = page.add('example', ui.form_card( + box='1 1 4 8', + items=[ + ui.text(make_markdown_table( + fields=air_passengers_fields, + rows=air_passengers_rows, + )), + ], +)) + +page.save() diff --git a/examples/markup.py b/examples/markup.py new file mode 100644 index 0000000000000000000000000000000000000000..a8abf591e76842dd3a652ddabc0633146f59ee92 --- /dev/null +++ b/examples/markup.py @@ -0,0 +1,21 @@ +# Markup +# Use a #markup card to display formatted content using #HTML. +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +menu = ''' +
    +
  1. Spam
  2. +
  3. Ham
  4. +
  5. Eggs
  6. +
+''' + +page['example'] = ui.markup_card( + box='1 1 2 2', + title='Menu', + content=menu, +) +page.save() diff --git a/examples/menu.py b/examples/menu.py new file mode 100644 index 0000000000000000000000000000000000000000..6c3847e3c7cbb47c2f59c13ca7d3302f4b553127 --- /dev/null +++ b/examples/menu.py @@ -0,0 +1,34 @@ +# Form / Menu +# Create a contextual menu component. Useful when you have a lot of links and want to conserve the space. +# #form #menu +# --- +from h2o_wave import main, app, Q, ui + +image = 'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&h=750&w=1260' +commands = [ + ui.command(name='profile', label='Profile', icon='Contact'), + ui.command(name='preferences', label='Preferences', icon='Settings'), + ui.command(name='logout', label='Logout', icon='SignOut'), +] + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['example'] = ui.form_card(box='1 1 2 3', items=[]) + q.client.initialized = True + if 'profile' in q.args and not q.args.show_form: + q.page['example'].items = [ + ui.text(f'profile={q.args.profile}'), + ui.text(f'preferences={q.args.preferences}'), + ui.text(f'logout={q.args.logout}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'].items = [ + ui.menu(image=image, items=commands), + ui.menu(icon='Add', items=commands), + ui.menu(label='App', items=commands), + ui.menu(items=commands) + ] + await q.page.save() diff --git a/examples/message_bar.py b/examples/message_bar.py new file mode 100644 index 0000000000000000000000000000000000000000..963b025ba9767ad66056b0e3cd97f9cb5898bfe4 --- /dev/null +++ b/examples/message_bar.py @@ -0,0 +1,27 @@ +# Form / Message Bar +# Use message bars to indicate relevant status information. +# #form #message_bar +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +page['example'] = ui.form_card( + box='1 1 3 7', + items=[ + ui.message_bar(type='blocked', text='This action is blocked.'), + ui.message_bar(type='error', text='This is an error message'), + ui.message_bar(type='warning', text='This is a warning message.'), + ui.message_bar(type='info', text='This is an information message.'), + ui.message_bar(type='success', text='This is a success message.'), + ui.message_bar(type='danger', text='This is a danger message.'), + ui.message_bar(type='success', text='This is a **MARKDOWN** _message_.'), + ui.message_bar(type='success', text='This is an HTML message.'), + ui.message_bar(type='info', text='With a button.', buttons=[ui.button(name='btn', label='Button')]), + ui.message_bar(type='info', text='With a button as link.', + buttons=[ui.button(name='btn', label='Button', link=True)]), + ui.message_bar(type='info', text='With multiline text that should hopefully span at least 2 rows', + buttons=[ui.button(name='btn', label='Button')]), + ] +) +page.save() diff --git a/examples/meta_dialog.py b/examples/meta_dialog.py new file mode 100644 index 0000000000000000000000000000000000000000..298f9afa01e7bf188a126fcd0ee72a30d48aead5 --- /dev/null +++ b/examples/meta_dialog.py @@ -0,0 +1,37 @@ +# Meta / Dialog +# Display a #dialog. #meta +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['meta'] = ui.meta_card(box='') + q.page['example'] = ui.form_card(box='1 1 2 1', items=[ + ui.button(name='show_dialog', label='Order donuts', primary=True) + ]) + q.client.initialized = True + else: + if q.args.show_dialog: + q.page['meta'].dialog = ui.dialog(title='Order Donuts', items=[ + ui.text('Donuts cost $1.99. Proceed?'), + ui.buttons([ui.button(name='next_step', label='Next', primary=True)]) + ]) + elif q.args.next_step: + q.page['meta'].dialog.items = [ + ui.text('You will be charged $1.99. Proceed?'), + ui.buttons([ + ui.button(name='cancel', label='Back to safety'), + ui.button(name='submit', label='Place order', primary=True), + ]) + ] + elif q.args.submit: + q.page['example'].items = [ui.message_bar('success', 'Order placed!')] + q.page['meta'].dialog = None + + elif q.args.cancel: + q.page['example'].items = [ui.message_bar('info', 'Order canceled!')] + q.page['meta'].dialog = None + + await q.page.save() diff --git a/examples/meta_dialog_closable.py b/examples/meta_dialog_closable.py new file mode 100644 index 0000000000000000000000000000000000000000..893f38146c284692057c409a41b749b114842de1 --- /dev/null +++ b/examples/meta_dialog_closable.py @@ -0,0 +1,40 @@ +# Meta / Dialog / Closable +# Display a #dialog having a close button, and detect when it's closed. #meta +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + # Create an empty meta_card to hold the dialog + q.page['meta'] = ui.meta_card(box='') + # Display a button to launch dialog + q.page['example'] = ui.form_card(box='1 1 2 1', items=[ + ui.button(name='show_dialog', label='Open dialog', primary=True) + ]) + q.client.initialized = True + + # Was the show_dialog button clicked? + if q.args.show_dialog: + # Create a dialog with a close button + q.page['meta'].dialog = ui.dialog( + title='Hello!', + name='my_dialog', + items=[ + ui.text('Click the X button to close this dialog.'), + ], + # Enable a close button (displayed at the top-right of the dialog) + closable=True, + # Get notified when the dialog is dismissed. + events=['dismissed'], + ) + + # Did we get events from the dialog? + if q.events.my_dialog: + # Was the dialog dismissed? + if q.events.my_dialog.dismissed: + # Delete the dialog + q.page['meta'].dialog = None + + await q.page.save() diff --git a/examples/meta_icon.py b/examples/meta_icon.py new file mode 100644 index 0000000000000000000000000000000000000000..b803f056d0f8ad8182a32192ca6379b06bfdaae6 --- /dev/null +++ b/examples/meta_icon.py @@ -0,0 +1,20 @@ +# Meta / Icon +# Set the browser window #icon for a page. #meta +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +page['meta'] = ui.meta_card(box='', icon='https://en.wikipedia.org/static/apple-touch/wikipedia.png') + +# You can also upload and assign an icon, like this: +# icon_path, = site.upload(['path/to/my/icon.png']) +# page['meta'] = ui.meta_card(box='', icon=icon_path) + +page['example'] = ui.markdown_card( + box='1 1 2 2', + title='', + content='Open this page in a new window to view its icon.', +) + +page.save() diff --git a/examples/meta_inline_script.py b/examples/meta_inline_script.py new file mode 100644 index 0000000000000000000000000000000000000000..787f960bbd2897fd3cb9fa57a10a1ede5a778aef --- /dev/null +++ b/examples/meta_inline_script.py @@ -0,0 +1,35 @@ +# Meta / Inline Script +# Execute arbitrary Javascript. +# --- +from h2o_wave import site, ui + +# This example displays a clock using Javascript. + +page = site['/demo'] + +# Add a placeholder for the clock. +page['example'] = ui.markup_card( + box='1 1 2 1', + title='Time', + content='
', +) + +# Specify the Javascript code to display the clock. +clock_script = ''' +// Locate the placeholder 'div' element in our markup_card. +const clock = document.getElementById("clock"); +const displayTime = () => { clock.innerText = (new Date()).toLocaleString(); }; + +// Display the time every second (1000ms). +window.setInterval(displayTime, 1000); +''' + +# Add the script to the page. +page['meta'] = ui.meta_card(box='', script=ui.inline_script( + # The Javascript code for this script. + content=clock_script, + # Execute this script only if the 'clock' element is available. + targets=['clock'], +)) + +page.save() diff --git a/examples/meta_inline_script_callback.py b/examples/meta_inline_script_callback.py new file mode 100644 index 0000000000000000000000000000000000000000..3a32aa284097c27a6c1af61b46f55830f1c3ad31 --- /dev/null +++ b/examples/meta_inline_script_callback.py @@ -0,0 +1,78 @@ +# Meta / Inline Script / Callback +# Handle events from arbitrary Javascript +# --- +from h2o_wave import main, app, Q, ui + +# Define a function that emits an event from Javascript to Python +counter_onclick = ''' +function increment() { + // Emit an event to the app. + // All three arguments are arbitrary. + // Here, we use: + // - 'counter' to indicate the source of the event. + // - 'clicked' to indicate the type of event. + // - the third argument can be a string, number, boolean or any complex structure, like { foo: 'bar', qux: 42 } + // In Python, q.events.counter.clicked will be set to True. + wave.emit('counter', 'clicked', true); +} +''' + +# The HTML and CSS to create a custom button. +# Note that we've named the element 'counter', +# and called the increment() Javascript function when clicked. +counter_html = ''' + +

Click Me!

+''' + + +@app('/demo') +async def serve(q: Q): + # Track how many times the button has been clicked. + if q.client.count is None: + q.client.count = 0 + + if not q.client.initialized: + # Add our script to the page. + q.page['meta'] = ui.meta_card( + box='', + script=ui.inline_script( + # The Javascript code for this script. + content=counter_onclick, + # Execute this script only if the 'counter' element is available. + targets=['counter'], + ) + ) + q.page['form'] = ui.form_card( + box='1 1 2 2', + title='Counter', + items=[ + # Display our custom button. + ui.markup(content=counter_html), + ui.text(name='text', content=''), + ], + ) + q.client.initialized = True + else: + # Do we have an event from the counter? + if q.events.counter: + # Is 'clicked' True? + if q.events.counter.clicked: + # Increment the count. + q.client.count += 1 + # Display the latest count. + q.page['form'].text.content = f'You clicked {q.client.count} times.' + + await q.page.save() diff --git a/examples/meta_inline_stylesheet.py b/examples/meta_inline_stylesheet.py new file mode 100644 index 0000000000000000000000000000000000000000..b400be38721871d309a6cc6ba56b6707da1e5b52 --- /dev/null +++ b/examples/meta_inline_stylesheet.py @@ -0,0 +1,25 @@ +# Meta / Inline Stylesheet +# Use inline CSS to style a Wave app in case of quirks. Prefer using native Wave components if possible. +# --- + +from h2o_wave import site, ui + +page = site['/demo'] + +style = ''' +p { + color: red; +} +''' + +# Add a placeholder. +page['example'] = ui.markdown_card( + box='1 1 2 2', + title='Try out the styling', + content='I should be red!', +) + +# Add the style to the page. +page['meta'] = ui.meta_card(box='', stylesheet=ui.inline_stylesheet(style)) + +page.save() diff --git a/examples/meta_notification.py b/examples/meta_notification.py new file mode 100644 index 0000000000000000000000000000000000000000..f0c43a8a94c49d01df2a9b44200ed93a61296cd7 --- /dev/null +++ b/examples/meta_notification.py @@ -0,0 +1,22 @@ +# Meta / Notification +# Display a desktop #notification. #meta +# --- +import time + +from h2o_wave import site, ui + +page = site['/demo'] + +page['meta'] = ui.meta_card(box='') + +page['example'] = ui.markdown_card( + box='1 1 2 2', + title='Desktop Notifications', + content='This page should display a desktop notification in a few seconds. Wait for it...', +) +page.save() + +time.sleep(5) +page['meta'].notification = 'And now for something completely different!' + +page.save() diff --git a/examples/meta_notification_bar.py b/examples/meta_notification_bar.py new file mode 100644 index 0000000000000000000000000000000000000000..a9df0fc6969eaf3847d561e6c31ca4f04f055bbb --- /dev/null +++ b/examples/meta_notification_bar.py @@ -0,0 +1,61 @@ +# Meta / Notification bar +# Display a notification bar #notification_bar. #meta +# Use this kind of notification when an immediate user feedback is needed. For cases when +# the feedback is not immediate (long-running jobs), use ui.notification as it will +# be visible even when the user is not currently focusing browser tab with your Wave app. +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['form'] = ui.form_card(box='1 1 2 4', items=[ + ui.button(name='top_right', label='Success top-right'), + ui.button(name='top_center', label='Error top-center'), + ui.button(name='top_left', label='Warning top-left'), + ui.button(name='bottom_right', label='Info bottom-right'), + ui.button(name='bottom_center', label='Info bottom-center'), + ui.button(name='bottom_left', label='Info bottom-left'), + ]) + q.client.initialized = True + if q.args.top_right: + q.page['meta'] = ui.meta_card(box='', notification_bar=ui.notification_bar( + text='Success notification', + type='success', + position='top-right', + buttons=[ui.button(name='btn1', label='Button 1', link=True)] + )) + if q.args.top_center: + q.page['meta'] = ui.meta_card(box='', notification_bar=ui.notification_bar( + text='Error notification message that should hopefully span at least two lines.', + type='error', + position='top-center', + buttons=[ + ui.button(name='btn1', label='Button 1'), + ui.button(name='btn2', label='Button 2') + ] + )) + if q.args.top_left: + q.page['meta'] = ui.meta_card(box='', notification_bar=ui.notification_bar( + text='Warning notification', + type='warning', + position='top-left', + )) + if q.args.bottom_right: + q.page['meta'] = ui.meta_card(box='', notification_bar=ui.notification_bar( + text='Info notification', + type='info', + position='bottom-right', + )) + if q.args.bottom_center: + q.page['meta'] = ui.meta_card(box='', notification_bar=ui.notification_bar( + text='Info notification', + position='bottom-center', + )) + if q.args.bottom_left: + q.page['meta'] = ui.meta_card(box='', notification_bar=ui.notification_bar( + text='Default notification', + position='bottom-left', + )) + await q.page.save() diff --git a/examples/meta_notification_bar_closable.py b/examples/meta_notification_bar_closable.py new file mode 100644 index 0000000000000000000000000000000000000000..18af83faecdcd4516d2fb9888504fdcabe889525 --- /dev/null +++ b/examples/meta_notification_bar_closable.py @@ -0,0 +1,35 @@ +# Meta / Notification bar / Closable +# Display a #notification_bar and detect when it's closed. #meta +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + # Create an empty meta_card to hold the notification bar + q.page['meta'] = ui.meta_card(box='') + # Display a button to show the notification bar + q.page['form'] = ui.form_card(box='1 1 2 4', items=[ + ui.button(name='show_notification_bar', label='Show notification bar'), + ]) + q.client.initialized = True + + # Was the show_notification_bar button clicked? + if q.args.show_notification_bar: + # Create a notification bar + q.page['meta'].notification_bar=ui.notification_bar( + text='You can close me!', + name="my_bar", + # Get notified when the notification bar is dismissed. + events=['dismissed'], + ) + + # Did we get events from the notification bar? + if q.events.my_bar: + # Was the notification bar dismissed? + if q.events.my_bar.dismissed: + # Delete the notification bar + q.page['meta'].notification_bar = None + + await q.page.save() diff --git a/examples/meta_redirect.py b/examples/meta_redirect.py new file mode 100644 index 0000000000000000000000000000000000000000..fffe184b1a6b9229492231b975dd2dacc09c6a70 --- /dev/null +++ b/examples/meta_redirect.py @@ -0,0 +1,27 @@ +# Meta / Redirect +# #Redirect the page to a new URL or hash. #meta +# --- +import time + +from h2o_wave import site, ui + +page = site['/demo'] + +page['meta'] = ui.meta_card(box='') + +page['example'] = ui.markdown_card( + box='1 1 2 2', + title='Redirect a page', + content='This page should redirect to Wikipedia in a few seconds. Wait for it...', +) +page.save() + +time.sleep(3) +# Redirect to a hash. +page['meta'].redirect = '#widgets' +page.save() + +time.sleep(3) +# Redirect to a URL. +page['meta'].redirect = 'https://www.wikipedia.org' +page.save() diff --git a/examples/meta_refresh.py b/examples/meta_refresh.py new file mode 100644 index 0000000000000000000000000000000000000000..e2e77503a5726a24ec131688df08e8a3ea19aacc --- /dev/null +++ b/examples/meta_refresh.py @@ -0,0 +1,18 @@ +# Meta / Refresh +# Turn off live updates for static pages to conserve server resources. +# #meta #refresh +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +# Set refresh rate to zero ("no updates") +page['meta'] = ui.meta_card(box='', refresh=0) + +page['example'] = ui.markdown_card( + box='1 1 2 2', + title='No updates for you', + content='This page stops receiving updates once loaded.', +) + +page.save() diff --git a/examples/meta_script.py b/examples/meta_script.py new file mode 100644 index 0000000000000000000000000000000000000000..cde010d4925764da0d76ae5085d9396b059c4606 --- /dev/null +++ b/examples/meta_script.py @@ -0,0 +1,72 @@ +# Meta / Script +# Load external Javascript libraries. +# --- + +from h2o_wave import site, ui + +# This example displays animated text using using anime.js (https://animejs.com/). +# Original example by Tobias Ahlin https://tobiasahlin.com/moving-letters/#2 + +page = site['/demo'] + +html = ''' + + +

Moving Letters!

+''' + +script = ''' +// Wrap every letter in a span +var textWrapper = document.querySelector('.anim'); +textWrapper.innerHTML = textWrapper.textContent.replace(/\S/g, "$&"); + +anime.timeline({loop: true}) + .add({ + targets: '.anim .letter', + scale: [4,1], + opacity: [0,1], + translateZ: 0, + easing: "easeOutExpo", + duration: 950, + delay: (el, i) => 70*i + }).add({ + targets: '.anim', + opacity: 0, + duration: 1000, + easing: "easeOutExpo", + delay: 1000 + }); +''' + +# Add a placeholder for the animation. +page['example'] = ui.markup_card( + box='1 1 6 8', + title='Animation', + content=html, +) + +# Add the script to the page. +page['meta'] = ui.meta_card( + box='', + # Load anime.js + scripts=[ui.script(path='https://cdnjs.cloudflare.com/ajax/libs/animejs/2.0.2/anime.min.js')], + script=ui.inline_script( + # The Javascript code for this script. + content=script, + # Execute this script only if the 'anime' library is available. + requires=['anime'], + # Execute this script only if the 'animation' element is available. + targets=['animation'], + )) + +page.save() diff --git a/examples/meta_script_callback.py b/examples/meta_script_callback.py new file mode 100644 index 0000000000000000000000000000000000000000..7f629db304cf3b951fff20e1eda3ad6962ef0991 --- /dev/null +++ b/examples/meta_script_callback.py @@ -0,0 +1,76 @@ +# Meta / Script / Callback +# Handle events from external Javascript libraries. +# --- +import json +import random +import math +from h2o_wave import main, app, Q, ui + +# Create some data for a random graph +node_count = 100 +edge_count = 500 +nodes = [ + dict(id=f'n{i}', label=f'Node {i}', x=random.random(), y=random.random(), size=random.random(), color='#ff0000') + for i in range(node_count)] + +edges = [dict(id=f'e{i}', source=f'n{math.floor(random.random() * node_count)}', + target=f'n{math.floor(random.random() * node_count)}', size=random.random(), color='#666') for i in + range(edge_count)] + +graph_data = dict(nodes=nodes, edges=edges) + +# Serialize graph data to Javascript / JSON. +graph_data_js = f'const graph = {json.dumps(graph_data)};' + +# Define a script that uses Sigma.js to render our graph. +render_graph = graph_data_js + ''' +const s = new sigma({ graph: graph, container: 'graph' }); +s.bind('clickNode', (e) => { + // Emit an event when a node is clicked. + // All three arguments are arbitrary. + // Here, we use: + // - 'graph' to indicate the source of the event. + // - 'node_clicked' to indicate the type of event. + // - the third argument can be a string, number, boolean or any complex structure, like { foo: 'bar', qux: 42 } + // In Python, q.events.graph.node_clicked will be set to the ID of the clicked node. + wave.emit('graph', 'node_clicked', e.data.node.id); +}); +''' + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['meta'] = ui.meta_card( + box='', + # Load Sigma.js + scripts=[ui.script(path='https://cdnjs.cloudflare.com/ajax/libs/sigma.js/1.2.1/sigma.min.js')], + # Call Javascript to render our graph using Sigma.js. + script=ui.inline_script( + content=render_graph, + # Ensure that Sigma.js is available before running our script. + requires=['sigma'], + # Ensure that the 'graph' element is available before running our script. + targets=['graph'] + ) + ) + # Add a placeholder named 'graph' to house our rendered graph. + q.page['vis'] = ui.markup_card( + box='1 1 6 8', + title='Select a node', + content='
' + ) + # Add another card to display which node was selected. + q.page['details'] = ui.markdown_card( + box='1 9 6 1', + title='', + content='The selected node will be displayed here.', + ) + q.client.initialized = True + else: + if q.events.graph: + selected_node = q.events.graph.node_clicked + if selected_node: + q.page['details'].content = f'You clicked on node {selected_node}' + + await q.page.save() diff --git a/examples/meta_side_panel.py b/examples/meta_side_panel.py new file mode 100644 index 0000000000000000000000000000000000000000..7c551f5cdac362d3d14c315fa621bb4b65ec905b --- /dev/null +++ b/examples/meta_side_panel.py @@ -0,0 +1,37 @@ +# Meta / SidePanel +# Display a #sidePanel. #meta +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['meta'] = ui.meta_card(box='') + q.page['example'] = ui.form_card(box='1 1 2 1', items=[ + ui.button(name='show_side_panel', label='Order donuts', primary=True) + ]) + q.client.initialized = True + else: + if q.args.show_side_panel: + q.page['meta'].side_panel = ui.side_panel(title='Welcome to store', items=[ + ui.text('Donuts cost $1.99. Proceed?'), + ui.buttons([ui.button(name='next_step', label='Next', primary=True)]) + ]) + elif q.args.next_step: + q.page['meta'].side_panel.items = [ + ui.text('You will be charged $1.99. Proceed?'), + ui.buttons([ + ui.button(name='cancel', label='Back to safety'), + ui.button(name='submit', label='Place order', primary=True), + ]) + ] + elif q.args.submit: + q.page['example'].items = [ui.message_bar('success', 'Order placed!')] + q.page['meta'].side_panel = None + + elif q.args.cancel: + q.page['example'].items = [ui.message_bar('info', 'Order canceled!')] + q.page['meta'].side_panel = None + + await q.page.save() diff --git a/examples/meta_side_panel_closable.py b/examples/meta_side_panel_closable.py new file mode 100644 index 0000000000000000000000000000000000000000..f653ce1216b9a7144e07700d761b3d922ee00201 --- /dev/null +++ b/examples/meta_side_panel_closable.py @@ -0,0 +1,41 @@ +# Meta / SidePanel / Closable +# Display a #sidePanel having a close button, and detect when it's closed. #meta +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + # Create an empty meta_card to hold the side panel + q.page['meta'] = ui.meta_card(box='') + # Display a button to show the side panel + q.page['example'] = ui.form_card(box='1 1 2 1', items=[ + ui.button(name='show_side_panel', label='Open side panel', primary=True) + ]) + q.client.initialized = True + + # Was the show_side_panel button clicked? + if q.args.show_side_panel: + # Create a side panel with a close button + q.page['meta'].side_panel = ui.side_panel( + title='Hello!', + name="my_side_panel", + items=[ + ui.text('Click the X button to close this side panel.') + ], + # Enable a close button (displayed at the top-right of the side panel) + closable=True, + # Get notified when the side panel is dismissed. + events=['dismissed'], + ) + + # Did we get events from the side panel? + if q.events.my_side_panel: + # Was the side panel dismissed? + if q.events.my_side_panel.dismissed: + # Delete the side panel + q.page['meta'].side_panel = None + + await q.page.save() + \ No newline at end of file diff --git a/examples/meta_stylesheet.py b/examples/meta_stylesheet.py new file mode 100644 index 0000000000000000000000000000000000000000..fd126feeb06cb5bd31c83252707720727ac98d7d --- /dev/null +++ b/examples/meta_stylesheet.py @@ -0,0 +1,22 @@ +# Meta / Stylesheet +# Load external CSS resources if needed. +# --- + +from h2o_wave import site, ui + +page = site['/demo'] + +# Add a placeholder. +page['example'] = ui.markup_card( + box='1 1 2 2', + title='This button should have Bootstrap styles.', + content='', +) + +page['meta'] = ui.meta_card( + box='', + # Load external stylesheet. The `path` can also be the one returned from `q.site.upload` if you want to use your own CSS files. + stylesheets=[ui.stylesheet(path='https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css')] +) + +page.save() diff --git a/examples/meta_theme.py b/examples/meta_theme.py new file mode 100644 index 0000000000000000000000000000000000000000..d04063c9cbeb64b75f17b2a58197b83ba6f50654 --- /dev/null +++ b/examples/meta_theme.py @@ -0,0 +1,35 @@ +# Meta / Theme +# Change the base color theme of the app. +# --- +from h2o_wave import Q, ui, main, app + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['meta'] = ui.meta_card(box='', theme='neon') + q.page['controls'] = ui.form_card(box='1 1 2 8', items=[ + ui.text_xl('Form'), + ui.textbox(name='textbox', label='Textbox'), + ui.toggle(name='toggle', label='Toggle'), + ui.choice_group(name='choice_group', label='Choice group', choices=[ + ui.choice(name=x, label=x) for x in ['Egg', 'Bacon', 'Spam'] + ]), + ui.checklist(name='checklist', label='Checklist', choices=[ + ui.choice(name=x, label=x) for x in ['Egg', 'Bacon', 'Spam'] + ]), + ui.dropdown(name='dropdown', label='Dropdown', choices=[ + ui.choice(name=x, label=x) for x in ['Egg', 'Bacon', 'Spam'] + ]), + ui.slider(name='slider', label='Slider'), + ui.button(name='toggle_theme', label='Toggle Theme', primary=True) + ]) + q.client.theme = 'neon' + q.client.initialized = True + + meta = q.page['meta'] + + if q.args.toggle_theme: + meta.theme = q.client.theme = 'neon' if q.client.theme == 'default' else 'default' + + await q.page.save() diff --git a/examples/meta_title.py b/examples/meta_title.py new file mode 100644 index 0000000000000000000000000000000000000000..12dd97edea08013c85b003a17e3e3536a7dda737 --- /dev/null +++ b/examples/meta_title.py @@ -0,0 +1,16 @@ +# Meta / Title +# Set the browser window title for a page. #meta +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +page['meta'] = ui.meta_card(box='', title='And now for something completely different!') + +page['example'] = ui.markdown_card( + box='1 1 2 2', + title='', + content='Open this page in a new window to view its title.', +) + +page.save() diff --git a/examples/meta_tracking.py b/examples/meta_tracking.py new file mode 100644 index 0000000000000000000000000000000000000000..c374714525e163e36e2e25c4fcb1b2a08d2b1baf --- /dev/null +++ b/examples/meta_tracking.py @@ -0,0 +1,27 @@ +# Meta / Tracking +# Track user interactions on your app's pages. +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + count = q.client.count or 0 + if not q.client.initialized: + + # Set up a tracker for the page using Google Analytics. + # All browser events/activities are logged against the specified property ID. + q.page['meta'] = ui.meta_card('', tracker=ui.tracker(type=ui.TrackerType.GA, id='G-W810CJL5GP')) + # That's all you need to do - set up a tracker. + # The rest of this example does not do anything special related to tracking. + + q.page['example'] = ui.form_card(box='1 1 2 1', items=[ + ui.button(name='increment', label=f'Count={count}') + ]) + q.client.initialized = True + else: + if q.args.increment: + q.client.count = count = count + 1 + q.page['example'].increment.label = f'Count={count}' + + await q.page.save() diff --git a/examples/ml_dai.py b/examples/ml_dai.py new file mode 100644 index 0000000000000000000000000000000000000000..270a1f71fdc35fef523ebb1645728045c7ca33db --- /dev/null +++ b/examples/ml_dai.py @@ -0,0 +1,178 @@ +# WaveML / DAI +# Build Wave Models for training and prediction of classification or regression using Driverless AI. +# --- +import os + +from h2o_wave import main, app, Q, copy_expando, ui +from h2o_wave_ml import build_model, ModelType +from h2o_wave_ml.utils import list_dai_instances + +from sklearn.datasets import load_wine +from sklearn.model_selection import train_test_split + +STEAM_URL = os.environ.get('STEAM_URL') +MLOPS_URL = os.environ.get('MLOPS_URL') + +DATASET_TEXT = '''The sample dataset used is the + wine dataset.''' +STEAM_TEXT = f'''No Driverless AI instances available. You may create one in + AI Engines and refresh the page.''' + + +def dai_experiment_url(instance_id: str, instance_name: str): + # URL link to Driverless AI experiment + return f'''**Driverless AI Experiment:** + {instance_name}''' + + +def mlops_deployment_url(project_id: str): + # URL link to MLOps deployment + return f'**MLOps Deployment:** {project_id}' + + +def form_unsupported(): + # display when app is not running on cloud + return [ + ui.text('''This example requires access to Driverless AI running on + H2O AI Cloud + and does not support standalone app instances.'''), + ui.text('''Sign up at https://h2o.ai/free + to run apps on cloud.''') + ] + + +def form_default(q: Q): + # display when app is initialized + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.text(content=STEAM_TEXT, visible=q.client.disable_training), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True, disabled=q.client.disable_training), + ui.button(name='predict', label='Predict', primary=True, disabled=True), + ]) + ] + + +def form_training_progress(q: Q): + # display when model training is in progress + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True, disabled=True), + ui.button(name='predict', label='Predict', primary=True, disabled=True) + ]), + ui.progress(label='Training in progress...', caption='This can take a few minutes...'), + ui.text(content=q.client.model_details) + ] + + +def form_training_completed(q: Q): + # display when model training is completed + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True), + ui.button(name='predict', label='Predict', primary=True) + ]), + ui.message_bar(type='success', text='Training successfully completed!'), + ui.text(content=q.client.model_details) + ] + + +def form_prediction_completed(q: Q): + # display when model prediction is completed + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True), + ui.button(name='predict', label='Predict', primary=True) + ]), + ui.message_bar(type='success', text='Prediction successfully completed!'), + ui.text(content=q.client.model_details), + ui.text(content=f'''**Example predictions:**
+ {q.client.preds[0]}
{q.client.preds[1]}
{q.client.preds[2]}''') + ] + + +@app('/demo') +async def serve(q: Q): + if 'H2O_CLOUD_ENVIRONMENT' not in os.environ: + # show appropriate message if app is not running on cloud + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=form_unsupported() + ) + elif q.args.train: + # get DAI instance name + copy_expando(q.args, q.client) + + for dai_instance in q.client.dai_instances: + if dai_instance['id'] == int(q.client.dai_instance_id): + q.client.dai_instance_name = dai_instance['name'] + + # set DAI model details + q.client.model_details = dai_experiment_url(q.client.dai_instance_id, q.client.dai_instance_name) + + # show training progress and details + q.page['example'].items = form_training_progress(q) + await q.page.save() + + # train WaveML Model using Driverless AI + q.client.wave_model = await q.run( + func=build_model, + train_df=q.client.train_df, + target_column='target', + model_type=ModelType.DAI, + refresh_token=q.auth.refresh_token, + _steam_dai_instance_name=q.client.dai_instance_name, + _dai_accuracy=1, + _dai_time=1, + _dai_interpretability=10 + ) + + # update DAI model details + q.client.project_id = q.client.wave_model.project_id + q.client.model_details += f'
{mlops_deployment_url(q.client.project_id)}' + + # show prediction option + q.page['example'].items = form_training_completed(q) + elif q.args.predict: + # predict on test data + q.client.preds = q.client.wave_model.predict(test_df=q.client.test_df) + + # show predictions + q.page['example'].items = form_prediction_completed(q) + else: + # prepare sample train and test dataframes + data = load_wine(as_frame=True)['frame'] + q.client.train_df, q.client.test_df = train_test_split(data, train_size=0.8) + + # DAI instances + q.client.dai_instances = list_dai_instances(refresh_token=q.auth.refresh_token) + q.client.choices_dai_instances = [ + ui.choice( + name=str(x['id']), + label=f'{x["name"]} ({x["status"].capitalize()})', + disabled=x['status'] != 'running' + ) for x in q.client.dai_instances + ] + + running_dai_instances = [x['id'] for x in q.client.dai_instances if x['status'] == 'running'] + q.client.disable_training = False if running_dai_instances else True + q.client.dai_instance_id = str(running_dai_instances[0]) if running_dai_instances else '' + + # display ui + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=form_default(q) + ) + + await q.page.save() diff --git a/examples/ml_dai_autodoc.py b/examples/ml_dai_autodoc.py new file mode 100644 index 0000000000000000000000000000000000000000..24e5129b695f5eafb227af12d5b9dd92f7d565a9 --- /dev/null +++ b/examples/ml_dai_autodoc.py @@ -0,0 +1,156 @@ +# WaveML / DAI / AutoDoc +# Download AutoDoc for Wave Models built using Driverless AI. +# --- +import os + +from h2o_wave import main, app, Q, copy_expando, ui +from h2o_wave_ml import build_model, ModelType +from h2o_wave_ml.utils import list_dai_instances, save_autodoc + +from sklearn.datasets import load_wine +from sklearn.model_selection import train_test_split + +STEAM_URL = os.environ.get('STEAM_URL') +MLOPS_URL = os.environ.get('MLOPS_URL') + +DATASET_TEXT = '''The sample dataset used is the + wine dataset.''' +STEAM_TEXT = f'''No Driverless AI instances available. You may create one in + AI Engines and refresh the page.''' + + +def dai_experiment_url(instance_id: str, instance_name: str): + # URL link to Driverless AI experiment + return f'''**Driverless AI Experiment:** + {instance_name}''' + + +def mlops_deployment_url(project_id: str): + # URL link to MLOps deployment + return f'**MLOps Deployment:** {project_id}' + + +def form_unsupported(): + # display when app is not running on cloud + return [ + ui.text('''This example requires access to Driverless AI running on + H2O AI Cloud + and does not support standalone app instances.'''), + ui.text('''Sign up at https://h2o.ai/free + to run apps on cloud.''') + ] + + +def form_default(q: Q): + # display when app is initialized + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.text(content=STEAM_TEXT, visible=q.client.disable_training), + ui.button(name='train', label='Train', primary=True, disabled=q.client.disable_training) + ] + + +def form_training_progress(q: Q): + # display when model training is in progress + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.button(name='train', label='Train', primary=True, disabled=q.client.disable_training), + ui.progress(label='Training in progress...', caption='This can take a few minutes...'), + ui.text(content=q.client.model_details) + ] + + +def form_training_completed(q: Q): + # display when model training is completed + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.button(name='train', label='Train', primary=True, disabled=q.client.disable_training), + ui.message_bar(type='success', text='Training successfully completed!'), + ui.text(content=q.client.model_details), + ui.text(content=f'**Download:** AutoDoc') + ] + + +@app('/demo') +async def serve(q: Q): + if 'H2O_CLOUD_ENVIRONMENT' not in os.environ: + # show appropriate message if app is not running on cloud + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=form_unsupported() + ) + elif q.args.train: + # get DAI instance name + copy_expando(q.args, q.client) + + for dai_instance in q.client.dai_instances: + if dai_instance['id'] == int(q.client.dai_instance_id): + q.client.dai_instance_name = dai_instance['name'] + + # set DAI model details + q.client.model_details = dai_experiment_url(q.client.dai_instance_id, q.client.dai_instance_name) + + # show training progress and details + q.page['example'].items = form_training_progress(q) + await q.page.save() + + # train WaveML Model using Driverless AI + q.client.wave_model = await q.run( + func=build_model, + train_df=q.client.train_df, + target_column='target', + model_type=ModelType.DAI, + refresh_token=q.auth.refresh_token, + _steam_dai_instance_name=q.client.dai_instance_name, + _dai_accuracy=1, + _dai_time=1, + _dai_interpretability=10 + ) + + # update DAI model details + q.client.project_id = q.client.wave_model.project_id + q.client.model_details += f'
{mlops_deployment_url(q.client.project_id)}' + + # download AutoDoc + path_autodoc = save_autodoc( + project_id=q.client.project_id, + output_dir_path='.', + refresh_token=q.auth.refresh_token + ) + + q.client.path_autodoc, *_ = await q.site.upload([path_autodoc]) + + # show model outputs + q.page['example'].items = form_training_completed(q) + else: + # prepare sample train and test dataframes + data = load_wine(as_frame=True)['frame'] + q.client.train_df, q.client.test_df = train_test_split(data, train_size=0.8) + + # DAI instances + q.client.dai_instances = list_dai_instances(refresh_token=q.auth.refresh_token) + q.client.choices_dai_instances = [ + ui.choice( + name=str(x['id']), + label=f'{x["name"]} ({x["status"].capitalize()})', + disabled=x['status'] != 'running' + ) for x in q.client.dai_instances + ] + + running_dai_instances = [x['id'] for x in q.client.dai_instances if x['status'] == 'running'] + q.client.disable_training = False if running_dai_instances else True + q.client.dai_instance_id = str(running_dai_instances[0]) if running_dai_instances else '' + + # display ui + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=form_default(q) + ) + + await q.page.save() diff --git a/examples/ml_dai_categorical.py b/examples/ml_dai_categorical.py new file mode 100644 index 0000000000000000000000000000000000000000..a674a18da27f84c13f58184902123d23aeb9cec0 --- /dev/null +++ b/examples/ml_dai_categorical.py @@ -0,0 +1,190 @@ +# WaveML / DAI / Categorical +# Configure categorical columns for Wave Models built using Driverless AI. +# --- +import os + +from h2o_wave import main, app, Q, copy_expando, ui +from h2o_wave_ml import build_model, ModelType +from h2o_wave_ml.utils import list_dai_instances + +from sklearn.datasets import load_wine +from sklearn.model_selection import train_test_split + +STEAM_URL = os.environ.get('STEAM_URL') +MLOPS_URL = os.environ.get('MLOPS_URL') + +DATASET_TEXT = '''The sample dataset used is the + wine dataset.''' +STEAM_TEXT = f'''No Driverless AI instances available. You may create one in + AI Engines and refresh the page.''' + + +def dai_experiment_url(instance_id: str, instance_name: str): + # URL link to Driverless AI experiment + return f'''**Driverless AI Experiment:** + {instance_name}''' + + +def mlops_deployment_url(project_id: str): + # URL link to MLOps deployment + return f'**MLOps Deployment:** {project_id}' + + +def form_unsupported(): + # display when app is not running on cloud + return [ + ui.text('''This example requires access to Driverless AI running on + H2O AI Cloud + and does not support standalone app instances.'''), + ui.text('''Sign up at https://h2o.ai/free + to run apps on cloud.''') + ] + + +def form_default(q: Q): + # display when app is initialized + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.text(content=STEAM_TEXT, visible=q.client.disable_training), + ui.dropdown(name='categorical_columns', label='Select categorical columns', + choices=q.client.column_choices, values=[]), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True, disabled=q.client.disable_training), + ui.button(name='predict', label='Predict', primary=True, disabled=True), + ]) + ] + + +def form_training_progress(q: Q): + # display when model training is in progress + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.dropdown(name='categorical_columns', label='Select categorical columns', + choices=q.client.column_choices, values=q.client.categorical_columns), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True, disabled=True), + ui.button(name='predict', label='Predict', primary=True, disabled=True) + ]), + ui.progress(label='Training in progress...', caption='This can take a few minutes...'), + ui.text(content=q.client.model_details) + ] + + +def form_training_completed(q: Q): + # display when model training is completed + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.dropdown(name='categorical_columns', label='Select categorical columns', + choices=q.client.column_choices, values=q.client.categorical_columns), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True), + ui.button(name='predict', label='Predict', primary=True) + ]), + ui.message_bar(type='success', text='Training successfully completed!'), + ui.text(content=q.client.model_details) + ] + + +def form_prediction_completed(q: Q): + # display when model prediction is completed + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.dropdown(name='categorical_columns', label='Select categorical columns', + choices=q.client.column_choices, values=q.client.categorical_columns), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True), + ui.button(name='predict', label='Predict', primary=True) + ]), + ui.message_bar(type='success', text='Prediction successfully completed!'), + ui.text(content=q.client.model_details), + ui.text(content=f'''**Example predictions:**
+ {q.client.preds[0]}
{q.client.preds[1]}
{q.client.preds[2]}''') + ] + + +@app('/demo') +async def serve(q: Q): + if 'H2O_CLOUD_ENVIRONMENT' not in os.environ: + # show appropriate message if app is not running on cloud + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=form_unsupported() + ) + elif q.args.train: + # get DAI instance name + copy_expando(q.args, q.client) + + for dai_instance in q.client.dai_instances: + if dai_instance['id'] == int(q.client.dai_instance_id): + q.client.dai_instance_name = dai_instance['name'] + + # set DAI model details + q.client.model_details = dai_experiment_url(q.client.dai_instance_id, q.client.dai_instance_name) + + # show training progress and details + q.page['example'].items = form_training_progress(q) + await q.page.save() + + # train WaveML Model using Driverless AI + q.client.wave_model = await q.run( + func=build_model, + train_df=q.client.train_df, + target_column='target', + model_type=ModelType.DAI, + categorical_columns=q.client.categorical_columns, + refresh_token=q.auth.refresh_token, + _steam_dai_instance_name=q.client.dai_instance_name, + _dai_accuracy=1, + _dai_time=1, + _dai_interpretability=10 + ) + + # update DAI model details + q.client.project_id = q.client.wave_model.project_id + q.client.model_details += f'
{mlops_deployment_url(q.client.project_id)}' + + # show prediction option + q.page['example'].items = form_training_completed(q) + elif q.args.predict: + # predict on test data + q.client.preds = q.client.wave_model.predict(test_df=q.client.test_df) + + # show predictions + q.page['example'].items = form_prediction_completed(q) + else: + # prepare sample train and test dataframes + data = load_wine(as_frame=True)['frame'] + q.client.train_df, q.client.test_df = train_test_split(data, train_size=0.8) + + # columns + q.client.column_choices = [ui.choice(x, x) for x in q.client.train_df.columns] + + # DAI instances + q.client.dai_instances = list_dai_instances(refresh_token=q.auth.refresh_token) + q.client.choices_dai_instances = [ + ui.choice( + name=str(x['id']), + label=f'{x["name"]} ({x["status"].capitalize()})', + disabled=x['status'] != 'running' + ) for x in q.client.dai_instances + ] + + running_dai_instances = [x['id'] for x in q.client.dai_instances if x['status'] == 'running'] + q.client.disable_training = False if running_dai_instances else True + q.client.dai_instance_id = str(running_dai_instances[0]) if running_dai_instances else '' + + # display ui + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=form_default(q) + ) + + await q.page.save() diff --git a/examples/ml_dai_instances.py b/examples/ml_dai_instances.py new file mode 100644 index 0000000000000000000000000000000000000000..20768824b03691ef0c856ce48812c3e437b6b1cf --- /dev/null +++ b/examples/ml_dai_instances.py @@ -0,0 +1,93 @@ +# WaveML / DAI / Instances +# List the Driverless AI instances of the user on Steam. +# --- +import os + +from h2o_wave import main, app, Q, ui +from h2o_wave_ml.utils import list_dai_instances + +STEAM_URL = os.environ.get('STEAM_URL') +STEAM_TEXT = f'''No Driverless AI instances available. You may create one in + AI Engines and refresh the page.''' + +ICON_MAP = { + 'created': 'Blocked2Solid', + 'starting': 'Blocked2Solid', + 'running': 'CompletedSolid', + 'unreachable': 'AlertSolid', + 'failed': 'AlertSolid', + 'stopping': 'Blocked2Solid', + 'stopped': 'Blocked2Solid', + 'terminating': 'Blocked2Solid', + 'terminated': 'Blocked2Solid' +} + + +def dai_instances_table(dai_instances: list): + # dai instances in ui.table + return ui.table( + name='table_dai', + columns=[ + ui.table_column(name='id', label='Id', min_width='50px', max_width='51px', link=False), + ui.table_column(name='name', label='Name', link=False), + ui.table_column(name='status', label='Status', cell_type=ui.icon_table_cell_type(color='#CDDD38'), + link=False), + ui.table_column(name='description', label='Description', link=False), + ui.table_column(name='version', label='Version', link=False) + ], + rows=[ + ui.table_row(str(i), [ + str(dai_instances[i]['id']), + dai_instances[i]['name'], + ICON_MAP[dai_instances[i]['status']], + dai_instances[i]['status'], + dai_instances[i]['version'] + ]) for i in range(len(dai_instances)) + ] + ) + + +def form_unsupported(): + # display when app is not running on cloud + return [ + ui.text('''This example requires access to Driverless AI running on + H2O AI Cloud + and does not support standalone app instances.'''), + ui.text('''Sign up at https://h2o.ai/free + to run apps on cloud.''') + ] + + +def form_default(q: Q): + # display when app is initialized + return [ + ui.label(label='List of Driverless AI instances'), + dai_instances_table(dai_instances=q.client.dai_instances) + ] + + +@app('/demo') +async def serve(q: Q): + if 'H2O_CLOUD_ENVIRONMENT' not in os.environ: + # show appropriate message if app is not running on cloud + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=form_unsupported() + ) + else: + # DAI instances + q.client.dai_instances = list_dai_instances(refresh_token=q.auth.refresh_token) + + # display ui + if q.client.dai_instances: + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=form_default(q) + ) + else: + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=[ui.text(content=STEAM_TEXT)] + ) + + await q.page.save() diff --git a/examples/ml_dai_parameters.py b/examples/ml_dai_parameters.py new file mode 100644 index 0000000000000000000000000000000000000000..cca9d95c094d39e33e624745840a8a370584de41 --- /dev/null +++ b/examples/ml_dai_parameters.py @@ -0,0 +1,190 @@ +# WaveML / DAI / Parameters +# Configure hyperparameters for Wave Models built using Driverless AI. +# --- +import os + +from h2o_wave import main, app, Q, copy_expando, ui +from h2o_wave_ml import build_model, ModelType +from h2o_wave_ml.utils import list_dai_instances + +from sklearn.datasets import load_wine +from sklearn.model_selection import train_test_split + +STEAM_URL = os.environ.get('STEAM_URL') +MLOPS_URL = os.environ.get('MLOPS_URL') + +DATASET_TEXT = '''The sample dataset used is the + wine dataset.''' +STEAM_TEXT = f'''No Driverless AI instances available. You may create one in + AI Engines and refresh the page.''' + + +def dai_experiment_url(instance_id: str, instance_name: str): + # URL link to Driverless AI experiment + return f'''**Driverless AI Experiment:** + {instance_name}''' + + +def mlops_deployment_url(project_id: str): + # URL link to MLOps deployment + return f'**MLOps Deployment:** {project_id}' + + +def form_unsupported(): + # display when app is not running on cloud + return [ + ui.text('''This example requires access to Driverless AI running on + H2O AI Cloud + and does not support standalone app instances.'''), + ui.text('''Sign up at https://h2o.ai/free + to run apps on cloud.''') + ] + + +def form_default(q: Q): + # display when app is initialized + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.text(content=STEAM_TEXT, visible=q.client.disable_training), + ui.slider(name='dai_interpretability', label='Interpretability', min=1, max=10, step=1, value=7), + ui.toggle(name='dai_reproducible', label='Reproducible', value=False), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True, disabled=q.client.disable_training), + ui.button(name='predict', label='Predict', primary=True, disabled=True), + ]) + ] + + +def form_training_progress(q: Q): + # display when model training is in progress + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.slider(name='dai_interpretability', label='Interpretability', min=1, max=10, step=1, + value=q.client.dai_interpretability), + ui.toggle(name='dai_reproducible', label='Reproducible', value=q.client.dai_reproducible), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True, disabled=True), + ui.button(name='predict', label='Predict', primary=True, disabled=True) + ]), + ui.progress(label='Training in progress...', caption='This can take a few minutes...'), + ui.text(content=q.client.model_details) + ] + + +def form_training_completed(q: Q): + # display when model training is completed + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.slider(name='dai_interpretability', label='Interpretability', min=1, max=10, step=1, + value=q.client.dai_interpretability), + ui.toggle(name='dai_reproducible', label='Reproducible', value=q.client.dai_reproducible), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True), + ui.button(name='predict', label='Predict', primary=True) + ]), + ui.message_bar(type='success', text='Training successfully completed!'), + ui.text(content=q.client.model_details) + ] + + +def form_prediction_completed(q: Q): + # display when model prediction is completed + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.slider(name='dai_interpretability', label='Interpretability', min=1, max=10, step=1, + value=q.client.dai_interpretability), + ui.toggle(name='dai_reproducible', label='Reproducible', value=q.client.dai_reproducible), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True), + ui.button(name='predict', label='Predict', primary=True) + ]), + ui.message_bar(type='success', text='Prediction successfully completed!'), + ui.text(content=q.client.model_details), + ui.text(content=f'''**Example predictions:**
+ {q.client.preds[0]}
{q.client.preds[1]}
{q.client.preds[2]}''') + ] + + +@app('/demo') +async def serve(q: Q): + if 'H2O_CLOUD_ENVIRONMENT' not in os.environ: + # show appropriate message if app is not running on cloud + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=form_unsupported() + ) + elif q.args.train: + # get DAI instance name + copy_expando(q.args, q.client) + + for dai_instance in q.client.dai_instances: + if dai_instance['id'] == int(q.client.dai_instance_id): + q.client.dai_instance_name = dai_instance['name'] + + # set DAI model details + q.client.model_details = dai_experiment_url(q.client.dai_instance_id, q.client.dai_instance_name) + + # show training progress and details + q.page['example'].items = form_training_progress(q) + await q.page.save() + + # train WaveML Model using Driverless AI + q.client.wave_model = await q.run( + func=build_model, + train_df=q.client.train_df, + target_column='target', + model_type=ModelType.DAI, + refresh_token=q.auth.refresh_token, + _steam_dai_instance_name=q.client.dai_instance_name, + _dai_accuracy=1, + _dai_time=1, + _dai_interpretability=q.client.dai_interpretability, + _dai_reproducible=q.client.dai_reproducible + ) + + # update DAI model details + q.client.project_id = q.client.wave_model.project_id + q.client.model_details += f'
{mlops_deployment_url(q.client.project_id)}' + + # show prediction option + q.page['example'].items = form_training_completed(q) + elif q.args.predict: + # predict on test data + q.client.preds = q.client.wave_model.predict(test_df=q.client.test_df) + + # show predictions + q.page['example'].items = form_prediction_completed(q) + else: + # prepare sample train and test dataframes + data = load_wine(as_frame=True)['frame'] + q.client.train_df, q.client.test_df = train_test_split(data, train_size=0.8) + + # DAI instances + q.client.dai_instances = list_dai_instances(refresh_token=q.auth.refresh_token) + q.client.choices_dai_instances = [ + ui.choice( + name=str(x['id']), + label=f'{x["name"]} ({x["status"].capitalize()})', + disabled=x['status'] != 'running' + ) for x in q.client.dai_instances + ] + + running_dai_instances = [x['id'] for x in q.client.dai_instances if x['status'] == 'running'] + q.client.disable_training = False if running_dai_instances else True + q.client.dai_instance_id = str(running_dai_instances[0]) if running_dai_instances else '' + + # display ui + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=form_default(q) + ) + + await q.page.save() diff --git a/examples/ml_dai_save.py b/examples/ml_dai_save.py new file mode 100644 index 0000000000000000000000000000000000000000..1ce3c26050ce67518566fe85d6f5b0dad7cb6567 --- /dev/null +++ b/examples/ml_dai_save.py @@ -0,0 +1,182 @@ +# WaveML / DAI / Save +# Save and load Wave Models built using Driverless AI. +# --- +import os + +from h2o_wave import main, app, Q, copy_expando, ui +from h2o_wave_ml import build_model, get_model, ModelType +from h2o_wave_ml.utils import list_dai_instances + +from sklearn.datasets import load_wine +from sklearn.model_selection import train_test_split + +STEAM_URL = os.environ.get('STEAM_URL') +MLOPS_URL = os.environ.get('MLOPS_URL') + +DATASET_TEXT = '''The sample dataset used is the + wine dataset.''' +STEAM_TEXT = f'''No Driverless AI instances available. You may create one in + AI Engines and refresh the page.''' + + +def dai_experiment_url(instance_id: str, instance_name: str): + # URL link to Driverless AI experiment + return f'''**Driverless AI Experiment:** + {instance_name}''' + + +def mlops_deployment_url(project_id: str): + # URL link to MLOps deployment + return f'**MLOps Deployment:** {project_id}' + + +def form_unsupported(): + # display when app is not running on cloud + return [ + ui.text('''This example requires access to Driverless AI running on + H2O AI Cloud + and does not support standalone app instances.'''), + ui.text('''Sign up at https://h2o.ai/free + to run apps on cloud.''') + ] + + +def form_default(q: Q): + # display when app is initialized + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.text(content=STEAM_TEXT, visible=q.client.disable_training), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True, disabled=q.client.disable_training), + ui.button(name='predict', label='Predict', primary=True, disabled=True), + ]) + ] + + +def form_training_progress(q: Q): + # display when model training is in progress + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True, disabled=True), + ui.button(name='predict', label='Predict', primary=True, disabled=True) + ]), + ui.progress(label='Training in progress...', caption='This can take a few minutes...'), + ui.text(content=q.client.model_details) + ] + + +def form_training_completed(q: Q): + # display when model training is completed + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True), + ui.button(name='predict', label='Predict', primary=True) + ]), + ui.message_bar(type='success', text='Training successfully completed!'), + ui.text(content=q.client.model_details) + ] + + +def form_prediction_completed(q: Q): + # display when model prediction is completed + return [ + ui.text(content=DATASET_TEXT), + ui.dropdown(name='dai_instance_id', label='Select Driverless AI instance', value=q.client.dai_instance_id, + choices=q.client.choices_dai_instances, required=True), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True), + ui.button(name='predict', label='Predict', primary=True) + ]), + ui.message_bar(type='success', text='Prediction successfully completed!'), + ui.text(content=q.client.model_details), + ui.text(content=f'''**Example predictions:**
+ {q.client.preds[0]}
{q.client.preds[1]}
{q.client.preds[2]}''') + ] + + +@app('/demo') +async def serve(q: Q): + if 'H2O_CLOUD_ENVIRONMENT' not in os.environ: + # show appropriate message if app is not running on cloud + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=form_unsupported() + ) + elif q.args.train: + # get DAI instance name + copy_expando(q.args, q.client) + + for dai_instance in q.client.dai_instances: + if dai_instance['id'] == int(q.client.dai_instance_id): + q.client.dai_instance_name = dai_instance['name'] + + # set DAI model details + q.client.model_details = dai_experiment_url(q.client.dai_instance_id, q.client.dai_instance_name) + + # show training progress and details + q.page['example'].items = form_training_progress(q) + await q.page.save() + + # train WaveML Model using Driverless AI + wave_model = await q.run( + func=build_model, + train_df=q.client.train_df, + target_column='target', + model_type=ModelType.DAI, + refresh_token=q.auth.refresh_token, + _steam_dai_instance_name=q.client.dai_instance_name, + _dai_accuracy=1, + _dai_time=1, + _dai_interpretability=10 + ) + + # update and save DAI model details + q.client.project_id = wave_model.project_id + q.client.endpoint_url = wave_model.endpoint_url + q.client.model_details += f'
{mlops_deployment_url(q.client.project_id)}' + + # show prediction option + q.page['example'].items = form_training_completed(q) + elif q.args.predict: + # load model from it's endpoint URL + wave_model = get_model(endpoint_url=q.client.endpoint_url, refresh_token=q.auth.refresh_token) + + # predict on test data + q.client.preds = wave_model.predict(test_df=q.client.test_df) + + # show predictions + q.page['example'].items = form_prediction_completed(q) + else: + # prepare sample train and test dataframes + data = load_wine(as_frame=True)['frame'] + q.client.train_df, q.client.test_df = train_test_split(data, train_size=0.8) + + # DAI instances + q.client.dai_instances = list_dai_instances(refresh_token=q.auth.refresh_token) + q.client.choices_dai_instances = [ + ui.choice( + name=str(x['id']), + label=f'{x["name"]} ({x["status"].capitalize()})', + disabled=x['status'] != 'running' + ) for x in q.client.dai_instances + ] + + running_dai_instances = [x['id'] for x in q.client.dai_instances if x['status'] == 'running'] + q.client.disable_training = False if running_dai_instances else True + q.client.dai_instance_id = str(running_dai_instances[0]) if running_dai_instances else '' + + # display ui + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=form_default(q) + ) + + await q.page.save() diff --git a/examples/ml_h2o.py b/examples/ml_h2o.py new file mode 100644 index 0000000000000000000000000000000000000000..0ff83209e104c91ff9d7a6126bc1a46071f0c706 --- /dev/null +++ b/examples/ml_h2o.py @@ -0,0 +1,61 @@ +# WaveML / H2O-3 +# Build Wave Models for training and prediction of classification or regression using H2O-3 AutoML. +# --- +from h2o_wave import main, app, Q, ui +from h2o_wave_ml import build_model, ModelType + +from sklearn.datasets import load_wine +from sklearn.model_selection import train_test_split + + +@app('/demo') +async def serve(q: Q): + if q.args.train: + # train WaveML Model using H2O-3 AutoML + q.client.wave_model = build_model( + train_df=q.client.train_df, + target_column='target', + model_type=ModelType.H2O3, + _h2o3_max_runtime_secs=5, + _h2o3_nfolds=2 + ) + model_id = q.client.wave_model.model.model_id + accuracy = round(100 - q.client.wave_model.model.mean_per_class_error() * 100, 2) + + # show training details and prediction option + q.page['example'].predict.disabled = False + q.page['example'].message.type = 'success' + q.page['example'].message.text = 'Training successfully completed!' + q.page['example'].model_id.content = f'''**H2O AutoML model id:** {model_id}
+ **Accuracy:** {accuracy}%''' + q.page['example'].example_predictions.content = '' + elif q.args.predict: + # predict on test data + preds = q.client.wave_model.predict(test_df=q.client.test_df) + + # show predictions + q.page['example'].message.text = 'Prediction successfully completed!' + q.page['example'].example_predictions.content = f'''**Example predictions:**
+ {preds[0]}
{preds[1]}
{preds[2]}''' + else: + # prepare sample train and test dataframes + data = load_wine(as_frame=True)['frame'] + q.client.train_df, q.client.test_df = train_test_split(data, train_size=0.8) + + # display ui + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=[ + ui.text(content='''The sample dataset used is the + wine dataset.'''), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True), + ui.button(name='predict', label='Predict', primary=True, disabled=True), + ]), + ui.message_bar(name='message', type='warning', text='Training will take a few seconds'), + ui.text(name='model_id', content=''), + ui.text(name='example_predictions', content='') + ] + ) + + await q.page.save() diff --git a/examples/ml_h2o_algo.py b/examples/ml_h2o_algo.py new file mode 100644 index 0000000000000000000000000000000000000000..fbc75f3f9879650f7196100c1c1246d92fcae3ed --- /dev/null +++ b/examples/ml_h2o_algo.py @@ -0,0 +1,68 @@ +# WaveML / H2O-3 / Algo +# Configure a specific algo for Wave Models built using H2O-3 AutoML. +# --- +from h2o_wave import main, app, Q, ui, copy_expando +from h2o_wave_ml import build_model, ModelType + +from sklearn.datasets import load_wine +from sklearn.model_selection import train_test_split + + +@app('/demo') +async def serve(q: Q): + if q.args.train: + # train WaveML Model using H2O-3 AutoML + copy_expando(q.args, q.client) + q.client.wave_model = build_model( + train_df=q.client.train_df, + target_column='target', + model_type=ModelType.H2O3, + _h2o3_max_runtime_secs=30, + _h2o3_nfolds=2, + _h2o3_include_algos=[q.client.algo] + ) + model_id = q.client.wave_model.model.model_id + accuracy = round(100 - q.client.wave_model.model.mean_per_class_error() * 100, 2) + + # show training details and prediction option + q.page['example'].algo.value = q.client.algo + q.page['example'].predict.disabled = False + q.page['example'].message.type = 'success' + q.page['example'].message.text = 'Training successfully completed!' + q.page['example'].model_id.content = f'''**H2O AutoML model id:** {model_id}
+ **Accuracy:** {accuracy}%''' + q.page['example'].example_predictions.content = '' + elif q.args.predict: + # predict on test data + preds = q.client.wave_model.predict(test_df=q.client.test_df) + + # show predictions + q.page['example'].message.text = 'Prediction successfully completed!' + q.page['example'].example_predictions.content = f'''**Example predictions:**
+ {preds[0]}
{preds[1]}
{preds[2]}''' + else: + # prepare sample train and test dataframes + data = load_wine(as_frame=True)['frame'] + q.client.train_df, q.client.test_df = train_test_split(data, train_size=0.8) + + # algos + algo_choices = [ui.choice(x, x) for x in ['DRF', 'GLM', 'XGBoost', 'GBM', 'DeepLearning']] + + # display ui + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=[ + ui.text(content='''The sample dataset used is the + wine dataset.'''), + ui.choice_group(name='algo', label='Select Algo', choices=algo_choices, value='DRF'), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True), + ui.button(name='predict', label='Predict', primary=True, disabled=True), + ]), + ui.message_bar(name='message', type='warning', text='Training will take a few seconds'), + ui.text(name='model_id', content=''), + ui.text(name='example_predictions', content='') + ] + ) + + await q.page.save() diff --git a/examples/ml_h2o_categorical.py b/examples/ml_h2o_categorical.py new file mode 100644 index 0000000000000000000000000000000000000000..dc4de911b54019292c0e84971e6a96ffe9ce4559 --- /dev/null +++ b/examples/ml_h2o_categorical.py @@ -0,0 +1,69 @@ +# WaveML / H2O-3 / Categorical +# Configure categorical columns for Wave Models built using H2O-3 AutoML. +# --- +from h2o_wave import main, app, Q, ui, copy_expando +from h2o_wave_ml import build_model, ModelType + +from sklearn.datasets import load_wine +from sklearn.model_selection import train_test_split + + +@app('/demo') +async def serve(q: Q): + if q.args.train: + # train WaveML Model using H2O-3 AutoML + copy_expando(q.args, q.client) + q.client.wave_model = build_model( + train_df=q.client.train_df, + target_column='target', + model_type=ModelType.H2O3, + categorical_columns=q.client.categorical_columns, + _h2o3_max_runtime_secs=5, + _h2o3_nfolds=2 + ) + model_id = q.client.wave_model.model.model_id + accuracy = round(100 - q.client.wave_model.model.mean_per_class_error() * 100, 2) + + # show training details and prediction option + q.page['example'].categorical_columns.values = q.client.categorical_columns + q.page['example'].predict.disabled = False + q.page['example'].message.type = 'success' + q.page['example'].message.text = 'Training successfully completed!' + q.page['example'].model_id.content = f'''**H2O AutoML model id:** {model_id}
+ **Accuracy:** {accuracy}%''' + q.page['example'].example_predictions.content = '' + elif q.args.predict: + # predict on test data + preds = q.client.wave_model.predict(test_df=q.client.test_df) + + # show predictions + q.page['example'].message.text = 'Prediction successfully completed!' + q.page['example'].example_predictions.content = f'''**Example predictions:**
+ {preds[0]}
{preds[1]}
{preds[2]}''' + else: + # prepare sample train and test dataframes + data = load_wine(as_frame=True)['frame'] + q.client.train_df, q.client.test_df = train_test_split(data, train_size=0.8) + + # columns + column_choices = [ui.choice(x, x) for x in q.client.train_df.columns if x != 'target'] + + # display ui + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=[ + ui.text(content='''The sample dataset used is the + wine dataset.'''), + ui.dropdown(name='categorical_columns', label='Select categorical columns', + choices=column_choices, values=[]), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True), + ui.button(name='predict', label='Predict', primary=True, disabled=True), + ]), + ui.message_bar(name='message', type='warning', text='Training will take a few seconds'), + ui.text(name='model_id', content=''), + ui.text(name='example_predictions', content='') + ] + ) + + await q.page.save() diff --git a/examples/ml_h2o_parameters.py b/examples/ml_h2o_parameters.py new file mode 100644 index 0000000000000000000000000000000000000000..03ff21eea28efbb6ea116dbf70a62356bd1b9f59 --- /dev/null +++ b/examples/ml_h2o_parameters.py @@ -0,0 +1,68 @@ +# WaveML / H2O-3 / Parameters +# Configure hyperparameters for Wave Models built using H2O-3 AutoML. +# --- +from h2o_wave import main, app, Q, ui, copy_expando +from h2o_wave_ml import build_model, ModelType + +from sklearn.datasets import load_wine +from sklearn.model_selection import train_test_split + + +@app('/demo') +async def serve(q: Q): + if q.args.train: + # train WaveML Model using H2O-3 AutoML + copy_expando(q.args, q.client) + exclude_algos = [] if q.client.include_dl else ['DeepLearning'] + q.client.wave_model = build_model( + train_df=q.client.train_df, + target_column='target', + model_type=ModelType.H2O3, + _h2o3_max_runtime_secs=q.client.max_runtime_secs, + _h2o3_nfolds=2, + _h2o3_exclude_algos=exclude_algos + ) + model_id = q.client.wave_model.model.model_id + accuracy = round(100 - q.client.wave_model.model.mean_per_class_error() * 100, 2) + + # show training details and prediction option + q.page['example'].max_runtime_secs.value = q.client.max_runtime_secs + q.page['example'].include_dl.value = q.client.include_dl + q.page['example'].predict.disabled = False + q.page['example'].message.type = 'success' + q.page['example'].message.text = 'Training successfully completed!' + q.page['example'].model_id.content = f'''**H2O AutoML model id:** {model_id}
+ **Accuracy:** {accuracy}%''' + q.page['example'].example_predictions.content = '' + elif q.args.predict: + # predict on test data + preds = q.client.wave_model.predict(test_df=q.client.test_df) + + # show predictions + q.page['example'].message.text = 'Prediction successfully completed!' + q.page['example'].example_predictions.content = f'''**Example predictions:**
+ {preds[0]}
{preds[1]}
{preds[2]}''' + else: + # prepare sample train and test dataframes + data = load_wine(as_frame=True)['frame'] + q.client.train_df, q.client.test_df = train_test_split(data, train_size=0.8) + + # display ui + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=[ + ui.text(content='''The sample dataset used is the + wine dataset.'''), + ui.spinbox(name='max_runtime_secs', label='Max Runtime (Secs)', min=5, max=30, step=1, value=10), + ui.toggle(name='include_dl', label='Include Deep Learning', value=False), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True), + ui.button(name='predict', label='Predict', primary=True, disabled=True), + ]), + ui.message_bar(name='message', type='warning', text='Training will take a few seconds'), + ui.text(name='model_id', content=''), + ui.text(name='example_predictions', content='') + ] + ) + + await q.page.save() diff --git a/examples/ml_h2o_save.py b/examples/ml_h2o_save.py new file mode 100644 index 0000000000000000000000000000000000000000..60e25f9eb4fa1c5d6c812b1b235e73763df63d15 --- /dev/null +++ b/examples/ml_h2o_save.py @@ -0,0 +1,67 @@ +# WaveML / H2O-3 / Save +# Save and load Wave Models built using H2O-3 AutoML. +# --- +from h2o_wave import main, app, Q, ui +from h2o_wave_ml import build_model, load_model, save_model, ModelType + +from sklearn.datasets import load_wine +from sklearn.model_selection import train_test_split + + +@app('/demo') +async def serve(q: Q): + if q.args.train: + # train WaveML Model using H2O-3 AutoML + wave_model = build_model( + train_df=q.client.train_df, + target_column='target', + model_type=ModelType.H2O3, + _h2o3_max_runtime_secs=5, + _h2o3_nfolds=2 + ) + model_id = wave_model.model.model_id + accuracy = round(100 - wave_model.model.mean_per_class_error() * 100, 2) + + # save model to local folder + q.client.path_model = save_model(model=wave_model, output_dir_path='./mymodelfolder') + + # show training details and prediction option + q.page['example'].predict.disabled = False + q.page['example'].message.type = 'success' + q.page['example'].message.text = 'Training successfully completed!' + q.page['example'].model_id.content = f'''**H2O AutoML model id:** {model_id}
+ **Accuracy:** {accuracy}%''' + q.page['example'].example_predictions.content = '' + elif q.args.predict: + # load model from local path + wave_model = load_model(file_path=q.client.path_model) + + # predict on test data + preds = wave_model.predict(test_df=q.client.test_df) + + # show predictions + q.page['example'].message.text = 'Prediction successfully completed!' + q.page['example'].example_predictions.content = f'''**Example predictions:**
+ {preds[0]}
{preds[1]}
{preds[2]}''' + else: + # prepare sample train and test dataframes + data = load_wine(as_frame=True)['frame'] + q.client.train_df, q.client.test_df = train_test_split(data, train_size=0.8) + + # display ui + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=[ + ui.text(content='''The sample dataset used is the + wine dataset.'''), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True), + ui.button(name='predict', label='Predict', primary=True, disabled=True), + ]), + ui.message_bar(name='message', type='warning', text='Training will take a few seconds'), + ui.text(name='model_id', content=''), + ui.text(name='example_predictions', content='') + ] + ) + + await q.page.save() diff --git a/examples/ml_h2o_shap.py b/examples/ml_h2o_shap.py new file mode 100644 index 0000000000000000000000000000000000000000..626bd992e73a52472dc849f027044233d107d4a0 --- /dev/null +++ b/examples/ml_h2o_shap.py @@ -0,0 +1,68 @@ +# WaveML / H2O-3 / SHAP +# Extract SHAP values during prediction from Wave Models built using H2O-3 AutoML. +# --- +from h2o import H2OFrame +from h2o_wave import main, app, Q, ui +from h2o_wave_ml import build_model, ModelType + +from sklearn.datasets import load_breast_cancer +from sklearn.model_selection import train_test_split + + +@app('/demo') +async def serve(q: Q): + if q.args.train: + # train WaveML Model using H2O-3 AutoML + q.client.wave_model = build_model( + train_df=q.client.train_df, + target_column='target', + model_type=ModelType.H2O3, + _h2o3_max_runtime_secs=5, + _h2o3_nfolds=2, + _h2o3_include_algos=['DRF', 'XGBoost', 'GBM'] + ) + model_id = q.client.wave_model.model.model_id + accuracy = round(q.client.wave_model.model.accuracy()[0][1] * 100, 2) + + # show training details and prediction option + q.page['example'].predict.disabled = False + q.page['example'].message.type = 'success' + q.page['example'].message.text = 'Training successfully completed!' + q.page['example'].model_id.content = f'''**H2O AutoML model id:** {model_id}
+ **Accuracy:** {accuracy}%''' + q.page['example'].example_predictions.content = '' + q.page['example'].example_contributions.content = '' + elif q.args.predict: + # predict on test data + preds = q.client.wave_model.predict(test_df=q.client.test_df) + shaps = q.client.wave_model.model.predict_contributions(H2OFrame(q.client.test_df)).as_data_frame() + + # show predictions + q.page['example'].message.text = 'Prediction successfully completed!' + q.page['example'].example_predictions.content = f'''**Example predictions:**
+ {preds[0]}
{preds[1]}
{preds[2]}''' + q.page['example'].example_contributions.content = f'''**Example SHAP contributions:**
+ {shaps.head(3).to_html()}''' + else: + # prepare sample train and test dataframes + data = load_breast_cancer(as_frame=True)['frame'] + q.client.train_df, q.client.test_df = train_test_split(data, train_size=0.8) + + # display ui + q.page['example'] = ui.form_card( + box='1 1 -1 -1', + items=[ + ui.text(content='''The sample dataset used is the + breast cancer dataset.'''), + ui.buttons(items=[ + ui.button(name='train', label='Train', primary=True), + ui.button(name='predict', label='Predict', primary=True, disabled=True), + ]), + ui.message_bar(name='message', type='warning', text='Training will take a few seconds'), + ui.text(name='model_id', content=''), + ui.text(name='example_predictions', content=''), + ui.text(name='example_contributions', content='') + ] + ) + + await q.page.save() diff --git a/examples/nav.py b/examples/nav.py new file mode 100644 index 0000000000000000000000000000000000000000..b56d10ce37da2f1c783363de3e75e36008915875 --- /dev/null +++ b/examples/nav.py @@ -0,0 +1,71 @@ +# Nav +# Use nav cards to display #sidebar #navigation. +# --- +from h2o_wave import main, app, Q, ui + + +persona = 'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&h=750&w=1260' + + +@app('/demo') +async def serve(q: Q): + if '#' in q.args and not q.args.show_nav: + hash_ = q.args['#'] + q.page.drop() + q.page['redirect'] = ui.form_card(box='1 1 2 5', items=[ + ui.text(f'#={hash_}'), + ui.button(name='show_nav', label='Back', primary=True), + ]) + else: + q.page['meta'] = ui.meta_card(box='', redirect='#') + q.page['nav1'] = ui.nav_card( + box='1 1 2 -1', + value='#menu/spam', + title='H2O Wave', + subtitle='And now for something completely different!', + image='https://wave.h2o.ai/img/h2o-logo.svg', + items=[ + ui.nav_group('Menu', items=[ + ui.nav_item(name='#menu/spam', label='Spam'), + ui.nav_item(name='#menu/ham', label='Ham'), + ui.nav_item(name='#menu/eggs', label='Eggs', tooltip='Make me a scrambled egg.'), + ui.nav_item(name='#menu/toast', label='Toast', disabled=True), + ]), + ui.nav_group('Help', items=[ + ui.nav_item(name='#about', label='About', icon='Info'), + ui.nav_item(name='#support', label='Support', icon='Help'), + ui.nav_item(name='faq', label='FAQ', icon='OfficeChat', path='https://h2o.ai/'), + ]) + ], + secondary_items=[ + ui.inline(items=[ + ui.persona(title='John Doe', subtitle='Software developer', size='s', image=persona), + ui.menu(items=[ + ui.command(name='profile', label='Profile', icon='Contact'), + ui.command(name='preferences', label='Preferences', icon='Settings'), + ui.command(name='logout', label='Logout', icon='SignOut'), + ]) + ]), + ], + ) + q.page['nav2'] = ui.nav_card( + box='3 1 2 -1', + value='#menu/ham', + persona=ui.persona(title='John Doe', subtitle='Data Scientist', caption='Online', size='xl', image=persona), + items=[ + ui.nav_group('Menu', items=[ + ui.nav_item(name='#menu/spam', label='Spam'), + ui.nav_item(name='#menu/ham', label='Ham'), + ui.nav_item(name='#menu/eggs', label='Eggs'), + ui.nav_item(name='#menu/toast', label='Toast', disabled=True), + ]), + ui.nav_group('Help', items=[ + ui.nav_item(name='#about', label='About', icon='Info'), + ui.nav_item(name='#support', label='Support', icon='Help'), + ui.nav_item(name='faq', label='FAQ', icon='OfficeChat', path='https://h2o.ai/'), + ]) + ], + secondary_items=[ui.button(name='logout', label='Logout', width='100%')], + color='primary' + ) + await q.page.save() diff --git a/examples/persona.py b/examples/persona.py new file mode 100644 index 0000000000000000000000000000000000000000..16ea0848ed9ce1eac94196a06d88328f33a33d73 --- /dev/null +++ b/examples/persona.py @@ -0,0 +1,27 @@ +# Form / Persona +# Create an individual's persona or avatar, a visual representation of a person across products. +# #form +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.persona: + q.page['example'].items = [ + ui.text_m(f'q.args.persona={q.args.persona}'), + ui.button(name='back', label='Back', primary=True), + ] + else: + image = 'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&h=750&w=1260' + q.page['example'] = ui.form_card(box='1 1 2 7', items=[ + ui.persona(title='John Doe', subtitle='Data Scientist', caption='Online', size='xs', image=image), + ui.persona(title='John Doe', subtitle='Data Scientist', caption='Online', size='s', image=image), + ui.persona(title='John Doe', subtitle='Data Scientist', caption='Online', size='m', image=image), + ui.persona(title='John Doe', subtitle='Data Scientist', caption='Online', size='l', image=image), + ui.persona(title='John Doe', subtitle='Data Scientist', caption='Online', size='xl', image=image), + ui.persona(title='', initials='JD', initials_color='$grey'), + ui.persona(name='persona', title='Click me', size='s', image=image) + ]) + + await q.page.save() diff --git a/examples/picker.py b/examples/picker.py new file mode 100644 index 0000000000000000000000000000000000000000..4dbe2c2a416314e0afb7b4fed013466e7e00672d --- /dev/null +++ b/examples/picker.py @@ -0,0 +1,27 @@ +# Form / Picker +# Use pickers to allow users to select one or more choices, such as tags or files, from a list. +# #form #picker #choice +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'selected={q.args.picker}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 5', items=[ + ui.picker(name='picker', label='Place an order (try Spam, Eggs or Ham):', choices=[ + ui.choice(name='spam', label='Spam'), + ui.choice(name='eggs', label='Eggs'), + ui.choice(name='ham', label='Ham'), + ui.choice(name='cheese', label='Cheese'), + ui.choice(name='beans', label='Beans'), + ui.choice(name='toast', label='Toast'), + ], values=['eggs']), + ui.button(name='show_inputs', label='Submit', primary=True), + ]) + await q.page.save() diff --git a/examples/picker_selection.py b/examples/picker_selection.py new file mode 100644 index 0000000000000000000000000000000000000000..0999eaf707aba872d2a6ca5921420444308ebbf0 --- /dev/null +++ b/examples/picker_selection.py @@ -0,0 +1,27 @@ +# Form / Picker / Selection +# Pre-select choices while displaying a #picker. +# #form #selection #choice +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'selected={q.args.picker}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 5', items=[ + ui.picker(name='picker', label='Picker with initial values', choices=[ + ui.choice(name='spam', label='Spam'), + ui.choice(name='eggs', label='Eggs'), + ui.choice(name='ham', label='Ham'), + ui.choice(name='cheese', label='Cheese'), + ui.choice(name='beans', label='Beans'), + ui.choice(name='toast', label='Toast'), + ], values=['spam', 'eggs']), + ui.button(name='show_inputs', label='Submit', primary=True), + ]) + await q.page.save() diff --git a/examples/pixel_art.py b/examples/pixel_art.py new file mode 100644 index 0000000000000000000000000000000000000000..7ccd1a154d673a8eb1c7b3e2b54e837bf8119e20 --- /dev/null +++ b/examples/pixel_art.py @@ -0,0 +1,16 @@ +# Pixel Art +# A card that demonstrates collaborative editing in Wave. +# Open `/demo` in multiple browsers and watch them synchronize in realtime. +# #collaboration +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] +page.drop() + +page.add('example', ui.pixel_art_card( + box='1 1 4 6', + title='Art', + data=data('color', 16 * 16), +)) +page.save() diff --git a/examples/plot_altair.py b/examples/plot_altair.py new file mode 100644 index 0000000000000000000000000000000000000000..6a34386281eb0bb6c099b6a0caed6443189d1289 --- /dev/null +++ b/examples/plot_altair.py @@ -0,0 +1,23 @@ +# Plot / Altair +# Use #Altair to create #plot specifications for the #Vega card. +# --- +import altair +from vega_datasets import data +from h2o_wave import site, ui + +spec = altair.Chart(data.cars()).mark_circle(size=60).encode( + x='Horsepower', + y='Miles_per_Gallon', + color='Origin', + tooltip=['Name', 'Origin', 'Horsepower', 'Miles_per_Gallon'] +).properties(width='container', height='container').interactive().to_json() + +page = site['/demo'] + +page['example'] = ui.vega_card( + box='1 1 4 5', + title='Altair Example', + specification=spec, +) + +page.save() diff --git a/examples/plot_app.py b/examples/plot_app.py new file mode 100644 index 0000000000000000000000000000000000000000..6f73a281fa2e10fa205c082cc37445807dc93daf --- /dev/null +++ b/examples/plot_app.py @@ -0,0 +1,30 @@ +# Plot / App +# Make a #plot from an app. +# --- +from .synth import FakeMultiCategoricalSeries as F +from h2o_wave import main, app, data, Q, ui + +n = 10 +k = 5 +f = F(groups=k) +values = [(g, t, x) for x in [f.next() for _ in range(n)] for g, t, x, dx in x] + + +@app('/demo') +async def serve(q: Q): + v = q.page.add('example', ui.plot_card( + box='1 1 4 6', + title='Intervals, stacked', + data=data('country product price', n * k), + plot=ui.plot([ui.mark( + coord='rect', + type='interval', + x='=product', + y='=price', + y_min=0, + color='=country', + stack='auto', + )]), + )) + v.data = values + await q.page.save() diff --git a/examples/plot_area.py b/examples/plot_area.py new file mode 100644 index 0000000000000000000000000000000000000000..8d6026ed0998f8a0fdaa6bb9b522c67b8175c0f5 --- /dev/null +++ b/examples/plot_area.py @@ -0,0 +1,25 @@ +# Plot / Area +# Make an area #plot. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 5 5', + title='Area', + data=data('year price', 9, rows=[ + ('1991', 15468), + ('1992', 16100), + ('1993', 15900), + ('1994', 17409), + ('1995', 17000), + ('1996', 31056), + ('1997', 31982), + ('1998', 32040), + ('1999', 33233), + ]), + plot=ui.plot([ui.mark(type='area', x='=year', y='=price', y_min=0)]) +)) + +page.save() diff --git a/examples/plot_area_groups.py b/examples/plot_area_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..75f2657d9833aaf0b7cf19fd25bcd6196b3cf1ca --- /dev/null +++ b/examples/plot_area_groups.py @@ -0,0 +1,42 @@ +# Plot / Area / Groups +# Make an area #plot showing multiple categories. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Area, groups', + data=data('month city temperature', 24, rows=[ + ('Jan', 'Tokyo', 7), + ('Jan', 'London', 3.9), + ('Feb', 'Tokyo', 6.9), + ('Feb', 'London', 4.2), + ('Mar', 'Tokyo', 9.5), + ('Mar', 'London', 5.7), + ('Apr', 'Tokyo', 14.5), + ('Apr', 'London', 8.5), + ('May', 'Tokyo', 18.4), + ('May', 'London', 11.9), + ('Jun', 'Tokyo', 21.5), + ('Jun', 'London', 15.2), + ('Jul', 'Tokyo', 25.2), + ('Jul', 'London', 17), + ('Aug', 'Tokyo', 26.5), + ('Aug', 'London', 16.6), + ('Sep', 'Tokyo', 23.3), + ('Sep', 'London', 14.2), + ('Oct', 'Tokyo', 18.3), + ('Oct', 'London', 10.3), + ('Nov', 'Tokyo', 13.9), + ('Nov', 'London', 6.6), + ('Dec', 'Tokyo', 9.6), + ('Dec', 'London', 4.8), + ]), + plot=ui.plot([ + ui.mark(type='area', x='=month', y='=temperature', color='=city', y_min=0) + ]) +)) + +page.save() diff --git a/examples/plot_area_line.py b/examples/plot_area_line.py new file mode 100644 index 0000000000000000000000000000000000000000..70d8c7b4c82a52426fd4e1052ab759d16b06f47c --- /dev/null +++ b/examples/plot_area_line.py @@ -0,0 +1,28 @@ +# Plot / Area + Line +# Make an area #plot with an additional line layer on top. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Area + Line', + data=data('year price', 9, rows=[ + ('1991', 15468), + ('1992', 16100), + ('1993', 15900), + ('1994', 17409), + ('1995', 17000), + ('1996', 31056), + ('1997', 31982), + ('1998', 32040), + ('1999', 33233), + ]), + plot=ui.plot([ + ui.mark(type='area', x='=year', y='=price', y_min=0), + ui.mark(type='line', x='=year', y='=price') + ]) +)) + +page.save() diff --git a/examples/plot_area_line_groups.py b/examples/plot_area_line_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..ee4a19c3d71a405b3e6536fa978fa45fb49f8267 --- /dev/null +++ b/examples/plot_area_line_groups.py @@ -0,0 +1,43 @@ +# Plot / Area + Line / Groups +# Make an combined area + line #plot showing multiple categories. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Area + Line, groups', + data=data('month city temperature', 24, rows=[ + ('Jan', 'Tokyo', 7), + ('Jan', 'London', 3.9), + ('Feb', 'Tokyo', 6.9), + ('Feb', 'London', 4.2), + ('Mar', 'Tokyo', 9.5), + ('Mar', 'London', 5.7), + ('Apr', 'Tokyo', 14.5), + ('Apr', 'London', 8.5), + ('May', 'Tokyo', 18.4), + ('May', 'London', 11.9), + ('Jun', 'Tokyo', 21.5), + ('Jun', 'London', 15.2), + ('Jul', 'Tokyo', 25.2), + ('Jul', 'London', 17), + ('Aug', 'Tokyo', 26.5), + ('Aug', 'London', 16.6), + ('Sep', 'Tokyo', 23.3), + ('Sep', 'London', 14.2), + ('Oct', 'Tokyo', 18.3), + ('Oct', 'London', 10.3), + ('Nov', 'Tokyo', 13.9), + ('Nov', 'London', 6.6), + ('Dec', 'Tokyo', 9.6), + ('Dec', 'London', 4.8), + ]), + plot=ui.plot([ + ui.mark(type='area', x='=month', y='=temperature', color='=city', y_min=0), + ui.mark(type='line', x='=month', y='=temperature', color='=city') + ]) +)) + +page.save() diff --git a/examples/plot_area_line_smooth.py b/examples/plot_area_line_smooth.py new file mode 100644 index 0000000000000000000000000000000000000000..6af2dd3d3bb68e9e34277de19251e56466861bd7 --- /dev/null +++ b/examples/plot_area_line_smooth.py @@ -0,0 +1,28 @@ +# Plot / Area + Line / Smooth +# Make a combined area + line #plot using a smooth curve. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Area + Line, smooth', + data=data('year price', 9, rows=[ + ('1991', 15468), + ('1992', 16100), + ('1993', 15900), + ('1994', 17409), + ('1995', 17000), + ('1996', 31056), + ('1997', 31982), + ('1998', 32040), + ('1999', 33233), + ]), + plot=ui.plot([ + ui.mark(type='area', x='=year', y='=price', curve='smooth', y_min=0), + ui.mark(type='line', x='=year', y='=price', curve='smooth') + ]) +)) + +page.save() diff --git a/examples/plot_area_negative.py b/examples/plot_area_negative.py new file mode 100644 index 0000000000000000000000000000000000000000..65e96beef462c2e369a0ea7881b86223530b3d24 --- /dev/null +++ b/examples/plot_area_negative.py @@ -0,0 +1,36 @@ +# Plot / Area / Negative +# Make an area #plot showing positive and negative values. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 5 5', + title='Area, negative values', + data=data('year value', 20, rows=[ + ('1996', 322), + ('1997', 324), + ('1998', -329), + ('1999', 342), + ('2000', -348), + ('2001', -334), + ('2002', 325), + ('2003', 316), + ('2004', 318), + ('2005', -330), + ('2006', 355), + ('2007', -366), + ('2008', -337), + ('2009', -352), + ('2010', -377), + ('2011', 383), + ('2012', 344), + ('2013', 366), + ('2014', -389), + ('2015', 334), + ]), + plot=ui.plot([ui.mark(type='area', x='=year', y='=value')]) +)) + +page.save() diff --git a/examples/plot_area_range.py b/examples/plot_area_range.py new file mode 100644 index 0000000000000000000000000000000000000000..7dc6ec1760e98bbfe52134b765d5f50462eaa630 --- /dev/null +++ b/examples/plot_area_range.py @@ -0,0 +1,47 @@ +# Plot / Area / Range +# Make an area #plot representing a range (band) of values. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Area, range', + data=data('date low high', 31, rows=[ + (1246406400000, 14.3, 27.7), + (1246492800000, 14.5, 27.8), + (1246579200000, 15.5, 29.6), + (1246665600000, 16.7, 30.7), + (1246752000000, 16.5, 25.0), + (1246838400000, 17.8, 25.7), + (1246924800000, 13.5, 24.8), + (1247011200000, 10.5, 21.4), + (1247097600000, 9.2, 23.8), + (1247184000000, 11.6, 21.8), + (1247270400000, 10.7, 23.7), + (1247356800000, 11.0, 23.3), + (1247443200000, 11.6, 23.7), + (1247529600000, 11.8, 20.7), + (1247616000000, 12.6, 22.4), + (1247702400000, 13.6, 19.6), + (1247788800000, 11.4, 22.6), + (1247875200000, 13.2, 25.0), + (1247961600000, 14.2, 21.6), + (1248048000000, 13.1, 17.1), + (1248134400000, 12.2, 15.5), + (1248220800000, 12.0, 20.8), + (1248307200000, 12.0, 17.1), + (1248393600000, 12.7, 18.3), + (1248480000000, 12.4, 19.4), + (1248566400000, 12.6, 19.9), + (1248652800000, 11.9, 20.2), + (1248739200000, 11.0, 19.3), + (1248825600000, 10.8, 17.8), + (1248912000000, 11.8, 18.5), + (1248998400000, 10.8, 16.1), + ]), + plot=ui.plot([ui.mark(type='area', x_scale='time', x='=date', y0='=low', y='=high')]) +)) + +page.save() diff --git a/examples/plot_area_smooth.py b/examples/plot_area_smooth.py new file mode 100644 index 0000000000000000000000000000000000000000..bac7e0e6475c2d737c509b68519025d94bc64ff8 --- /dev/null +++ b/examples/plot_area_smooth.py @@ -0,0 +1,25 @@ +# Plot / Area / Smooth +# Make an area #plot with a smooth curve. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Area, smooth', + data=data('year price', 9, rows=[ + ('1991', 15468), + ('1992', 16100), + ('1993', 15900), + ('1994', 17409), + ('1995', 17000), + ('1996', 31056), + ('1997', 31982), + ('1998', 32040), + ('1999', 33233), + ]), + plot=ui.plot([ui.mark(type='area', x_scale='time', x='=year', y='=price', curve='smooth', y_min=0)]) +)) + +page.save() diff --git a/examples/plot_area_stacked.py b/examples/plot_area_stacked.py new file mode 100644 index 0000000000000000000000000000000000000000..390c4710be11465370c60bdec24cd2199d796391 --- /dev/null +++ b/examples/plot_area_stacked.py @@ -0,0 +1,46 @@ +# Plot / Area / Stacked +# Make a #stacked area #plot. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Area, stacked', + data=data('country year value', 28, rows=[ + ('Asia', '1750', 502), + ('Asia', '1800', 635), + ('Asia', '1850', 809), + ('Asia', '1900', 5268), + ('Asia', '1950', 4400), + ('Asia', '1999', 3634), + ('Asia', '2050', 947), + ('Africa', '1750', 106), + ('Africa', '1800', 107), + ('Africa', '1850', 111), + ('Africa', '1900', 1766), + ('Africa', '1950', 221), + ('Africa', '1999', 767), + ('Africa', '2050', 133), + ('Europe', '1750', 163), + ('Europe', '1800', 203), + ('Europe', '1850', 276), + ('Europe', '1900', 628), + ('Europe', '1950', 547), + ('Europe', '1999', 729), + ('Europe', '2050', 408), + ('Oceania', '1750', 200), + ('Oceania', '1800', 200), + ('Oceania', '1850', 200), + ('Oceania', '1900', 460), + ('Oceania', '1950', 230), + ('Oceania', '1999', 300), + ('Oceania', '2050', 300), + ]), + plot=ui.plot([ + ui.mark(type='area', x_scale='time', x='=year', y='=value', y_min=0, color='=country', stack='auto') + ]) +)) + +page.save() diff --git a/examples/plot_axis_title.py b/examples/plot_axis_title.py new file mode 100644 index 0000000000000000000000000000000000000000..6bc4948408aecbf97e7cb6694098a6df6a94f58c --- /dev/null +++ b/examples/plot_axis_title.py @@ -0,0 +1,31 @@ +# Plot / Axis Titles +# Display custom axis titles on a #plot. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Line title', + data=data('month price', 12, rows=[ + ('Jan', 51), + ('Feb', 91), + ('Mar', 34), + ('Apr', 47), + ('May', 63), + ('June', 58), + ('July', 56), + ('Aug', 77), + ('Sep', 99), + ('Oct', 106), + ('Nov', 88), + ('Dec', 56), + ]), + plot=ui.plot([ + ui.mark(type='line', x='=month', y='=price', y_min=0, x_title='Month', y_title='Price') + ]) +)) + +page.save() + diff --git a/examples/plot_bokeh.py b/examples/plot_bokeh.py new file mode 100644 index 0000000000000000000000000000000000000000..f42163703241ad205c0fa9296c80cf71116f4409 --- /dev/null +++ b/examples/plot_bokeh.py @@ -0,0 +1,40 @@ +# Plot / Bokeh +# Use #Bokeh to create plots. #plot +# --- +import numpy as np +from bokeh.models import HoverTool +from bokeh.plotting import figure +from bokeh.resources import CDN +from bokeh.embed import file_html + +from h2o_wave import site, ui + +n = 500 +x = 2 + 2 * np.random.standard_normal(n) +y = 2 + 2 * np.random.standard_normal(n) +p = figure( + match_aspect=True, + tools="wheel_zoom,reset", + background_fill_color='#440154', + sizing_mode='stretch_both' +) +p.grid.visible = False +r, bins = p.hexbin(x, y, size=0.5, hover_color="pink", hover_alpha=0.8) +p.circle(x, y, color="white", size=1) +p.add_tools(HoverTool( + tooltips=[("count", "@c"), ("(q,r)", "(@q, @r)")], + mode="mouse", + point_policy="follow_mouse", + renderers=[r] +)) + +# Export html for our frame card +html = file_html(p, CDN, "plot") + +page = site['/demo'] +page['example'] = ui.frame_card( + box='1 1 -1 -1', + title='Hexbin for 500 points', + content=html, +) +page.save() diff --git a/examples/plot_bokeh_callbacks.py b/examples/plot_bokeh_callbacks.py new file mode 100644 index 0000000000000000000000000000000000000000..492797c70e92ffcaab241996edc11df0ee11fdea --- /dev/null +++ b/examples/plot_bokeh_callbacks.py @@ -0,0 +1,109 @@ +# Plot / Bokeh / Widgets +# Embed Bokeh widgets with script callbacks +# --- + +# Original source: https://docs.bokeh.org/en/latest/docs/user_guide/interaction/callbacks.html#customjs-for-selections + +import json +from random import random +from h2o_wave import main, app, Q, ui, data +from bokeh.resources import CDN +from bokeh.layouts import row +from bokeh.models import ColumnDataSource, CustomJS +from bokeh.plotting import figure, output_file, show +from bokeh.embed import json_item + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.client.initialized = True + + # Create a plot + x = [random() for x in range(500)] + y = [random() for y in range(500)] + + s1 = ColumnDataSource(data=dict(x=x, y=y)) + p1 = figure(plot_width=250, plot_height=300, tools="lasso_select", title="Select Here") + p1.circle('x', 'y', source=s1, alpha=0.6) + + s2 = ColumnDataSource(data=dict(x=[], y=[])) + p2 = figure(plot_width=250, plot_height=300, x_range=(0, 1), y_range=(0, 1), tools="", title="Watch Here") + p2.circle('x', 'y', source=s2, alpha=0.6) + + s1.selected.js_on_change( + 'indices', + CustomJS( + args=dict(s1=s1, s2=s2), + code=""" + var indices = cb_obj.indices; + var d1 = s1.data; + var d2 = s2.data; + d2['x'] = [] + d2['y'] = [] + for (var i = 0; i < indices.length; i++) { + d2['x'].push(d1['x'][indices[i]]) + d2['y'].push(d1['y'][indices[i]]) + } + s2.change.emit(); + + // Send the selected indices to the Wave app via an event. + // Here, + // - The first argument, 'the_plot', is some name to uniquely identify the source of the event. + // - The second argument, 'selected', is some name to uniquely identify the type of event. + // - The third argument is any arbitrary data to be sent as part of the event. + // Ordinarily, we would just call wave.emit('the_plot', 'selected', indices), but this particular + // example triggers events every time the indices change (which is several times per second), + // so we use a 'debounced' version of 'emit()' that waits for a second before emitting an event. + // Here, 'emit_debounced()' is not part of the Wave API, but custom-built for this example - see + // the inline_script's 'content' below. + emit_debounced('the_plot', 'selected', indices); + // The indices will be accessible to the Wave app using 'q.events.the_plot.selected'. + """ + ) + ) + + layout = row(p1, p2) + + # Serialize the plot as JSON. + # See https://docs.bokeh.org/en/latest/docs/user_guide/embed.html#json-items + plot_id = 'my_plot' + plot_data = json.dumps(json_item(layout, plot_id)) + + q.page['meta'] = ui.meta_card( + box='', + # Import Bokeh Javascript libraries from CDN + scripts=[ui.script(path=f) for f in CDN.js_files], + # Execute custom Javascript + script=ui.inline_script( + # The inline script does two things: + content=f''' + // 1. Create a debounced version of `wave.emit()` and make it accessible to Bokeh's event handler. + // window.emit_debounced() is the name of new, debounced (calmer) version of wave.emit() that waits + // for 1000ms before emitting an event. + window.emit_debounced=window.wave.debounce(1000, window.wave.emit); + + // 2. Make Bokeh render the plot. + Bokeh.embed.embed_item({plot_data}); + ''', + # Ensure that the Bokeh Javascript library is loaded + requires=['Bokeh'], + # Ensure that the target HTML container element is available + targets=[plot_id], + ), + ) + q.page['plot'] = ui.markup_card( + box='1 1 4 4', + title='', + content=f'
', + ) + q.page['details'] = ui.markdown_card( + box='1 5 4 1', + title='Selected Marks', + content='Nothing selected.', + ) + else: + if q.events.the_plot.selected: + q.page['details'].content = f'You selected {q.events.the_plot.selected}' + + await q.page.save() diff --git a/examples/plot_bokeh_script.py b/examples/plot_bokeh_script.py new file mode 100644 index 0000000000000000000000000000000000000000..046abb78ac8b49404473e33d65880a650017b0a1 --- /dev/null +++ b/examples/plot_bokeh_script.py @@ -0,0 +1,50 @@ +# Plot / Bokeh / Script +# Embed Bokeh components into a page using Javascript. +# --- + +import json +from h2o_wave import site, ui +from bokeh.resources import CDN +from bokeh.plotting import figure +from bokeh.embed import json_item +from bokeh.sampledata.iris import flowers + +# Make a plot +plot = figure(title="Iris Morphology") +plot.xaxis.axis_label = 'Petal Length' +plot.yaxis.axis_label = 'Petal Width' + +colormap = {'setosa': 'red', 'versicolor': 'green', 'virginica': 'blue'} +colors = [colormap[x] for x in flowers['species']] +plot.circle(flowers["petal_length"], flowers["petal_width"], color=colors, fill_alpha=0.2, size=10) + +# Serialize the plot as JSON. +# See https://docs.bokeh.org/en/latest/docs/user_guide/embed.html#json-items +plot_id = 'my_plot' +plot_data = json.dumps(json_item(plot, plot_id)) + +page = site['/demo'] + +page['meta'] = ui.meta_card( + box='', + # Import Bokeh Javascript libraries from CDN + scripts=[ui.script(path=f) for f in CDN.js_files], + # Execute custom Javascript + script=ui.inline_script( + # Call Bakeh's renderer using Javascript + content=f'Bokeh.embed.embed_item({plot_data});', + # Ensure that the Bokeh Javascript library is available + requires=['Bokeh'], + # Ensure that the target HTML container element is available + targets=[plot_id], + ), +) + +# Create a HTML container element to hold our plot. +page['example'] = ui.markup_card( + box='1 1 5 8', + title='', + content=f'
', +) + +page.save() diff --git a/examples/plot_d3.js b/examples/plot_d3.js new file mode 100644 index 0000000000000000000000000000000000000000..15fe8c46ebb262322d544d16e0235b8b4be1ca21 --- /dev/null +++ b/examples/plot_d3.js @@ -0,0 +1,72 @@ +const + width = document.body.clientWidth, + height = Math.min(640, width), + groupTicks = (d, step) => { + const k = (d.endAngle - d.startAngle) / d.value; + return d3.range(0, d.value, step).map(value => { + return { value: value, angle: value * k + d.startAngle }; + }); + }, + formatValue = d3.formatPrefix(",.0", 1e3), + chord = d3.chord() + .padAngle(0.05) + .sortSubgroups(d3.descending), + outerRadius = Math.min(width, height) * 0.5 - 30, + innerRadius = outerRadius - 20, + arc = d3.arc() + .innerRadius(innerRadius) + .outerRadius(outerRadius), + ribbon = d3.ribbon() + .radius(innerRadius), + color = d3.scaleOrdinal() + .domain(d3.range(4)) + .range(["#000000", "#FFDD89", "#957244", "#F26223"]), + render = (data) => { + const svg = d3.select("body") + .append("svg") + .attr("viewBox", [-width / 2, -height / 2, width, height]) + .attr("font-size", 10) + .attr("font-family", "sans-serif"); + + const chords = chord(data); + + const group = svg.append("g") + .selectAll("g") + .data(chords.groups) + .join("g"); + + group.append("path") + .attr("fill", d => color(d.index)) + .attr("stroke", d => d3.rgb(color(d.index)).darker()) + .attr("d", arc); + + const groupTick = group.append("g") + .selectAll("g") + .data(d => groupTicks(d, 1e3)) + .join("g") + .attr("transform", d => `rotate(${d.angle * 180 / Math.PI - 90}) translate(${outerRadius},0)`); + + groupTick.append("line") + .attr("stroke", "#000") + .attr("x2", 6); + + groupTick + .filter(d => d.value % 5e3 === 0) + .append("text") + .attr("x", 8) + .attr("dy", ".35em") + .attr("transform", d => d.angle > Math.PI ? "rotate(180) translate(-16)" : null) + .attr("text-anchor", d => d.angle > Math.PI ? "end" : null) + .text(d => formatValue(d.value)); + + svg.append("g") + .attr("fill-opacity", 0.67) + .selectAll("path") + .data(chords) + .join("path") + .attr("d", ribbon) + .attr("fill", d => color(d.target.index)) + .attr("stroke", d => d3.rgb(color(d.target.index)).darker()); + }; + + diff --git a/examples/plot_d3.py b/examples/plot_d3.py new file mode 100644 index 0000000000000000000000000000000000000000..a5785287b3bd68236535687a3c81ceb57226b3e0 --- /dev/null +++ b/examples/plot_d3.py @@ -0,0 +1,45 @@ +# Plot / D3.js +# Create custom plots using D3.js. #plot +# --- +import json +import os.path +from h2o_wave import site, ui + +# The example D3 Javascript file is located in the same directory as this example; get its path +d3_js_script_filename = os.path.join(os.path.dirname(__file__), 'plot_d3.js') + +# Upload the script to the server. Typically, you'd do this only once, when your app is installed. +d3_js_script_path, = site.upload([d3_js_script_filename]) + +html_template = ''' + + + + + + + + + + +''' + +# This data is hard-coded here for simplicity. +# During production use, this data would be the output of some compute routine. +data = [ + [11975, 5871, 8916, 2868], + [1951, 10048, 2060, 6171], + [8010, 16145, 8090, 8045], + [1013, 990, 940, 6907], +] + +# Plug JSON-serialized data into our html template +html = html_template.format(script_path=d3_js_script_path, data=json.dumps(data)) + +page = site['/demo'] +page['example'] = ui.frame_card( + box='1 1 5 8', + title='D3 Chord Diagram', + content=html, +) +page.save() diff --git a/examples/plot_events.py b/examples/plot_events.py new file mode 100644 index 0000000000000000000000000000000000000000..e9db958dc5397d5259124a4291b912f235b1eb0c --- /dev/null +++ b/examples/plot_events.py @@ -0,0 +1,31 @@ +# Plot / Events +# Handle #events on a #plot card. +# --- +from h2o_wave import main, app, Q, ui, data + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.client.initialized = True + q.page['pricing'] = ui.plot_card( + box='1 1 4 5', + title='Interval', + data=data(fields='product price', rows=[ + ['spam', 1.49], + ['eggs', 2.49], + ['ham', 1.99], + ], pack=True), + plot=ui.plot([ui.mark(type='interval', x='=product', y='=price', y_min=0)]), + events=['select_marks'] + ) + q.page['details'] = ui.markdown_card( + box='1 6 4 2', + title='Selected Product', + content='Nothing selected.', + ) + else: + if q.events.pricing: + q.page['details'].content = f'You selected {q.events.pricing.select_marks}' + + await q.page.save() diff --git a/examples/plot_events_disabled.py b/examples/plot_events_disabled.py new file mode 100644 index 0000000000000000000000000000000000000000..0cc26df4fca0c38a17ebce2304a0096439d32a1e --- /dev/null +++ b/examples/plot_events_disabled.py @@ -0,0 +1,40 @@ +# Plot / Events/ Disabled +# Customize for which marks on a #plot card you do not wish to handle #events . +# --- +from h2o_wave import main, app, Q, ui, data + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.client.initialized = True + q.page['example'] = ui.plot_card( + box='1 1 4 5', + title='Interval, range', + data=data('year value', 8, rows=[ + ('1991', 3), + ('1992', 4), + ('1993', 3.5), + ('1994', 5), + ('1995', 4.9), + ('1996', 6), + ('1997', 7), + ('1998', 9), + ('1999', 13), + ]), + plot=ui.plot([ + ui.mark(type='line', x_scale='time', x='=year', y='=value', y_min=0, interactive=False), + ui.mark(type='point', x='=year', y='=value', size=8, fill_color='red') + ]), + events=['select_marks'], + ) + q.page['details'] = ui.markdown_card( + box='1 6 4 2', + title='Selected Year', + content='Nothing selected.', + ) + else: + if q.events.example: + q.page['details'].content = f'You selected {q.events.example.select_marks}' + + await q.page.save() diff --git a/examples/plot_events_routing.py b/examples/plot_events_routing.py new file mode 100644 index 0000000000000000000000000000000000000000..5dea5a23c53fafb2eda0d217f49e075c9fca7003 --- /dev/null +++ b/examples/plot_events_routing.py @@ -0,0 +1,35 @@ +# Plot / Events / Routing +# Handle #events on a #plot card using routing. +# --- +from h2o_wave import main, app, on, handle_on, Q, ui, data + + +@on('pricing.select_marks') +async def show_selected_marks(q: Q, marks: any): + q.page['details'].content = f'You selected {marks}' + await q.page.save() + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.client.initialized = True + q.page['pricing'] = ui.plot_card( + box='1 1 4 5', + title='Interval', + data=data(fields='product price', rows=[ + ['spam', 1.49], + ['eggs', 2.49], + ['ham', 1.99], + ], pack=True), + plot=ui.plot([ui.mark(type='interval', x='=product', y='=price', y_min=0)]), + events=['select_marks'] + ) + q.page['details'] = ui.markdown_card( + box='1 6 4 2', + title='Selected Product', + content='Nothing selected.', + ) + await q.page.save() + else: + await handle_on(q) diff --git a/examples/plot_form.py b/examples/plot_form.py new file mode 100644 index 0000000000000000000000000000000000000000..b7b5acef81afb01be18cdd128b31d83bc4499331 --- /dev/null +++ b/examples/plot_form.py @@ -0,0 +1,36 @@ +# Plot / Form +# Display a #plot inside a #form. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.form_card( + box='1 1 -1 8', + items=[ + ui.text_xl('This year'), + ui.visualization( + plot=ui.plot([ui.mark(type='interval', x='=profession', y='=salary', y_min=0)]), + data=data(fields='profession salary', rows=[ + ('medicine', 23000), + ('fire fighting', 18000), + ('pedagogy', 24000), + ('psychology', 22500), + ('computer science', 36000), + ], pack=True), + ), + ui.text_xl('Last year'), + ui.visualization( + plot=ui.plot([ui.mark(type='interval', x='=profession', y='=salary', y_min=0)]), + data=data(fields='profession salary', rows=[ + ('medicine', 21000), + ('fire fighting', 17000), + ('pedagogy', 23500), + ('psychology', 22300), + ('computer science', 33000), + ], pack=True) + ), + ], +)) + +page.save() diff --git a/examples/plot_histogram.py b/examples/plot_histogram.py new file mode 100644 index 0000000000000000000000000000000000000000..d0817ffe8547cc550641511a3ebe5a506276d573 --- /dev/null +++ b/examples/plot_histogram.py @@ -0,0 +1,24 @@ +# Plot / Histogram +# Make a #histogram. #plot +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Histogram', + data=data('price low high', 8, rows=[ + (4, 50, 100), + (6, 100, 150), + (8, 150, 200), + (16, 350, 400), + (18, 400, 450), + (10, 200, 250), + (12, 250, 300), + (14, 300, 350), + ]), + plot=ui.plot([ui.mark(type='interval', y='=price', x1='=low', x2='=high', y_min=0)]) +)) + +page.save() diff --git a/examples/plot_interaction_brush.py b/examples/plot_interaction_brush.py new file mode 100644 index 0000000000000000000000000000000000000000..c75a577103feb157dac4c82a4862f41c583315bd --- /dev/null +++ b/examples/plot_interaction_brush.py @@ -0,0 +1,27 @@ +# Plot / Interaction / Brush +# Make a scatterplot with brush enabled. #plot +# --- + +from h2o_wave import main, app, Q, ui, data + + +@app('/demo') +async def serve(q: Q): + q.page['example'] = ui.plot_card( + box='1 1 4 5', + title='Line plot brush', + data=data('year value', 8, rows=[ + ('1991', 3), + ('1992', 4), + ('1993', 3.5), + ('1994', 5), + ('1995', 4.9), + ('1996', 6), + ('1997', 7), + ('1998', 9), + ('1999', 13), + ]), + plot=ui.plot([ui.mark(type='line', x_scale='time', x='=year', y='=value', y_min=0)]), + interactions=['brush'] + ) + await q.page.save() diff --git a/examples/plot_interaction_drag_move.py b/examples/plot_interaction_drag_move.py new file mode 100644 index 0000000000000000000000000000000000000000..a2a1003a4e0750c1e10ecb4dc9d78a9033d5ebbe --- /dev/null +++ b/examples/plot_interaction_drag_move.py @@ -0,0 +1,28 @@ +# Plot / Interaction / Drag move +# Make a scatterplot with drag move enabled. #plot +# --- + +from h2o_wave import main, app, Q, ui, data + + +@app('/demo') +async def serve(q: Q): + q.page['example'] = ui.plot_card( + box='1 1 4 5', + title='Point plot drag move', + data=data('height weight', 10, rows=[ + (170, 59), + (159.1, 47.6), + (166, 69.8), + (176.2, 66.8), + (160.2, 75.2), + (180.3, 76.4), + (164.5, 63.2), + (173, 60.9), + (183.5, 74.8), + (175.5, 70), + ]), + interactions=['drag_move'], + plot=ui.plot([ui.mark(type='point', x='=weight', y='=height')]) + ) + await q.page.save() diff --git a/examples/plot_interaction_zoom.py b/examples/plot_interaction_zoom.py new file mode 100644 index 0000000000000000000000000000000000000000..ea38c863c4b2f0757e5962152ad7d1d4d0fdebb2 --- /dev/null +++ b/examples/plot_interaction_zoom.py @@ -0,0 +1,28 @@ +# Plot / Interaction / Zoom +# Make a scatterplot with zoom enabled. #plot +# --- + +from h2o_wave import main, app, Q, ui, data + + +@app('/demo') +async def serve(q: Q): + q.page['example'] = ui.plot_card( + box='1 1 4 5', + title='Point plot zoom', + data=data('height weight', 10, rows=[ + (170, 59), + (159.1, 47.6), + (166, 69.8), + (176.2, 66.8), + (160.2, 75.2), + (180.3, 76.4), + (164.5, 63.2), + (173, 60.9), + (183.5, 74.8), + (175.5, 70), + ]), + interactions=['scale_zoom'], + plot=ui.plot([ui.mark(type='point', x='=weight', y='=height')]) + ) + await q.page.save() diff --git a/examples/plot_interval.py b/examples/plot_interval.py new file mode 100644 index 0000000000000000000000000000000000000000..e4662c43d5af6c065f301694d77ec23f6c6d11ae --- /dev/null +++ b/examples/plot_interval.py @@ -0,0 +1,21 @@ +# Plot / Interval +# Make a column #plot. #interval +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Interval', + data=data('profession salary', 5, rows=[ + ('medicine', 23000), + ('fire fighting', 18000), + ('pedagogy', 24000), + ('psychology', 22500), + ('computer science', 36000), + ]), + plot=ui.plot([ui.mark(type='interval', x='=profession', y='=salary', y_min=0)]) +)) + +page.save() diff --git a/examples/plot_interval_annotation.py b/examples/plot_interval_annotation.py new file mode 100644 index 0000000000000000000000000000000000000000..bcad04c41ba766a86c2e163a2f1cd7ffa07cfd64 --- /dev/null +++ b/examples/plot_interval_annotation.py @@ -0,0 +1,28 @@ +# Plot / Interval / Annotation +# Add annotations to a column #plot. #annotation #interval +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Interval - annotation', + data=data('profession salary', 5, rows=[ + ('medicine', 23000), + ('fire fighting', 18000), + ('pedagogy', 24000), + ('psychology', 22500), + ('computer science', 36000), + ]), + plot=ui.plot([ + ui.mark(type='interval', x='=profession', y='=salary', y_min=0), + ui.mark(x='psychology', y=32000, label='point'), + ui.mark(x='pedagogy', label='vertical line'), + ui.mark(y=23000, label='horizontal line'), + ui.mark(x='fire fighting', x0='medicine', label='vertical region'), + ui.mark(y=35000, y0=30000, label='horizontal region') + ]) +)) + +page.save() diff --git a/examples/plot_interval_annotation_transpose.py b/examples/plot_interval_annotation_transpose.py new file mode 100644 index 0000000000000000000000000000000000000000..b65e27091c876635890b0f0f24c02524ae59f5bc --- /dev/null +++ b/examples/plot_interval_annotation_transpose.py @@ -0,0 +1,28 @@ +# Plot / Interval / Annotation / Transpose +# Add annotations to a bar #plot. #annotation #interval +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Categorical-Numeric', + data=data('profession salary', 5, rows=[ + ('medicine', 23000), + ('fire fighting', 18000), + ('pedagogy', 24000), + ('psychology', 22500), + ('computer science', 36000), + ]), + plot=ui.plot([ + ui.mark(type='interval', x='=salary', y='=profession'), + ui.mark(y='fire fighting', x=23000, label='point'), + ui.mark(y='pedagogy', label='vertical line'), + ui.mark(x=27000, label='horizontal line'), + ui.mark(y='computer science', y0='psychology', label='horizontal region'), + ui.mark(x=35000, x0=30000, label='vertical region') + ]) +)) + +page.save() diff --git a/examples/plot_interval_groups.py b/examples/plot_interval_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..304126278ed226685f981c9d4bf74f4ca253f243 --- /dev/null +++ b/examples/plot_interval_groups.py @@ -0,0 +1,26 @@ +# Plot / Interval / Groups +# Make a grouped column #plot. #interval +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Intervals, groups', + data=data('day type time', 10, rows=[ + ('Mon.','series1', 2800), + ('Mon.','series2', 2260), + ('Tues.','series1', 1800), + ('Tues.','series2', 1300), + ('Wed.','series1', 950), + ('Wed.','series2', 900), + ('Thur.','series1', 500), + ('Thur.','series2', 390), + ('Fri.','series1', 170), + ('Fri.','series2', 100), + ]), + plot=ui.plot([ui.mark(type='interval', x='=day', y='=time', color='=type', dodge='auto', y_min=0)]) +)) + +page.save() diff --git a/examples/plot_interval_groups_transpose.py b/examples/plot_interval_groups_transpose.py new file mode 100644 index 0000000000000000000000000000000000000000..bfb9601f3ba2352b50b88605546b50bab5921116 --- /dev/null +++ b/examples/plot_interval_groups_transpose.py @@ -0,0 +1,27 @@ +# Plot / Interval / Groups / Transpose +# Make a grouped bar #plot. #interval +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Intervals, groups', + data=data('day type time', 10, rows=[ + ('Mon.','series1', 2800), + ('Mon.','series2', 2260), + ('Tues.','series1', 1800), + ('Tues.','series2', 1300), + ('Wed.','series1', 950), + ('Wed.','series2', 900), + ('Thur.','series1', 500), + ('Thur.','series2', 390), + ('Fri.','series1', 170), + ('Fri.','series2', 100), + ]), + plot=ui.plot([ui.mark(type='interval', y='=day', x='=time', color='=type', dodge='auto', x_min=0)]) +)) + +page.save() + diff --git a/examples/plot_interval_helix.py b/examples/plot_interval_helix.py new file mode 100644 index 0000000000000000000000000000000000000000..a041382ef75e92c6c00390149432efeb801ec9eb --- /dev/null +++ b/examples/plot_interval_helix.py @@ -0,0 +1,15 @@ +# Plot / Interval / Helix +# Make a bar #plot in helical coordinates. #interval +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Interval, helix', + data=data('product price', 200, rows=[ (f'P{i}', i) for i in range(200)]), + plot=ui.plot([ui.mark(coord='helix', type='interval', x='=product', y='=price', y_min=0)]) +)) + +page.save() diff --git a/examples/plot_interval_labels.py b/examples/plot_interval_labels.py new file mode 100644 index 0000000000000000000000000000000000000000..5d061b8f0e23ee6b8643350629ede2a058a14cc8 --- /dev/null +++ b/examples/plot_interval_labels.py @@ -0,0 +1,30 @@ +# Plot / Interval / Labels +# Make a column #plot with labels on each bar. #interval +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Label Customization', + data=data('profession salary', 5, rows=[ + ('medicine', 33000), + ('fire fighting', 18000), + ('pedagogy', 24000), + ('psychology', 22500), + ('computer science', 36000), + ]), + plot=ui.plot([ + ui.mark( + type='interval', + x='=profession', + y='=salary', y_min=0, + label='=${{intl salary minimum_fraction_digits=2 maximum_fraction_digits=2}}', + label_offset=0, label_position='middle', label_rotation='-90', label_fill_color='#fff', + label_font_weight='bold' + ) + ]) +)) + +page.save() diff --git a/examples/plot_interval_polar.py b/examples/plot_interval_polar.py new file mode 100644 index 0000000000000000000000000000000000000000..f6e409061df0051ff0da83f03c83215c507a47d1 --- /dev/null +++ b/examples/plot_interval_polar.py @@ -0,0 +1,32 @@ +# Plot / Interval / Polar +# Make a rose #plot (a bar plot in polar coordinates). #interval +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Interval, polar', + data=data('month rainfall', 16, rows=[ + ('Jan', 18.9), + ('Feb', 28.8), + ('Mar', 39.3), + ('Apr', 31.4), + ('May', 47), + ('Jun', 20.3), + ('Jul', 24), + ('Aug', 35.6), + ('Jan', 12.4), + ('Feb', 23.2), + ('Mar', 34.5), + ('Apr', 29.7), + ('May', 42), + ('Jun', 35.5), + ('Jul', 37.4), + ('Aug', 42.4), + ]), + plot=ui.plot([ui.mark(coord='polar', type='interval', x='=month', y='=rainfall', y_min=0, stroke_color='$card')]) +)) + +page.save() diff --git a/examples/plot_interval_polar_stacked.py b/examples/plot_interval_polar_stacked.py new file mode 100644 index 0000000000000000000000000000000000000000..c0fef0fe6dcb24aef2c0f236a19ea0e249bd4772 --- /dev/null +++ b/examples/plot_interval_polar_stacked.py @@ -0,0 +1,43 @@ +# Plot / Interval / Polar / Stacked +# Make a #stacked rose #plot (a stacked bar plot in polar coordinates). #interval +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Intervals, polar, stacked', + data=data('city month rainfall', 16, rows=[ + ('London', 'Jan', 18.9), + ('London', 'Feb', 28.8), + ('London', 'Mar', 39.3), + ('London', 'Apr', 31.4), + ('London', 'May', 47), + ('London', 'Jun', 20.3), + ('London', 'Jul', 24), + ('London', 'Aug', 35.6), + ('Berlin', 'Jan', 12.4), + ('Berlin', 'Feb', 23.2), + ('Berlin', 'Mar', 34.5), + ('Berlin', 'Apr', 29.7), + ('Berlin', 'May', 42), + ('Berlin', 'Jun', 35.5), + ('Berlin', 'Jul', 37.4), + ('Berlin', 'Aug', 42.4), + ]), + plot=ui.plot([ + ui.mark( + coord='polar', + type='interval', + x='=month', + y='=rainfall', + color='=city', + stack='auto', + y_min=0, + stroke_color='$card' + ) + ]) +)) + +page.save() diff --git a/examples/plot_interval_range.py b/examples/plot_interval_range.py new file mode 100644 index 0000000000000000000000000000000000000000..0b5701f00dbf5c52153a79154b2297d96061ab63 --- /dev/null +++ b/examples/plot_interval_range.py @@ -0,0 +1,22 @@ +# Plot / Interval / Range +# Make a column #plot with each bar representing high/low (or start/end) values. +# Transposing this produces a gantt plot. #interval #range +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Interval, range', + data=data('profession max min', 5, rows=[ + ('medicine', 110000, 23000), + ('fire fighting', 120000, 18000), + ('pedagogy', 125000, 24000), + ('psychology', 130000, 22500), + ('computer science', 151000, 36000), + ]), + plot=ui.plot([ui.mark(type='interval', x='=profession', y0='=min', y='=max')]) +)) + +page.save() diff --git a/examples/plot_interval_range_transpose.py b/examples/plot_interval_range_transpose.py new file mode 100644 index 0000000000000000000000000000000000000000..a229e07c649bba1ea16d3682d9641b0d6945d459 --- /dev/null +++ b/examples/plot_interval_range_transpose.py @@ -0,0 +1,21 @@ +# Plot / Interval / Transpose +# Make a bar #plot. #interval +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Interval range transposed', + data=data('profession max min', 5, rows=[ + ('medicine', 110000, 23000), + ('fire fighting', 120000, 18000), + ('pedagogy', 125000, 24000), + ('psychology', 130000, 22500), + ('computer science', 151000, 36000), + ]), + plot=ui.plot([ui.mark(type='interval', x='=max', x0='=min', y='=profession')]), +)) + +page.save() diff --git a/examples/plot_interval_stacked.py b/examples/plot_interval_stacked.py new file mode 100644 index 0000000000000000000000000000000000000000..9814632f303cc289a66c6e1f744191e9ce2909ba --- /dev/null +++ b/examples/plot_interval_stacked.py @@ -0,0 +1,28 @@ +# Plot / Interval / Stacked +# Make a #stacked column #plot. #interval +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Intervals, stacked', + data=data('day type time', 10, rows=[ + ('Mon.','series1', 2800), + ('Mon.','series2', 2260), + ('Tues.','series1', 1800), + ('Tues.','series2', 1300), + ('Wed.','series1', 950), + ('Wed.','series2', 900), + ('Thur.','series1', 500), + ('Thur.','series2', 390), + ('Fri.','series1', 170), + ('Fri.','series2', 100), + ]), + plot=ui.plot([ + ui.mark(type='interval', x='=day', y='=time', color='=type', stack='auto', y_min=0) + ]) +)) + +page.save() diff --git a/examples/plot_interval_stacked_grouped.py b/examples/plot_interval_stacked_grouped.py new file mode 100644 index 0000000000000000000000000000000000000000..9084f3797965a4521c4d14716d3a363b1a204030 --- /dev/null +++ b/examples/plot_interval_stacked_grouped.py @@ -0,0 +1,38 @@ +# Plot / Interval / Stacked / Grouped +# Make a column #plot with both #stacked and grouped bars. #interval +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Intervals, stacked and dodged', + data=data('day type time gender', 12, rows=[ + ('Mon.','series1', 2800, 'male'), + ('Mon.','series1', 1800, 'female'), + ('Mon.','series2', 2260, 'female'), + ('Mon.','series2', 710, 'male'), + ('Tues.','series1', 1800, 'male'), + ('Tues.','series1', 290, 'female'), + ('Tues.','series2', 1300, 'female'), + ('Tues.','series2', 960, 'male'), + ('Wed.','series1', 950, 'male'), + ('Wed.','series1', 2730, 'female'), + ('Wed.','series2', 1390, 'male'), + ('Wed.','series2', 900, 'female'), + ]), + plot=ui.plot([ + ui.mark( + type='interval', + x='=day', + y='=time', + color='=type', + stack='auto', + dodge='=gender', + y_min=0 + ) + ]) +)) + +page.save() diff --git a/examples/plot_interval_stacked_grouped_transpose.py b/examples/plot_interval_stacked_grouped_transpose.py new file mode 100644 index 0000000000000000000000000000000000000000..ac5c8a9c49e665f548c9d7eb8d1ed6698f00b157 --- /dev/null +++ b/examples/plot_interval_stacked_grouped_transpose.py @@ -0,0 +1,38 @@ +# Plot / Interval / Stacked / Grouped / Transpose +# Make a bar #plot with both #stacked and grouped bars. #interval +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Intervals, stacked and dodged', + data=data('day type time gender', 12, rows=[ + ('Mon.','series1', 2800, 'male'), + ('Mon.','series1', 1800, 'female'), + ('Mon.','series2', 2260, 'female'), + ('Mon.','series2', 710, 'male'), + ('Tues.','series1', 1800, 'male'), + ('Tues.','series1', 290, 'female'), + ('Tues.','series2', 1300, 'female'), + ('Tues.','series2', 960, 'male'), + ('Wed.','series1', 950, 'male'), + ('Wed.','series1', 2730, 'female'), + ('Wed.','series2', 1390, 'male'), + ('Wed.','series2', 900, 'female'), + ]), + plot=ui.plot([ + ui.mark( + type='interval', + y='=day', + x='=time', + color='=type', + stack='auto', + dodge='=gender', + x_min=0 + ) + ]) +)) + +page.save() diff --git a/examples/plot_interval_stacked_transpose.py b/examples/plot_interval_stacked_transpose.py new file mode 100644 index 0000000000000000000000000000000000000000..d603146da984905159c7621b4390ff9a9a8eda66 --- /dev/null +++ b/examples/plot_interval_stacked_transpose.py @@ -0,0 +1,28 @@ +# Plot / Interval / Stacked / Transpose +# Make a #stacked bar #plot. #interval +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Intervals, stacked', + data=data('day type time', 10, rows=[ + ('Mon.','series1', 2800), + ('Mon.','series2', 2260), + ('Tues.','series1', 1800), + ('Tues.','series2', 1300), + ('Wed.','series1', 950), + ('Wed.','series2', 900), + ('Thur.','series1', 500), + ('Thur.','series2', 390), + ('Fri.','series1', 170), + ('Fri.','series2', 100), + ]), + plot=ui.plot([ + ui.mark(type='interval', y='=day', x='=time', color='=type', stack='auto', x_min=0) + ]) +)) + +page.save() diff --git a/examples/plot_interval_theta.py b/examples/plot_interval_theta.py new file mode 100644 index 0000000000000000000000000000000000000000..5a4dfb90b8ed2e3eb4aec790fe55c2581e43b110 --- /dev/null +++ b/examples/plot_interval_theta.py @@ -0,0 +1,26 @@ +# Plot / Interval / Theta +# Make a "racetrack" #plot (a bar plot in polar coordinates, transposed). #interval +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Intervals, theta', + data=data('question percent', 8, rows=[ + ('Question 1', 0.21), + ('Question 2', 0.4), + ('Question 3', 0.49), + ('Question 4', 0.52), + ('Question 5', 0.53), + ('Question 6', 0.84), + ('Question 7', 0.88), + ('Question 8', 0.9), + ]), + plot=ui.plot([ + ui.mark(coord='theta', type='interval', x='=question', y='=percent', stack='auto', y_min=0) + ]) +)) + +page.save() diff --git a/examples/plot_interval_theta_stacked.py b/examples/plot_interval_theta_stacked.py new file mode 100644 index 0000000000000000000000000000000000000000..9cc6a9195d16076c90e0a95c1591c0b8948ca4bb --- /dev/null +++ b/examples/plot_interval_theta_stacked.py @@ -0,0 +1,36 @@ +# Plot / Interval / Theta / Stacked +# Make a stacked "racetrack" #plot (a bar plot in polar coordinates, transposed). #interval +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Intervals, theta, stacked', + data=data('day type time', 10, rows=[ + ('Mon.','series1', 470), + ('Mon.','series2', 700), + ('Tues.','series1', 1800), + ('Tues.','series2', 1300), + ('Wed.','series1', 1650), + ('Wed.','series2', 1900), + ('Thur.','series1', 2500), + ('Thur.','series2', 1470), + ('Fri.','series1', 2800), + ('Fri.','series2', 2260), + ]), + plot=ui.plot([ + ui.mark( + coord='theta', + type='interval', + x='=day', + y='=time', + color='=type', + stack='auto', + y_min=0 + ) + ]) +)) + +page.save() diff --git a/examples/plot_interval_transpose.py b/examples/plot_interval_transpose.py new file mode 100644 index 0000000000000000000000000000000000000000..8150c4d823c669ca884dc19cd78905ffc43f183b --- /dev/null +++ b/examples/plot_interval_transpose.py @@ -0,0 +1,21 @@ +# Plot / Interval / Transpose +# Make a bar #plot. #interval +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Interval transposed', + data=data('profession salary', 5, rows=[ + ('medicine', 33000), + ('fire fighting', 18000), + ('pedagogy', 24000), + ('psychology', 22500), + ('computer science', 36000), + ]), + plot=ui.plot([ui.mark(type='interval', x='=salary', y='=profession', y_min=0)]) +)) + +page.save() diff --git a/examples/plot_line.py b/examples/plot_line.py new file mode 100644 index 0000000000000000000000000000000000000000..68c5a701e726213796ea6ebf5c0c624995fa2615 --- /dev/null +++ b/examples/plot_line.py @@ -0,0 +1,25 @@ +# Plot / Line +# Make a line #plot. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Line', + data=data('year value', 8, rows=[ + ('1991', 3), + ('1992', 4), + ('1993', 3.5), + ('1994', 5), + ('1995', 4.9), + ('1996', 6), + ('1997', 7), + ('1998', 9), + ('1999', 13), + ]), + plot=ui.plot([ui.mark(type='line', x_scale='time', x='=year', y='=value', y_min=0)]) +)) + +page.save() diff --git a/examples/plot_line_annotation.py b/examples/plot_line_annotation.py new file mode 100644 index 0000000000000000000000000000000000000000..e65a8ed4c6003f03d4988d224bd84595ee4cd652 --- /dev/null +++ b/examples/plot_line_annotation.py @@ -0,0 +1,34 @@ +# Plot / Line / Annotation +# Add annotations to a line #plot. #annotation +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Line, annotation', + data=data('year value', 8, rows=[ + ('1991', 3), + ('1992', 4), + ('1993', 3.5), + ('1994', 5), + ('1995', 4.9), + ('1996', 6), + ('1997', 7), + ('1998', 9), + ('1999', 13), + ]), + plot=ui.plot([ + ui.mark(type='line', x_scale='time', x='=year', y='=value', y_min=0), + ui.mark(x='1994', y=13, label='point'), + ui.mark(x='1993', label='vertical line'), + ui.mark(y=6, label='horizontal line'), + ui.mark(x='1996', x0='1995', label='vertical region'), + ui.mark(y=8, y0=7, label='horizontal region'), + ui.mark(x='1997', x0='1998', y=3, y0=4, + label='rectangular region') + ]) +)) + +page.save() \ No newline at end of file diff --git a/examples/plot_line_groups.py b/examples/plot_line_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..8f3d6940e012817612da422b00720e9b8ad32630 --- /dev/null +++ b/examples/plot_line_groups.py @@ -0,0 +1,40 @@ +# Plot / Line / Groups +# Make a multi-series line #plot. #multi_series +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Line, groups', + data=data('month city temperature', 24, rows=[ + ('Jan', 'Tokyo', 7), + ('Jan', 'London', 3.9), + ('Feb', 'Tokyo', 6.9), + ('Feb', 'London', 4.2), + ('Mar', 'Tokyo', 9.5), + ('Mar', 'London', 5.7), + ('Apr', 'Tokyo', 14.5), + ('Apr', 'London', 8.5), + ('May', 'Tokyo', 18.4), + ('May', 'London', 11.9), + ('Jun', 'Tokyo', 21.5), + ('Jun', 'London', 15.2), + ('Jul', 'Tokyo', 25.2), + ('Jul', 'London', 17), + ('Aug', 'Tokyo', 26.5), + ('Aug', 'London', 16.6), + ('Sep', 'Tokyo', 23.3), + ('Sep', 'London', 14.2), + ('Oct', 'Tokyo', 18.3), + ('Oct', 'London', 10.3), + ('Nov', 'Tokyo', 13.9), + ('Nov', 'London', 6.6), + ('Dec', 'Tokyo', 9.6), + ('Dec', 'London', 4.8), + ]), + plot=ui.plot([ui.mark(type='line', x='=month', y='=temperature', color='=city', y_min=0)]) +)) + +page.save() diff --git a/examples/plot_line_labels.py b/examples/plot_line_labels.py new file mode 100644 index 0000000000000000000000000000000000000000..4582ff0f94a437c93149b68314915475914f95a4 --- /dev/null +++ b/examples/plot_line_labels.py @@ -0,0 +1,28 @@ +# Plot / Line / Labels +# Add labels to a line #plot. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Line, labels', + data=data('year price', 9, rows=[ + ('1991', 3), + ('1992', 4), + ('1993', 3.5), + ('1994', 5), + ('1995', 4.9), + ('1996', 6), + ('1997', 7), + ('1998', 9), + ('1999', 13), + ]), + plot=ui.plot([ + ui.mark(type='line', x_scale='time', x='=year', y='=price', y_min=0, + label='=${{intl price minimum_fraction_digits=2 maximum_fraction_digits=2}}') + ]) +)) + +page.save() diff --git a/examples/plot_line_labels_custom.py b/examples/plot_line_labels_custom.py new file mode 100644 index 0000000000000000000000000000000000000000..cbc5e53318d7847cc3a7f687f93cbc3ba43eccba --- /dev/null +++ b/examples/plot_line_labels_custom.py @@ -0,0 +1,29 @@ +# Plot / Line / Labels / Custom +# Add labels to a line #plot. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Line, labels, custom', + data=data('year price', 9, rows=[ + ('1991', 3), + ('1992', 4), + ('1993', 3.5), + ('1994', 5), + ('1995', 4.9), + ('1996', 6), + ('1997', 7), + ('1998', 9), + ('1999', 13), + ]), + plot=ui.plot([ + ui.mark(type='line', x_scale='time', x='=year', y='=price', y_min=0, + label='=${{intl price minimum_fraction_digits=2 maximum_fraction_digits=2}}', + label_fill_color='rgba(0,0,0,0.65)', label_stroke_color='$red', label_stroke_size=2) + ]) +)) + +page.save() diff --git a/examples/plot_line_labels_no_overlap.py b/examples/plot_line_labels_no_overlap.py new file mode 100644 index 0000000000000000000000000000000000000000..72c3d055965148c1b66ea4b5788dbd6c8658af73 --- /dev/null +++ b/examples/plot_line_labels_no_overlap.py @@ -0,0 +1,38 @@ +# Plot / Line / Labels / Occlusion +# Make a line #plot with non-overlapping labels. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Line, remove overlaping labels', + data=data('year price', 9, rows=[ + ('1991', 3), + ('1992', 4), + ('1993', 3.5), + ('1994', 5), + ('1995', 4.9), + ('1996', 6), + ('1997', 7), + ('1998', 9), + ('1999', 11), + ('2000', 13.5), + ('2001', 14), + ('2002', 15), + ('2003', 16), + ('2004', 16.5), + ('2005', 17), + ('2006', 17.5), + ('2007', 18), + ('2008', 18.5), + ]), + plot=ui.plot([ + ui.mark(type='line', x_scale='time', x='=year', y='=price', y_min=0, + label='=${{intl price minimum_fraction_digits=2 maximum_fraction_digits=2}}', + label_overlap='hide') + ]) +)) + +page.save() diff --git a/examples/plot_line_labels_stroked.py b/examples/plot_line_labels_stroked.py new file mode 100644 index 0000000000000000000000000000000000000000..e66de6d3ebab71db113a7791c7642aef3ed49a23 --- /dev/null +++ b/examples/plot_line_labels_stroked.py @@ -0,0 +1,30 @@ +# Plot / Line / Labels / Stroked +# Customize label rendering: add a subtle outline to labels to improve readability. +# #plot +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Line, labels less messy', + data=data('year price', 9, rows=[ + ('1991', 3), + ('1992', 4), + ('1993', 3.5), + ('1994', 5), + ('1995', 4.9), + ('1996', 6), + ('1997', 7), + ('1998', 9), + ('1999', 13), + ]), + plot=ui.plot([ + ui.mark(type='line', x_scale='time', x='=year', y='=price', y_min=0, + label='=${{intl price minimum_fraction_digits=2 maximum_fraction_digits=2}}', + label_fill_color='rgba(0,0,0,0.65)', label_stroke_color='#fff', label_stroke_size=2) + ]) +)) + +page.save() diff --git a/examples/plot_line_smooth.py b/examples/plot_line_smooth.py new file mode 100644 index 0000000000000000000000000000000000000000..8c9b235828bd098db4ca13e7ba4480d1156ce6a7 --- /dev/null +++ b/examples/plot_line_smooth.py @@ -0,0 +1,30 @@ +# Plot / Line / Smooth +# Make a line #plot using a smooth curve. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Line, smooth', + data=data('month price', 12, rows=[ + ('Jan', 51), + ('Feb', 91), + ('Mar', 34), + ('Apr', 47), + ('May', 63), + ('June', 58), + ('July', 56), + ('Aug', 77), + ('Sep', 99), + ('Oct', 106), + ('Nov', 88), + ('Dec', 56), + ]), + plot=ui.plot([ + ui.mark(type='line', x='=month', y='=price', curve='smooth', y_min=0) + ]) +)) + +page.save() diff --git a/examples/plot_matplotlib.py b/examples/plot_matplotlib.py new file mode 100644 index 0000000000000000000000000000000000000000..0e33113eaa482b47b84a84219e722ecc79bfffd9 --- /dev/null +++ b/examples/plot_matplotlib.py @@ -0,0 +1,61 @@ +# Plot / Matplotlib +# Use #matplotlib to create plots. Also demonstrates how to provide live control over plots. +# #plot +# --- +import uuid +import os +import numpy as np +import matplotlib.pyplot as plt + +from h2o_wave import ui, main, app, Q + +np.random.seed(19680801) + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: # First visit + q.client.initialized = True + q.client.points = 25 + q.client.alpha = 50 + + q.page['controls'] = ui.form_card( + box='1 1 2 3', + items=[ + ui.text_xl("Lets make some plots"), + ui.slider(name='points', label='Points', min=5, max=50, step=1, value=q.client.points, trigger=True), + ui.slider(name='alpha', label='Alpha', min=5, max=100, step=1, value=q.client.alpha, trigger=True), + ] + ) + q.page['plot'] = ui.markdown_card(box='3 1 2 3', title='Your plot!', content='') + + if q.args.points is not None: + q.client.points = q.args.points + + if q.args.alpha is not None: + q.client.alpha = q.args.alpha + + n = q.client.points + + # Render plot + plt.figure(figsize=(2, 2)) + plt.scatter( + np.random.rand(n), np.random.rand(n), + s=(30 * np.random.rand(n)) ** 2, + c=np.random.rand(n), + alpha=q.client.alpha / 100.0 + ) + image_filename = f'{str(uuid.uuid4())}.png' + plt.savefig(image_filename) + + # Upload + image_path, = await q.site.upload([image_filename]) + + # Clean up + os.remove(image_filename) + + # Display our plot in our markdown card + q.page['plot'].content = f'![plot]({image_path})' + + # Save page + await q.page.save() diff --git a/examples/plot_pandas.py b/examples/plot_pandas.py new file mode 100644 index 0000000000000000000000000000000000000000..290010912d321cd537c20cd882fba9dd0f4a6448 --- /dev/null +++ b/examples/plot_pandas.py @@ -0,0 +1,51 @@ +# Plot / Pandas +# Plot Pandas dataframes. +# --- +from h2o_wave import site, data, ui +import pandas as pd +import numpy as np + +page = site['/demo'] + +n = 100 +df = pd.DataFrame(dict( + length=np.random.rand(n), + width=np.random.rand(n), + data_type=np.random.choice(a=['Train', 'Test'], size=n, p=[0.8, 0.2]) +)) + +# Plot two numeric columns by each other and color based on a third, categorical column +page['scatter'] = ui.plot_card( + box='1 1 -1 4', + title='Scatter Plot from Dataframe', + data=data( + fields=df.columns.tolist(), + rows=df.values.tolist(), + pack=True, + ), + plot=ui.plot(marks=[ui.mark( + type='point', + x='=length', x_title='Length', + y='=width', y_title='Width', + color='=data_type', shape='circle', + )]) +) + +# Aggregate the data in pandas and plot a bar chart of the average value of one column by some other column +df_agg = df.groupby(['data_type']).mean().reset_index() +page['bar'] = ui.plot_card( + box='1 5 -1 4', + title='Bar Plot from Aggregated Dataframe', + data=data( + fields=df_agg.columns.tolist(), + rows=df_agg.values.tolist(), + pack=True, + ), + plot=ui.plot(marks=[ui.mark( + type='interval', + x='=data_type', x_title='Modeling Data Type', + y='=length', y_title='Length', + )]) +) + +page.save() diff --git a/examples/plot_path.py b/examples/plot_path.py new file mode 100644 index 0000000000000000000000000000000000000000..98a87e5a6314f4c38aedd7ea61de96aae44e9300 --- /dev/null +++ b/examples/plot_path.py @@ -0,0 +1,26 @@ +# Plot / Path +# Make a path #plot. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 5 5', + title='Path', + data=data('price performance', 10, rows=[ + (0.1, 0.6), + (0.2, 0.5), + (0.3, 0.3), + (0.4, 0.2), + (0.4, 0.5), + (0.2, 0.2), + (0.8, 0.5), + (0.3, 0.3), + (0.2, 0.4), + (0.1, 0.0), + ]), + plot=ui.plot([ui.mark(type='path', x='=price', y='=performance')]) +)) + +page.save() diff --git a/examples/plot_path_point.py b/examples/plot_path_point.py new file mode 100644 index 0000000000000000000000000000000000000000..4c388d752d088b1f13fbdd4b0689b6ce665b63ca --- /dev/null +++ b/examples/plot_path_point.py @@ -0,0 +1,29 @@ +# Plot / Path / Point +# Make a path #plot with an additional layer of points. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Path + Point', + data=data('price performance', 10, rows=[ + (0.1, 0.6), + (0.2, 0.5), + (0.3, 0.3), + (0.4, 0.2), + (0.4, 0.5), + (0.2, 0.2), + (0.8, 0.5), + (0.3, 0.3), + (0.2, 0.4), + (0.1, 0.0), + ]), + plot=ui.plot([ + ui.mark(type='path', x='=price', y='=performance'), + ui.mark(type='point', x='=price', y='=performance'), + ]) +)) + +page.save() diff --git a/examples/plot_path_smooth.py b/examples/plot_path_smooth.py new file mode 100644 index 0000000000000000000000000000000000000000..fe34c35e79cd91259b07757f8c1b27fa0c5b855f --- /dev/null +++ b/examples/plot_path_smooth.py @@ -0,0 +1,26 @@ +# Plot / Path / Smooth +# Make a path #plot with a smooth curve. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Path, smooth', + data=data('price performance', 10, rows=[ + (0.1, 0.6), + (0.2, 0.5), + (0.3, 0.3), + (0.4, 0.2), + (0.4, 0.5), + (0.2, 0.2), + (0.8, 0.5), + (0.3, 0.3), + (0.2, 0.4), + (0.1, 0.0), + ]), + plot=ui.plot([ui.mark(type='path', x='=price', y='=performance', curve='smooth')]) +)) + +page.save() diff --git a/examples/plot_plotly.py b/examples/plot_plotly.py new file mode 100644 index 0000000000000000000000000000000000000000..81865ecb4658bf6e146ffde83a86dd4981609506 --- /dev/null +++ b/examples/plot_plotly.py @@ -0,0 +1,61 @@ +# Plot / Plotly +# Use #plotly to create plots. Also demonstrates how to provide live control over plots. +# #plot +# --- + +import numpy as np +from plotly import graph_objects as go +from plotly import io as pio + +from h2o_wave import ui, main, app, Q + +np.random.seed(19680801) + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: # First visit + q.client.initialized = True + q.client.points = 25 + q.client.plotly_controls = False + + q.page['controls'] = ui.form_card( + box='1 1 4 2', + items=[ + ui.slider(name='points', label='Points', min=5, max=50, step=1, value=q.client.points, trigger=True), + ui.toggle(name='plotly_controls', label='Plotly Controls', trigger=True), + ] + ) + q.page['plot'] = ui.frame_card(box='1 3 4 5', title='', content='') + + if q.args.points is not None: + q.client.points = q.args.points + + if q.args.plotly_controls is not None: + q.client.plotly_controls = q.args.plotly_controls + + n = q.client.points + + # Create plot with plotly + fig = go.Figure(data=go.Scatter( + x=np.random.rand(n), + y=np.random.rand(n), + mode='markers', + marker=dict(size=(8 * np.random.rand(n)) ** 2, + color=np.random.rand(n)), + opacity=0.8, + )) + _ = fig.update_layout( + margin=dict(l=10, r=10, t=10, b=10), + paper_bgcolor='rgb(255, 255, 255)', + plot_bgcolor='rgb(255, 255, 255)', + ) + config = {'scrollZoom': q.client.plotly_controls, + 'showLink': q.client.plotly_controls, + 'displayModeBar': q.client.plotly_controls} + html = pio.to_html(fig, validate=False, include_plotlyjs='cdn', config=config) + + q.page['plot'].content = html + + # Save page + await q.page.save() diff --git a/examples/plot_point.py b/examples/plot_point.py new file mode 100644 index 0000000000000000000000000000000000000000..0b94ee11190d1e1dfb9ce86602acd645beb76c71 --- /dev/null +++ b/examples/plot_point.py @@ -0,0 +1,26 @@ +# Plot / Point +# Make a scatterplot. #plot +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Point', + data=data('height weight', 10, rows=[ + (170, 59), + (159.1, 47.6), + (166, 69.8), + (176.2, 66.8), + (160.2, 75.2), + (180.3, 76.4), + (164.5, 63.2), + (173, 60.9), + (183.5, 74.8), + (175.5, 70), + ]), + plot=ui.plot([ui.mark(type='point', x='=weight', y='=height')]) +)) + +page.save() diff --git a/examples/plot_point_annotation.py b/examples/plot_point_annotation.py new file mode 100644 index 0000000000000000000000000000000000000000..b6d40d6bd89cf2568669b9b26703677c20fa8472 --- /dev/null +++ b/examples/plot_point_annotation.py @@ -0,0 +1,35 @@ +# Plot / Point / Annotation +# Add annotations (points, lines and regions) to a #plot. +# #annotation +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Numeric-Numeric', + data=data('height weight', 10, rows=[ + (170, 59), + (159.1, 47.6), + (166, 69.8), + (176.2, 66.8), + (160.2, 75.2), + (180.3, 76.4), + (164.5, 63.2), + (173, 60.9), + (183.5, 74.8), + (175.5, 70), + ]), + plot=ui.plot([ + ui.mark(type='point', x='=weight', y='=height', x_min=0, x_max=100, y_min=0, y_max=100), # the plot + ui.mark(x=50, y=50, label='point'), # A single reference point + ui.mark(x=40, label='vertical line'), + ui.mark(y=40, label='horizontal line'), + ui.mark(x=70, x0=60, label='vertical region'), + ui.mark(y=70, y0=60, label='horizontal region'), + ui.mark(x=30, x0=20, y=30, y0=20, label='rectangular region') + ]) +)) + +page.save() diff --git a/examples/plot_point_custom.py b/examples/plot_point_custom.py new file mode 100644 index 0000000000000000000000000000000000000000..90522cadb2c90f09c9df92ec4eb6a4013ec95d5c --- /dev/null +++ b/examples/plot_point_custom.py @@ -0,0 +1,31 @@ +# Plot / Point / Custom +# Customize a plot's fill/stroke color, size and opacity. +# #plot +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Point, custom', + data=data('lifeExpectancy GDP population', 10, rows=[ + (75.32, 12779.37964, 40301927), + (72.39, 9065.800825, 190010647), + (80.653, 36319.23501, 33390141), + (78.273, 8948.102923, 11416987), + (72.961, 4959.114854, 1318683096), + (82.208, 39724.97867, 6980412), + (82.603, 31656.06806, 127467972), + (76.423, 5937.029526, 3600523), + (79.829, 36126.4927, 8199783), + (79.441, 33692.60508, 10392226), + (81.235, 34435.36744, 20434176), + (80.204, 25185.00911, 4115771) + ]), + plot=ui.plot([ui.mark(type='point', x='=GDP', y='=lifeExpectancy', size='=population', size_range='4 30', + fill_color='#eb4559', stroke_color='#eb4559', stroke_size=1, fill_opacity=0.3, + stroke_opacity=1)]) +)) + +page.save() diff --git a/examples/plot_point_groups.py b/examples/plot_point_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..de43f5d376f6b846573c8eb9ff06ae1f43423232 --- /dev/null +++ b/examples/plot_point_groups.py @@ -0,0 +1,27 @@ +# Plot / Point / Groups +# Make a scatterplot with categories encoded as colors. +# #plot +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Point, groups', + data=data('gender height weight', 10, rows=[ + ('female', 170, 59), + ('female', 159.1, 47.6), + ('female', 166, 69.8), + ('female', 176.2, 66.8), + ('female', 160.2, 75.2), + ('male', 180.3, 76.4), + ('male', 164.5, 63.2), + ('male', 173, 60.9), + ('male', 183.5, 74.8), + ('male', 175.5, 70), + ]), + plot=ui.plot([ui.mark(type='point', x='=weight', y='=height', color='=gender', shape='circle')]) +)) + +page.save() diff --git a/examples/plot_point_map.py b/examples/plot_point_map.py new file mode 100644 index 0000000000000000000000000000000000000000..3d717eee1a2f4f9e51c4b4129995897a3bac6c90 --- /dev/null +++ b/examples/plot_point_map.py @@ -0,0 +1,30 @@ +# Plot / Point / Map +# Make a #plot to compare quantities across categories. Similar to a heatmap, +# but using size-encoding instead of color-encoding. +# #map +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Points, size-encoded', + data=data('year person sales', 10, rows=[ + ('2021', 'Joe', 10), + ('2022', 'Jane', 58), + ('2023', 'Ann', 114), + ('2021', 'Tim', 31), + ('2023', 'Joe', 96), + ('2021', 'Jane', 55), + ('2023', 'Jane', 5), + ('2022', 'Tim', 85), + ('2023', 'Tim', 132), + ('2022', 'Joe', 54), + ('2021', 'Ann', 78), + ('2022', 'Ann', 18), + ]), + plot=ui.plot([ui.mark(type='point', x='=person', y='=year', size='=sales', shape='circle')]) +)) + +page.save() diff --git a/examples/plot_point_shapes.py b/examples/plot_point_shapes.py new file mode 100644 index 0000000000000000000000000000000000000000..46db840d0b55d66da7295040ed89f91535625a67 --- /dev/null +++ b/examples/plot_point_shapes.py @@ -0,0 +1,27 @@ +# Plot / Point / Shapes +# Make a scatterplot with categories encoded as mark shapes. +# #plot +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Point, shapes', + data=data('gender height weight', 10, rows=[ + ('female', 170, 59), + ('female', 159.1, 47.6), + ('female', 166, 69.8), + ('female', 176.2, 66.8), + ('female', 160.2, 75.2), + ('male', 180.3, 76.4), + ('male', 164.5, 63.2), + ('male', 173, 60.9), + ('male', 183.5, 74.8), + ('male', 175.5, 70), + ]), + plot=ui.plot([ui.mark(type='point', x='=weight', y='=height', shape='=gender')]) +)) + +page.save() diff --git a/examples/plot_point_sizes.py b/examples/plot_point_sizes.py new file mode 100644 index 0000000000000000000000000000000000000000..9343ac0b2783fe59aadf2af7305ae33987d9eaaf --- /dev/null +++ b/examples/plot_point_sizes.py @@ -0,0 +1,29 @@ +# Plot / Point / Sizes +# Make a scatterplot with mark sizes mapped to a continuous variable (a "bubble plot"). +# #plot +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Point, sized', + data=data('lifeExpectancy GDP population', 10, rows=[ + (75.32, 12779.37964, 40301927), + (72.39, 9065.800825, 190010647), + (80.653, 36319.23501, 33390141), + (78.273, 8948.102923, 11416987), + (72.961, 4959.114854, 1318683096), + (82.208, 39724.97867, 6980412), + (82.603, 31656.06806, 127467972), + (76.423, 5937.029526, 3600523), + (79.829, 36126.4927, 8199783), + (79.441, 33692.60508, 10392226), + (81.235, 34435.36744, 20434176), + (80.204, 25185.00911, 4115771) + ]), + plot=ui.plot([ui.mark(type='point', x='=GDP', y='=lifeExpectancy', size='=population')]) +)) + +page.save() diff --git a/examples/plot_polygon.py b/examples/plot_polygon.py new file mode 100644 index 0000000000000000000000000000000000000000..486ead93f157484e4054c8b567e3a18c906bf70f --- /dev/null +++ b/examples/plot_polygon.py @@ -0,0 +1,29 @@ +# Plot / Polygon +# Make a heatmap. #plot +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Heatmap', + data=data('year person sales', 10, rows=[ + ('2021', 'Joe', 10), + ('2022', 'Jane', 58), + ('2023', 'Ann', 114), + ('2021', 'Tim', 31), + ('2023', 'Joe', 96), + ('2021', 'Jane', 55), + ('2023', 'Jane', 5), + ('2022', 'Tim', 85), + ('2023', 'Tim', 132), + ('2022', 'Joe', 54), + ('2021', 'Ann', 78), + ('2022', 'Ann', 18), + ]), + plot=ui.plot([ui.mark(type='polygon', x='=person', y='=year', color='=sales', + color_range='#fee8c8 #fdbb84 #e34a33')]) +)) + +page.save() diff --git a/examples/plot_schema.py b/examples/plot_schema.py new file mode 100644 index 0000000000000000000000000000000000000000..5abc9609699989e1e580340aea953651e44b1595 --- /dev/null +++ b/examples/plot_schema.py @@ -0,0 +1,37 @@ +# Plot / Schema +# Make a box and whiskers plot. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 -1 -1', + title='Box plot', + data=data( + fields=['region', 'low', 'q1', 'q2', 'q3', 'high'], + rows=[ + ['Oceania', 1, 9, 16, 22, 24], + ['East Europe', 1, 5, 8, 12, 16], + ['Australia', 1, 8, 12, 19, 26], + ['South America', 2, 8, 12, 21, 28], + ['North Africa', 1, 8, 14, 18, 24], + ['North America', 3, 10, 17, 28, 30], + ['West Europe', 1, 7, 10, 17, 22], + ['West Africa', 1, 6, 8, 13, 16], + ], + pack=True, + ), + plot=ui.plot([ui.mark( + type='schema', + x='=region', + y1='=low', # min + y_q1='=q1', # lower quartile + y_q2='=q2', # median + y_q3='=q3', # upper quartile + y2='=high', # max + fill_color='#ccf5ff', + )]) +)) + +page.save() diff --git a/examples/plot_schema_transpose.py b/examples/plot_schema_transpose.py new file mode 100644 index 0000000000000000000000000000000000000000..eee2294c3173923bde1f3a4205b4b044ec16e45c --- /dev/null +++ b/examples/plot_schema_transpose.py @@ -0,0 +1,37 @@ +# Plot / Schema / Transpose +# Make a horizontal box and whiskers plot. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Box plot', + data=data( + fields=['region', 'low', 'q1', 'q2', 'q3', 'high'], + rows=[ + ['Oceania', 1, 9, 16, 22, 24], + ['East Europe', 1, 5, 8, 12, 16], + ['Australia', 1, 8, 12, 19, 26], + ['South America', 2, 8, 12, 21, 28], + ['North Africa', 1, 8, 14, 18, 24], + ['North America', 3, 10, 17, 28, 30], + ['West Europe', 1, 7, 10, 17, 22], + ['West Africa', 1, 6, 8, 13, 16], + ], + pack=True, + ), + plot=ui.plot([ui.mark( + type='schema', + x1='=low', # min + x_q1='=q1', # lower quartile + x_q2='=q2', # median + x_q3='=q3', # upper quartile + x2='=high', # max + y='=region', + fill_color='#ccf5ff', + )]) +)) + +page.save() diff --git a/examples/plot_step.py b/examples/plot_step.py new file mode 100644 index 0000000000000000000000000000000000000000..eb03af730f028b9dfa4ce30835ed9423631b3258 --- /dev/null +++ b/examples/plot_step.py @@ -0,0 +1,30 @@ +# Plot / Line / Step +# Make a line #plot with a step curve. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Line, step', + data=data('month price', 12, rows=[ + ('Jan', 51), + ('Feb', 91), + ('Mar', 34), + ('Apr', 47), + ('May', 63), + ('June', 58), + ('July', 56), + ('Aug', 77), + ('Sep', 99), + ('Oct', 106), + ('Nov', 88), + ('Dec', 56), + ]), + plot=ui.plot([ + ui.mark(type='line', x='=month', y='=price', curve='step', y_min=0) + ]) +)) + +page.save() diff --git a/examples/plot_step_after.py b/examples/plot_step_after.py new file mode 100644 index 0000000000000000000000000000000000000000..c5384061bd7bc96f092913ec93d03e3b9530cde8 --- /dev/null +++ b/examples/plot_step_after.py @@ -0,0 +1,30 @@ +# Plot / Line / Step / After +# Make a line #plot with a step-after curve. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Line, step-after', + data=data('month price', 12, rows=[ + ('Jan', 51), + ('Feb', 91), + ('Mar', 34), + ('Apr', 47), + ('May', 63), + ('June', 58), + ('July', 56), + ('Aug', 77), + ('Sep', 99), + ('Oct', 106), + ('Nov', 88), + ('Dec', 56), + ]), + plot=ui.plot([ + ui.mark(type='line', x='=month', y='=price', curve='step-after', y_min=0) + ]) +)) + +page.save() diff --git a/examples/plot_step_before.py b/examples/plot_step_before.py new file mode 100644 index 0000000000000000000000000000000000000000..4e88b8050036e58563b8588ed94bfb8dd0c6167d --- /dev/null +++ b/examples/plot_step_before.py @@ -0,0 +1,30 @@ +# Plot / Line / Step / Before +# Make a line #plot with a step-before curve. +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +page.add('example', ui.plot_card( + box='1 1 4 5', + title='Line, step before', + data=data('month price', 12, rows=[ + ('Jan', 51), + ('Feb', 91), + ('Mar', 34), + ('Apr', 47), + ('May', 63), + ('June', 58), + ('July', 56), + ('Aug', 77), + ('Sep', 99), + ('Oct', 106), + ('Nov', 88), + ('Dec', 56), + ]), + plot=ui.plot([ + ui.mark(type='line', x='=month', y='=price', curve='step-before', y_min=0) + ]) +)) + +page.save() diff --git a/examples/plot_theme.py b/examples/plot_theme.py new file mode 100644 index 0000000000000000000000000000000000000000..227090b88a134309fb8841cc126ee4b7ae5aa209 --- /dev/null +++ b/examples/plot_theme.py @@ -0,0 +1,432 @@ +# Plot / Theme +# Themeable #plot showcase. +# --- +import random + +from .synth import FakeTimeSeries, FakeMultiTimeSeries, FakeCategoricalSeries, FakeMultiCategoricalSeries, FakeScatter +from h2o_wave import main, app, data, Q, ui + + +n = 10 +k = 3 +f = FakeTimeSeries() +f_multi = FakeMultiTimeSeries() +f_cat = FakeCategoricalSeries() +f_cat_multi = FakeMultiCategoricalSeries(groups=k) +f_scat = FakeScatter() + +zones = [ + ui.zone(name='title', size='60px'), + ui.zone(name='plots', direction=ui.ZoneDirection.ROW, wrap='start', justify='center'), +] + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['meta'] = ui.meta_card(box='', layouts=[ + ui.layout(breakpoint='xs', zones=zones), + ui.layout(breakpoint='m', zones=zones), + ui.layout(breakpoint='xl', zones=zones), + ]) + + q.client.active_theme = 'default' + q.page['title'] = ui.section_card( + box='title', + title='Plot theme demo', + subtitle='Toggle theme to see default plot colors change!', + items=[ui.toggle(name='toggle_theme', label='Dark theme', trigger=True)], + ) + + v = q.page.add('point-sized', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Point, sized', + data=data('price performance discount', n), + plot=ui.plot([ui.mark(type='point', x='=price', y='=performance', size='=discount')]) + )) + v.data = [(x, y, random.randint(1, 10)) for x, y in [f_scat.next() for _ in range(n)]] + + v = q.page.add('point-shapes', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Point, shapes', + data=data('product price performance', n * 2), + plot=ui.plot([ui.mark(type='point', x='=price', y='=performance', shape='=product', shape_range='circle square')]) + )) + v.data = [('G1', x, y) for x, y in [f_scat.next() for _ in range(n)]] + [('G2', x, y) for x, y in [f_scat.next() for _ in range(n)]] + + k1, k2 = 20, 10 + v = q.page.add('poit-size', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Points, size-encoded', + data=data('country product profit', k1 * k2), + plot=ui.plot([ui.mark(type='point', x='=country', y='=product', size='=profit', shape='circle')]) + )) + rows = [] + for i in range(k1): + for j in range(k2): + x = f.next() + rows.append((f'A{i + 1}', f'B{j + 1}', x)) + v.data = rows + + v = q.page.add('area', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Area', + data=data('date price', n), + plot=ui.plot([ui.mark(type='area', x_scale='time', x='=date', y='=price', y_min=0)]) + )) + v.data = [(t, x) for t, x, dx in [f.next() for _ in range(n)]] + + v = q.page.add('area-line', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Area + Line', + data=data('date price', n), + plot=ui.plot([ + ui.mark(type='area', x_scale='time', x='=date', y='=price', y_min=0), + ui.mark(type='line', x='=date', y='=price') + ]) + )) + v.data = [(t, x) for t, x, dx in [f.next() for _ in range(n)]] + + v = q.page.add('area-line-smooth', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Area + Line, smooth', + data=data('date price', n), + plot=ui.plot([ + ui.mark(type='area', x_scale='time', x='=date', y='=price', curve='smooth', y_min=0), + ui.mark(type='line', x='=date', y='=price', curve='smooth') + ]) + )) + v.data = [(t, x) for t, x, dx in [f.next() for _ in range(n)]] + + v = q.page.add('area-line-groups', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Area + Line, groups', + data=data('product date price', n * 5), + plot=ui.plot([ + ui.mark(type='area', x_scale='time', x='=date', y='=price', color='=product', y_min=0), + ui.mark(type='line', x='=date', y='=price', color='=product') + ]) + )) + v.data = [(g, t, x) for x in [f_multi.next() for _ in range(n)] for g, t, x, dx in x] + + v = q.page.add('area-groups', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Area, groups', + data=data('product date price', n * 5), + plot=ui.plot([ui.mark(type='area', x_scale='time', x='=date', y='=price', color='=product', y_min=0)]) + )) + v.data = [(g, t, x) for x in [f_multi.next() for _ in range(n)] for g, t, x, dx in x] + + v = q.page.add('area-stacked', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Area, stacked', + data=data('product date price', n * 5), + plot=ui.plot( + [ui.mark(type='area', x_scale='time', x='=date', y='=price', color='=product', stack='auto', y_min=0)]) + )) + v.data = [(g, t, x) for x in [f_multi.next() for _ in range(n)] for g, t, x, dx in x] + + f_negative = FakeTimeSeries(min=-50, max=50, start=0) + v = q.page.add('area-negative-values', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Area, negative values', + data=data('date price', n), + plot=ui.plot([ui.mark(type='area', x_scale='time', x='=date', y='=price')]) + )) + v.data = [(t, x) for t, x, dx in [f_negative.next() for _ in range(n)]] + + v = q.page.add('area-range', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Area, range', + data=data('date low high', n), + plot=ui.plot([ui.mark(type='area', x_scale='time', x='=date', y0='=low', y='=high')]) + )) + v.data = [(t, x - random.randint(3, 8), x + random.randint(3, 8)) for t, x, dx in [f.next() for _ in range(n)]] + + v = q.page.add('area-smooth', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Area, smooth', + data=data('date price', n), + plot=ui.plot([ui.mark(type='area', x_scale='time', x='=date', y='=price', curve='smooth', y_min=0)]) + )) + v.data = [(t, x) for t, x, dx in [f.next() for _ in range(n)]] + + v = q.page.add('example', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Label Customization', + data=data('product price', n), + plot=ui.plot([ + ui.mark(type='interval', x='=product', + y='=${{intl price minimum_fraction_digits=2 maximum_fraction_digits=2}}', y_min=0, + label='=${{intl price minimum_fraction_digits=2 maximum_fraction_digits=2}}', + label_offset=0, label_position='middle', label_rotation='-90', label_fill_color='#fff', + label_font_weight='bold')]) + )) + v.data = [(c, x) for c, x, dx in [f_cat.next() for _ in range(n)]] + + v = q.page.add('interval', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Interval', + data=data('product price', n), + plot=ui.plot([ui.mark(type='interval', x='=product', y='=price', y_min=0)]) + )) + v.data = [(c, x) for c, x, dx in [f_cat.next() for _ in range(n)]] + + v = q.page.add('interval-annotation', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Categorical-Numeric', + data=data('product price', n), + plot=ui.plot([ + ui.mark(type='interval', x='=product', y='=price', y_min=0, y_max=100), + ui.mark(x='C20', y=80, label='point'), + ui.mark(x='C23', label='vertical line'), + ui.mark(y=40, label='horizontal line'), + ui.mark(x='C26', x0='C23', label='vertical region'), + ui.mark(y=70, y0=60, label='horizontal region') + ]) + )) + v.data = [(c, x) for c, x, dx in [f_cat.next() for _ in range(n)]] + + v = q.page.add('histogram', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Histogram', + data=data('lo hi price', n), + plot=ui.plot([ui.mark(type='interval', y='=price', x1='=lo', x2='=hi', y_min=0)]) + )) + v.data = [(i * 10, i * 10 + 10, x) for i, (c, x, dx) in enumerate([f_cat.next() for _ in range(n)])] + + v = q.page.add('interval-range', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Histogram', + data=data('lo hi price', n), + plot=ui.plot([ui.mark(type='interval', y='=price', x1='=lo', x2='=hi', y_min=0)]) + )) + v.data = [(i * 10, i * 10 + 10, x) for i, (c, x, dx) in enumerate([f_cat.next() for _ in range(n)])] + + v = q.page.add('interval-range', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='35%'), + ), + title='Interval, range', + data=data('product low high', 3), + plot=ui.plot([ui.mark(type='interval', x='=product', y0='=low', y='=high')]) + )) + v.data = [(c, x - random.randint(3, 10), x + random.randint(3, 10)) for c, x, _ in [f.next() for _ in range(3)]] + + v = q.page.add('interval-transpose', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='29%'), + ), + title='Categorical-Numeric', + data=data('product price', 20), + plot=ui.plot([ + ui.mark(type='interval', y='=product', x='=price', x_min=0, x_max=100), + ]) + )) + v.data = [(c, x) for c, x, dx in [f_cat.next() for _ in range(20)]] + + v = q.page.add('intervals-theta-stacked', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Intervals, theta, stacked', + data=data('country product price', n * k), + plot=ui.plot([ + ui.mark(coord='theta', type='interval', x='=product', y='=price', color='=country', stack='auto', y_min=0) + ]) + )) + v.data = [(g, t, x) for x in [f_cat_multi.next() for _ in range(n)] for g, t, x, dx in x] + + v = q.page.add('interval-helix', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Interval, helix', + data=data('product price', 60), + plot=ui.plot([ui.mark(coord='helix', type='interval', x='=product', y='=price', y_min=0)]) + )) + v.data = [(c, x) for c, x, dx in [f_cat.next() for _ in range(60)]] + + v = q.page.add('interval-polar', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Interval, polar', + data=data('product price', n), + plot=ui.plot([ui.mark(coord='polar', type='interval', x='=product', y='=price', y_min=0)]) + )) + v.data = [(c, x) for c, x, dx in [f_cat.next() for _ in range(n)]] + + v = q.page.add('intervals-groups', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Intervals, groups', + data=data('country product price', n * k), + plot=ui.plot([ui.mark(type='interval', x='=product', y='=price', color='=country', dodge='auto', y_min=0)]) + )) + v.data = [(g, t, x) for x in [f_cat_multi.next() for _ in range(n)] for g, t, x, dx in x] + + v = q.page.add('intervals-stacked', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Intervals, stacked', + data=data('country product price', n * k), + plot=ui.plot([ui.mark(type='interval', x='=product', y='=price', color='=country', stack='auto', y_min=0)]) + )) + v.data = [(g, t, x) for x in [f_cat_multi.next() for _ in range(n)] for g, t, x, dx in x] + + v = q.page.add('point-annotation', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Numeric-Numeric', + data=data('price performance', n), + plot=ui.plot([ + ui.mark(type='point', x='=price', y='=performance', x_min=0, x_max=100, y_min=0, y_max=100), # the plot + ui.mark(x=50, y=50, label='point'), # A single reference point + ui.mark(x=40, label='vertical line'), + ui.mark(y=40, label='horizontal line'), + ui.mark(x=70, x0=60, label='vertical region'), + ui.mark(y=70, y0=60, label='horizontal region'), + ui.mark(x=30, x0=20, y=30, y0=20, label='rectangular region') + ]) + )) + v.data = [f_scat.next() for _ in range(n)] + + v = q.page.add('path', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Path', + data=data('profit sales', n), + plot=ui.plot([ui.mark(type='path', x='=profit', y='=sales')]) + )) + v.data = [(x, y) for x, y in [f_scat.next() for _ in range(n)]] + + v = q.page.add('step', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Line, step', + data=data('date price', n), + plot=ui.plot([ui.mark(type='line', x_scale='time', x='=date', y='=price', curve='step', y_min=0)]) + )) + v.data = [(t, x) for t, x, dx in [f.next() for _ in range(n)]] + + v = q.page.add('line-annotation', ui.plot_card( + box=ui.boxes( + ui.box('plots', width='100%'), + ui.box('plots', width='48%'), + ui.box('plots', width='32%'), + ), + title='Time-Numeric', + data=data('date price', n), + plot=ui.plot([ + ui.mark(type='line', x_scale='time', x='=date', y='=price', y_min=0, y_max=100), + ui.mark(x=50, y=50, label='point'), + ui.mark(x='2010-05-15T19:59:21.000000Z', label='vertical line'), + ui.mark(y=40, label='horizontal line'), + ui.mark(x='2010-05-24T19:59:21.000000Z', x0='2010-05-20T19:59:21.000000Z', label='vertical region'), + ui.mark(y=70, y0=60, label='horizontal region'), + ui.mark(x='2010-05-10T19:59:21.000000Z', x0='2010-05-05T19:59:21.000000Z', y=30, y0=20, + label='rectangular region') + ]) + )) + v.data = [(t, x) for t, x, dx in [f.next() for _ in range(n)]] + + q.client.initialized = True + + if q.args.toggle_theme is not None: + q.client.active_theme = 'neon' if q.args.toggle_theme else 'default' + q.page['meta'].theme = q.client.active_theme + q.page['title'].toggle_theme.value = q.client.active_theme == 'neon' + await q.page.save() diff --git a/examples/plot_vega.py b/examples/plot_vega.py new file mode 100644 index 0000000000000000000000000000000000000000..138c37b6ed3604039e7632bd95e61fb4b1b75ab5 --- /dev/null +++ b/examples/plot_vega.py @@ -0,0 +1,114 @@ +# Plot / Vega +# Make a #plot using Vega. #vega +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +spec = ''' +{ + "$schema": "https://vega.github.io/schema/vega/v5.json", + "description": "A basic bar chart example, with value labels shown upon mouse hover.", + "width": 500, + "height": 250, + "padding": 5, + + "data": [ + { + "name": "table", + "values": [ + {"category": "A", "amount": 28}, + {"category": "B", "amount": 55}, + {"category": "C", "amount": 43}, + {"category": "D", "amount": 91}, + {"category": "E", "amount": 81}, + {"category": "F", "amount": 53}, + {"category": "G", "amount": 19}, + {"category": "H", "amount": 87} + ] + } + ], + + "signals": [ + { + "name": "tooltip", + "value": {}, + "on": [ + {"events": "rect:mouseover", "update": "datum"}, + {"events": "rect:mouseout", "update": "{}"} + ] + } + ], + + "scales": [ + { + "name": "xscale", + "type": "band", + "domain": {"data": "table", "field": "category"}, + "range": "width", + "padding": 0.05, + "round": true + }, + { + "name": "yscale", + "domain": {"data": "table", "field": "amount"}, + "nice": true, + "range": "height" + } + ], + + "axes": [ + { "orient": "bottom", "scale": "xscale" }, + { "orient": "left", "scale": "yscale" } + ], + + "marks": [ + { + "type": "rect", + "from": {"data":"table"}, + "encode": { + "enter": { + "x": {"scale": "xscale", "field": "category"}, + "width": {"scale": "xscale", "band": 1}, + "y": {"scale": "yscale", "field": "amount"}, + "y2": {"scale": "yscale", "value": 0} + }, + "update": { + "fill": {"value": "steelblue"} + }, + "hover": { + "fill": {"value": "red"} + } + } + }, + { + "type": "text", + "encode": { + "enter": { + "align": {"value": "center"}, + "baseline": {"value": "bottom"}, + "fill": {"value": "#333"} + }, + "update": { + "x": {"scale": "xscale", "signal": "tooltip.category", "band": 0.5}, + "y": {"scale": "yscale", "signal": "tooltip.amount", "offset": -2}, + "text": {"signal": "tooltip.amount"}, + "fillOpacity": [ + {"test": "datum === tooltip", "value": 0}, + {"value": 1} + ] + } + } + } + ] +} +''' + +page.add('vega', ui.vega_card( + box='1 1 4 4', + title='Full Vega spec grammar', + specification=spec, + grammar='vega', +)) + +page.save() diff --git a/examples/plot_vegalite.py b/examples/plot_vegalite.py new file mode 100644 index 0000000000000000000000000000000000000000..27fc04ed65e1ca9ef9dd9af224d498a90430513a --- /dev/null +++ b/examples/plot_vegalite.py @@ -0,0 +1,59 @@ +# Plot / Vega-lite +# Make a #plot using Vega-lite. #vega +# --- +from h2o_wave import site, data, ui + +page = site['/demo'] + +spec1 = ''' +{ + "description": "A simple bar plot with embedded data.", + "data": { + "values": [ + {"a": "A","b": 28}, {"a": "B","b": 55}, {"a": "C","b": 43}, + {"a": "D","b": 91}, {"a": "E","b": 81}, {"a": "F","b": 53}, + {"a": "G","b": 19}, {"a": "H","b": 87}, {"a": "I","b": 52} + ] + }, + "mark": "bar", + "encoding": { + "x": {"field": "a", "type": "ordinal"}, + "y": {"field": "b", "type": "quantitative"} + } +} +''' + +page.add('embedded', ui.vega_card( + box='1 1 2 4', + title='Plot with embedded data', + specification=spec1, +)) + +# The following produces the same plot as above, but separates the +# Vega-lite spec from the data. This allows you to create a plot once +# and update data multiple times. +spec2 = ''' +{ + "description": "A simple bar plot with embedded data.", + "mark": "bar", + "encoding": { + "x": {"field": "a", "type": "ordinal"}, + "y": {"field": "b", "type": "quantitative"} + } +} +''' + +data2 = data(fields=["a", "b"], rows=[ + ["A", 28], ["B", 55], ["C", 43], + ["D", 91], ["E", 81], ["F", 53], + ["G", 19], ["H", 87], ["I", 52] +]) + +page.add('external', ui.vega_card( + box='1 5 2 4', + title='Plot with external data', + specification=spec2, + data=data2, +)) + +page.save() diff --git a/examples/plot_vegalite_flex.py b/examples/plot_vegalite_flex.py new file mode 100644 index 0000000000000000000000000000000000000000..49082b696991b6bb2345160bb039c948128ba49d --- /dev/null +++ b/examples/plot_vegalite_flex.py @@ -0,0 +1,75 @@ +# Plot / Vega-lite / Flex +# Place Vegalite plots in a flexible layout. +# --- +from h2o_wave import site, ui, data + +page = site['/demo'] +page.drop() + +page['meta'] = ui.meta_card(box='', layouts=[ + ui.layout( + breakpoint='xs', + width='100%', + zones=[ + ui.zone('top', size='350px', direction=ui.ZoneDirection.ROW, zones=[ + ui.zone('top_left', size='75%'), + ui.zone('top_right', size='1'), + ]), + ui.zone('bottom', size='350px', direction=ui.ZoneDirection.ROW, zones=[ + ui.zone('bottom_left', size='1'), + ui.zone('bottom_center', size='2'), + ui.zone('bottom_right', size='3'), + ]), + ] + ) +]) + +plot_spec = ''' +{ + "description": "A simple bar plot with embedded data.", + "mark": "bar", + "encoding": { + "x": {"field": "a", "type": "ordinal"}, + "y": {"field": "b", "type": "quantitative"} + } +} +''' + +plot_data = data(fields=["a", "b"], rows=[ + ["A", 28], ["B", 55], ["C", 43], + ["D", 91], ["E", 81], ["F", 53], + ["G", 19], ["H", 87], ["I", 52] +], pack=True) + +page.add('top_left', ui.vega_card( + box='top_left', + title='Plot', + specification=plot_spec, + data=plot_data, +)) +page.add('top_right', ui.vega_card( + box='top_right', + title='Plot', + specification=plot_spec, + data=plot_data, +)) +page.add('bottom_left', ui.vega_card( + box='bottom_left', + title='Plot', + specification=plot_spec, + data=plot_data, +)) +page.add('bottom_center', ui.vega_card( + box='bottom_center', + title='Plot', + specification=plot_spec, + data=plot_data, +)) +page.add('bottom_right', ui.vega_card( + box='bottom_right', + title='Plot', + specification=plot_spec, + data=plot_data, +)) + +page.save() diff --git a/examples/plot_vegalite_form.py b/examples/plot_vegalite_form.py new file mode 100644 index 0000000000000000000000000000000000000000..414b2c9d190021b9c1533972ef242c4305fbd5d1 --- /dev/null +++ b/examples/plot_vegalite_form.py @@ -0,0 +1,58 @@ +# Plot / Vega-lite / Form +# Display a Vega-lite #plot inside a #form card. #vega +# --- +from h2o_wave import site, data, ui +import random +import time + +page = site['/demo'] + +spec = ''' +{ + "description": "A simple bar plot with embedded data.", + "mark": "bar", + "encoding": { + "x": {"field": "a", "type": "ordinal"}, + "y": {"field": "b", "type": "quantitative"} + } +} +''' + + +# Get data rows for our plot. +# Typically, this data would be read from some external data source. +def poll(): + return [ + ["A", rnd()], ["B", rnd()], ["C", rnd()], + ["D", rnd()], ["E", rnd()], ["F", rnd()], + ["G", rnd()], ["H", rnd()], ["I", rnd()] + ] + + +# Generate random datum between 1 and 100 +def rnd(): + return random.randint(1, 100) + + +page['example'] = ui.form_card( + box='1 1 -1 -1', + items=[ + ui.text_xl('Example 1'), + ui.vega_visualization( + specification=spec, + data=data(fields=["a", "b"], rows=poll(), pack=True), + ), + ui.text_xl('Example 2'), + ui.vega_visualization( + specification=spec, + data=data(fields=["a", "b"], rows=poll(), pack=True), + ), + ui.text_xl('Example 3'), + ui.vega_visualization( + specification=spec, + data=data(fields=["a", "b"], rows=poll(), pack=True), + ), + ], +) + +page.save() diff --git a/examples/plot_vegalite_update.py b/examples/plot_vegalite_update.py new file mode 100644 index 0000000000000000000000000000000000000000..84a6734ce683acd6a4e426d48a3aba73e7be856d --- /dev/null +++ b/examples/plot_vegalite_update.py @@ -0,0 +1,50 @@ +# Plot / Vega-lite / Update +# Periodically update a Vega-lite #plot. #vega +# --- +from h2o_wave import site, data, ui +import random +import time + +page = site['/demo'] + +spec = ''' +{ + "description": "A simple bar plot with embedded data.", + "mark": "bar", + "encoding": { + "x": {"field": "a", "type": "ordinal"}, + "y": {"field": "b", "type": "quantitative"} + } +} +''' + + +# Generate random datum between 1 and 100 +def rnd(): + return random.randint(1, 100) + + +# Get data rows for our plot. +# Typically, this data would be read from some external data source. +def poll(): + return [ + ["A", rnd()], ["B", rnd()], ["C", rnd()], + ["D", rnd()], ["E", rnd()], ["F", rnd()], + ["G", rnd()], ["H", rnd()], ["I", rnd()] + ] + + +vis = page.add('external', ui.vega_card( + box='1 1 2 4', + title='Plot with external data', + specification=spec, + data=data(fields=["a", "b"], rows=poll()), +)) + +page.save() + +while True: + time.sleep(1) + # Update the plot's data rows + vis.data = poll() + page.save() diff --git a/examples/post.py b/examples/post.py new file mode 100644 index 0000000000000000000000000000000000000000..bad691ee5e3837729d14a0e52249aa0eb67944bf --- /dev/null +++ b/examples/post.py @@ -0,0 +1,42 @@ +# Post card +# Create a post card displaying persona, image, caption and optional buttons. +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +image = 'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&h=750&w=1260' +page['example'] = ui.post_card( + box='1 1 3 5', + persona=ui.persona(title='John Doe', subtitle='Data Scientist', image=image, caption='caption'), + commands=[ + ui.command(name='new', label='New', icon='Add', items=[ + ui.command(name='email', label='Email Message', icon='Mail'), + ui.command(name='calendar', label='Calendar Event', icon='Calendar'), + ]), + ui.command(name='upload', label='Upload', icon='Upload'), + ui.command(name='share', label='Share', icon='Share'), + ui.command(name='download', label='Download', icon='Download'), + ], + items=[ + ui.inline(justify='end', items=[ + ui.mini_buttons([ + ui.mini_button(name='like', label='4', icon='Heart'), + ui.mini_button(name='comment', label='2', icon='Comment'), + ui.mini_button(name='share', label='1', icon='Share'), + ]), + ]), + ui.buttons(items=[ + ui.button(name='like', label='Like'), + ui.button(name='comment', label='Comment'), + ui.button(name='share', label='Share'), + ]), + ], + caption=''' +Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quia aliquam maxime quos facere +necessitatibus tempore eum odio, qui illum. Repellat modi dolor facilis odio ex possimus +''', + aux_value='2h ago', + image='https://images.pexels.com/photos/3225517/pexels-photo-3225517.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260' # noqa +) +page.save() diff --git a/examples/preview.py b/examples/preview.py new file mode 100644 index 0000000000000000000000000000000000000000..3091c98a7ecc4898e740da473ba4ac201f54541d --- /dev/null +++ b/examples/preview.py @@ -0,0 +1,31 @@ +# Preview card +# Create a preview card displaying an image with shadow overlay, title, social icons, caption, and button. +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.preview_card: + q.page['example'] = ui.form_card(box='1 1 3 4', items=[ + ui.button(name='back', label='Back', primary=True), + ]) + else: + q.page['example'] = ui.preview_card( + name='preview_card', + box='1 1 3 4', + image='https://images.pexels.com/photos/1269968/pexels-photo-1269968.jpeg?auto=compress', + title='Post title', + items=[ui.mini_buttons([ + ui.mini_button(name='like', label='4', icon='Heart'), + ui.mini_button(name='comment', label='2', icon='Comment'), + ui.mini_button(name='share', label='1', icon='Share'), + ]) + ], + caption=''' + Lorem ipsum dolor sit amet, coectetur adipiscing elit. Etiam ut hendrerit lectus.As Etiam venenatis id nulla a molestie. + Lorem ipsum dolor sit amet, coectetur adipiscing elit. Etiam ut hendrerit lectus.As Etiam venenatis id nulla a molestie. + ''', + label='Click me' + ) + await q.page.save() diff --git a/examples/profile.py b/examples/profile.py new file mode 100644 index 0000000000000000000000000000000000000000..1b6fa9766cfecc8284d1f7d4bccfda952085d159 --- /dev/null +++ b/examples/profile.py @@ -0,0 +1,29 @@ +# Profile +# Create a profile card to display information about a user. +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +image = 'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&h=750&w=1260' +page.add('example', ui.profile_card( + box='1 1 3 5', + image='https://images.pexels.com/photos/3225517/pexels-photo-3225517.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260', # noqa + persona=ui.persona(title='John Doe', subtitle='Data Scientist', image=image), + items=[ + ui.inline(justify='center', items=[ + ui.mini_buttons([ + ui.mini_button(name='upload', label='Upload', icon='Upload'), + ui.mini_button(name='share', label='Share', icon='Share'), + ui.mini_button(name='download', label='Download', icon='Download'), + ]) + ]), + ui.inline(justify='center', items=[ + ui.button(name='btn1', label='Button 1'), + ui.button(name='btn2', label='Button 2'), + ui.button(name='btn3', label='Button 3'), + ]), + ] +)) + +page.save() diff --git a/examples/progress.py b/examples/progress.py new file mode 100644 index 0000000000000000000000000000000000000000..11f04f77b36e146e6fb88df453097d4d16d12039 --- /dev/null +++ b/examples/progress.py @@ -0,0 +1,18 @@ +# Form / Progress +# Use a #progress bar to indicate completion status of an operation. +# #form +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +page['example'] = ui.form_card( + box='1 1 4 7', + items=[ + ui.progress(label='Indeterminate Progress', caption='Goes on forever'), + ui.progress(label='Standard Progress', caption='Downloading the interwebs...', value=0.25), + ui.progress(label='Spinner Progress', type='spinner'), + ui.progress(label='', caption='Spinner Progress with text at the bottom', type='spinner'), + ] +) +page.save() diff --git a/examples/progress_update.py b/examples/progress_update.py new file mode 100644 index 0000000000000000000000000000000000000000..f0bb35e6c0b3412fcba463824d12fd9bb9557c02 --- /dev/null +++ b/examples/progress_update.py @@ -0,0 +1,28 @@ +# Form / Progress / Updating +# Update a #progress bar's completion status periodically. +# #form +# --- +import time + +from h2o_wave import site, ui + +page = site['/demo'] + +page['example'] = ui.form_card( + box='1 1 4 7', + items=[ + ui.progress(name='progress', label='Basic Progress'), + ] +) +page.save() + +for i in range(1, 11): + time.sleep(1) + page['example'].items = [ + ui.progress(name='progress', label='Basic Progress', caption=f'{i * 10}% complete', value=i / 10), + ] + + # This will work, too: + # page['example'].progress.value = i/10 + + page.save() diff --git a/examples/range_slider.py b/examples/range_slider.py new file mode 100644 index 0000000000000000000000000000000000000000..e38304e1da0db82162584cb48edf9114054e0d52 --- /dev/null +++ b/examples/range_slider.py @@ -0,0 +1,26 @@ +# Form / Range Slider +# Use a #range #slider to allow users to select a value range (from, to). +# #form +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'range_slider={q.args.range_slider}'), + ui.text(f'range_slider_step={q.args.range_slider_step}'), + ui.text(f'range_slider_disabled={q.args.range_slider_disabled}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 5', items=[ + ui.range_slider(name='range_slider', label='Default slider'), + ui.range_slider(name='range_slider_step', label='Step slider', min=0, max=1000, step=100, min_value=0, + max_value=100), + ui.range_slider(name='range_slider_disabled', label='Disabled slider', min=0, max=100, step=10, min_value=0, + max_value=70, disabled=True), + ui.button(name='show_inputs', label='Submit', primary=True), + ]) + await q.page.save() diff --git a/examples/repeat.py b/examples/repeat.py new file mode 100644 index 0000000000000000000000000000000000000000..3b475dc4d37bdaf4d8a57e3130dad6a5ee59188c --- /dev/null +++ b/examples/repeat.py @@ -0,0 +1,24 @@ +# Repeat +# Use a #repeat card to render a card repeatedly. +# --- +import random + +from faker import Faker + +from h2o_wave import site, ui, pack, data + +fake = Faker() + +page = site['/demo'] + +c = page.add('example', ui.repeat_card( + box='1 1 2 4', + item_view='list_item1', + item_props=pack(dict(title='=code', caption='=currency', value='=trades', aux_value='=returns')), + data=data('currency code trades returns', -15), +)) + +c.data = [[fake.cryptocurrency_name(), fake.cryptocurrency_code(), random.randint(100, 1000), random.randint(10, 100)] + for _ in range(15)] + +page.save() diff --git a/examples/requirements.txt b/examples/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..e97dba6611f72964b4211ea326461c5bf9d604a2 --- /dev/null +++ b/examples/requirements.txt @@ -0,0 +1,14 @@ +altair==4.2.0 +bokeh==2.4.2 +Faker==13.3.4 +h2o_wave[ml] +loguru==0.6.0 +matplotlib==3.5.1 +numpy==1.22.2 +opencv-python==4.5.5.64 +pandas==1.3.5 +plotly==5.7.0 +pygments==2.11.2 +scikit-learn==1.0.2 +toml==0.10.2 +vega-datasets==0.9.0 \ No newline at end of file diff --git a/examples/routing.py b/examples/routing.py new file mode 100644 index 0000000000000000000000000000000000000000..8a7542a059e1535d1c32e092bdb8690b766b0bfe --- /dev/null +++ b/examples/routing.py @@ -0,0 +1,63 @@ +# Routing +# Use `on` and `handle_on` to simplify query handling by #routing queries to designated functions. +# --- +from h2o_wave import main, app, Q, ui, on, handle_on + + +# This function is called when q.args['empty_cart'] is True. +@on(arg='empty_cart') +async def clear_cart(q: Q): + q.page['cart'].cart_info.content = 'Your cart was emptied!' + await q.page.save() + + +# If the name of the function is the same as that of the q.arg, simply use on(). +# This function is called when q.args['buy_now'] is True. +@on() +async def buy_now(q: Q): + q.page['cart'].cart_info.content = 'Nothing to buy!' + await q.page.save() + + +# This function is called when q.args['#'] is 'about'. +@on(arg='#about') +async def handle_about(q: Q): + q.page['blurb'].content = 'Everything here is gluten-free!' + await q.page.save() + + +# This function is called when q.args['#'] is 'menu/spam', 'menu/ham', 'menu/eggs', etc. +# The 'product' placeholder's value is passed as an argument to the function. +@on(arg='#menu/{product}') +async def handle_menu(q: Q, product: str): + q.page['blurb'].content = f"Sorry, we're out of {product}!" + await q.page.save() + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.client.initialized = True + q.page['nav'] = ui.markdown_card( + box='1 1 4 2', + title='Menu', + content='[Spam](#menu/spam) / [Ham](#menu/ham) / [Eggs](#menu/eggs) / [About](#about)', + ) + q.page['blurb'] = ui.markdown_card( + box='1 3 4 2', + title='Description', + content='Welcome to our store!', + ) + q.page['cart'] = ui.form_card( + box='1 5 4 2', + title='Cart', + items=[ + ui.text(name='cart_info', content='Your cart is empty!'), + ui.buttons([ + ui.button(name=buy_now.__name__, label='Buy Now!', primary=True), + ui.button(name='empty_cart', label='Clear cart'), + ]) + ], + ) + await q.page.save() + await handle_on(q) diff --git a/examples/routing_predicates.py b/examples/routing_predicates.py new file mode 100644 index 0000000000000000000000000000000000000000..83d13eddde3b036785130ce0249d7c93e5f902f0 --- /dev/null +++ b/examples/routing_predicates.py @@ -0,0 +1,50 @@ +# Routing / Predicates +# Use `on` and `handle_on` with predicates to handle routing with custom conditions. +# --- +from h2o_wave import main, app, Q, ui, on, handle_on + + +# This function is called when q.args['temperature'] < 15. +@on('temperature', lambda x: x < 15) +async def when_cold(q: Q): + await show_slider(q, "It's too cold for a party!") + + +# This function is called when q.args['temperature'] is between 15 and 28. +@on('temperature', lambda x: 15 <= x < 28) +async def when_normal(q: Q): + await show_slider(q, "Party time!") + + +# This function is called when q.args['temperature'] > 28 +@on('temperature', lambda x: x > 28) +async def when_hot(q: Q): + await show_slider(q, "It's hot for a party!") + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.client.initialized = True + q.args.temperature = 20 + await show_slider(q, "") + else: + await handle_on(q) + + +async def show_slider(q: Q, message: str): + q.page['output'] = ui.form_card( + box='1 1 3 2', + title="Party Meter", + items=[ + ui.slider( + name='temperature', + label='Temperature (°C)', + max=50, + value=q.args.temperature, + trigger=True, + ), + ui.text(message), + ] + ) + await q.page.save() diff --git a/examples/separator.py b/examples/separator.py new file mode 100644 index 0000000000000000000000000000000000000000..e5a352a50b31bd17a34540bc494f8cd3ccd38d11 --- /dev/null +++ b/examples/separator.py @@ -0,0 +1,21 @@ +# Form / Separator +# Use a #separator to visually separate content into groups. +# #form +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +page['example'] = ui.form_card( + box='1 1 2 5', + items=[ + ui.separator(label='Separator 1'), + ui.text('The quick brown fox jumps over the lazy dog.'), + ui.separator(label='Separator 2'), + ui.text('The quick brown fox jumps over the lazy dog.'), + ui.separator(label='Separator 3'), + ui.text('The quick brown fox jumps over the lazy dog.'), + ] +) + +page.save() diff --git a/examples/site_async.py b/examples/site_async.py new file mode 100644 index 0000000000000000000000000000000000000000..ec06df899b30fc0cd1480feb8d9a3ea74b68ba75 --- /dev/null +++ b/examples/site_async.py @@ -0,0 +1,59 @@ +# Site / Async +# Update any page on a site from within an app using an `AsyncSite` instance. +# #site +# --- +from .synth import FakePercent +from h2o_wave import Q, app, main, ui, AsyncSite + +site = AsyncSite() + +# Grab a reference to the /stats page +stats_page = site['/stats'] + +# A flag to indicate whether to pause or resume updating the page at '/stats' +update_stats = False + + +async def update_stats_page(q, page): + f = FakePercent() + card = page['example'] + while update_stats: + await q.sleep(1) + price, percent = f.next() + card.data.price = price + card.data.percent = percent + card.progress = percent + await page.save() + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + # Set up up the page at /stats + stats_page.drop() # Clear any existing page + stats_page['example'] = ui.wide_gauge_stat_card( + box='1 1 2 1', + title='Stats', + value='=${{intl price minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value='={{intl percent style="percent" minimum_fraction_digits=2 maximum_fraction_digits=2}}', + plot_color='$red', + progress=0, + data=dict(price=0, percent=0), + ) + await stats_page.save() + + # Set up this app's UI + q.page['form'] = ui.form_card(box='1 1 6 5', items=[ + ui.frame(path='stats', height='110px'), + ui.button(name='toggle', label='Start updates', primary=True), + ]) + await q.page.save() + + q.client.initialized = True + + if q.args.toggle: + global update_stats + update_stats = not update_stats + q.page['form'].toggle.label = 'Stop updates' if update_stats else 'Start updates' + await q.page.save() + await update_stats_page(q, stats_page) diff --git a/examples/slider.py b/examples/slider.py new file mode 100644 index 0000000000000000000000000000000000000000..fdc00a544f4515a5c29f2b2dd28a7f5a56a75634 --- /dev/null +++ b/examples/slider.py @@ -0,0 +1,25 @@ +# Form / Slider +# Use a #slider to allow users to set a value within a specific range. +# #form +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'slider={q.args.slider}'), + ui.text(f'slider_zero={q.args.slider_zero}'), + ui.text(f'slider_disabled={q.args.slider_disabled}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 5', items=[ + ui.slider(name='slider', label='Standard slider', min=0, max=100, step=10, value=30), + ui.slider(name='slider_zero', label='Origin from zero', min=-10, max=10, step=1, value=-3), + ui.slider(name='slider_disabled', label='Disabled slider', min=0, max=100, step=10, value=30, + disabled=True), + ui.button(name='show_inputs', label='Submit', primary=True), + ]) + await q.page.save() diff --git a/examples/spinbox.py b/examples/spinbox.py new file mode 100644 index 0000000000000000000000000000000000000000..765807dacdc3ed6b7adf0b92576c3800ef7178df --- /dev/null +++ b/examples/spinbox.py @@ -0,0 +1,23 @@ +# Form / Spinbox +# Use a #spinbox to allow users to incrementally adjust a value in small steps. +# #form +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'spinbox={q.args.spinbox}'), + ui.text(f'spinbox_disabled={q.args.spinbox_disabled}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 5', items=[ + ui.spinbox(name='spinbox', label='Standard spinbox', min=0, max=100, step=10, value=30), + ui.spinbox(name='spinbox_disabled', label='Disabled spinbox', min=0, max=100, step=10, value=30, + disabled=True), + ui.button(name='show_inputs', label='Submit', primary=True), + ]) + await q.page.save() diff --git a/examples/spinbox_trigger.py b/examples/spinbox_trigger.py new file mode 100644 index 0000000000000000000000000000000000000000..cac8c12567b2cd0694bbc36cef4f164dd486770e --- /dev/null +++ b/examples/spinbox_trigger.py @@ -0,0 +1,24 @@ +# Form / Spinbox / Trigger +# Enable the `trigger` attribute in order to handle live changes to a spinbox. +# #form #spinbox #trigger +# --- +from typing import Optional +from h2o_wave import main, app, Q, ui + + +def get_form_items(value: Optional[float]): + return [ + ui.text(f'spinbox_trigger={value}'), + ui.spinbox(name='spinbox_trigger', label='Pick a number', trigger=True), + ] + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['example'] = ui.form_card(box='1 1 4 4', items=get_form_items(None)) + q.client.initialized = True + if q.args.spinbox_trigger is not None: + q.page['example'].items = get_form_items(q.args.spinbox_trigger) + + await q.page.save() diff --git a/examples/stat_large.py b/examples/stat_large.py new file mode 100644 index 0000000000000000000000000000000000000000..3db8e42d67e5fefdb1e6df7b0efa218306b6d326 --- /dev/null +++ b/examples/stat_large.py @@ -0,0 +1,32 @@ +# Stat / Large +# Create a stat card displaying a primary value, an auxiliary value and a caption. +# #stat_card +# --- +import time + +from faker import Faker + +from synth import FakePercent +from h2o_wave import site, ui + +page = site['/demo'] + +fake = Faker() +f = FakePercent() +val, pc = f.next() +c = page.add('example', ui.large_stat_card( + box='1 1 2 2', + title=fake.cryptocurrency_name(), + value='=${{intl qux minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value='={{intl quux style="percent" minimum_fraction_digits=1 maximum_fraction_digits=1}}', + data=dict(qux=val, quux=pc), + caption=' '.join(fake.sentences()), +)) +page.save() + +while True: + time.sleep(1) + val, pc = f.next() + c.data.qux = val + c.data.quux = pc + page.save() diff --git a/examples/stat_large_bar.py b/examples/stat_large_bar.py new file mode 100644 index 0000000000000000000000000000000000000000..7244b8140ccd0655601dc1fec1ce6a098c1a53c9 --- /dev/null +++ b/examples/stat_large_bar.py @@ -0,0 +1,38 @@ +# Stat / Bar / Large +# Create a large captioned card displaying a primary value, an auxiliary value and a progress bar, +# with captions for each value. +# #stat_card #progress +# --- +import time + +from faker import Faker + +from synth import FakePercent +from h2o_wave import site, ui + +page = site['/demo'] + +fake = Faker() +f = FakePercent() +val, pc = f.next() +c = page.add('example', ui.large_bar_stat_card( + box='1 1 2 2', + title=fake.cryptocurrency_name(), + value='=${{intl foo minimum_fraction_digits=2 maximum_fraction_digits=2}}', + value_caption='This Month', + aux_value='={{intl bar style="percent" minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value_caption='Previous Month', + plot_color='$red', + progress=pc, + data=dict(foo=val, bar=pc), + caption=' '.join(fake.sentences(2)), +)) +page.save() + +while True: + time.sleep(1) + val, pc = f.next() + c.data.foo = val + c.data.bar = pc + c.progress = pc + page.save() diff --git a/examples/stat_small.py b/examples/stat_small.py new file mode 100644 index 0000000000000000000000000000000000000000..d960f313dd5e27f0a36e19ff720ed35392376dc7 --- /dev/null +++ b/examples/stat_small.py @@ -0,0 +1,28 @@ +# Stat / Small +# Create a stat card displaying a single value. +# #stat_card +# --- +import time + +from faker import Faker + +from synth import FakePercent +from h2o_wave import site, ui + +page = site['/demo'] + +fake = Faker() +f = FakePercent() +val, _ = f.next() +c = page.add('example', ui.small_stat_card( + box='1 1 1 1', + title=fake.cryptocurrency_name(), + value=f'${val:.2f}', +)) +page.save() + +while True: + time.sleep(1) + val, _ = f.next() + c.value = f'${val:.2f}' + page.save() diff --git a/examples/stat_small_series_area.py b/examples/stat_small_series_area.py new file mode 100644 index 0000000000000000000000000000000000000000..dea80fe806c30a5ee95bcf793351c2c049b6f525 --- /dev/null +++ b/examples/stat_small_series_area.py @@ -0,0 +1,44 @@ +# Stat / Series / Small / Area +# Create a small stat card displaying a primary value and a series plot. +# #stat_card #series +# --- +import time + +from faker import Faker + +from synth import FakeCategoricalSeries +from h2o_wave import site, ui, data + +page = site['/demo'] + +colors = '$red $pink $blue $azure $cyan $teal $mint $green $lime $yellow $amber $orange $tangerine'.split() +curves = 'linear smooth step step-after step-before'.split() +fake = Faker() +cards = [] +for i in range(len(curves)): + f = FakeCategoricalSeries() + cat, val, pc = f.next() + c = page.add(f'example{i}', ui.small_series_stat_card( + box=f'1 {i + 1} 1 1', + title=fake.cryptocurrency_name(), + value='=${{intl qux minimum_fraction_digits=2 maximum_fraction_digits=2}}', + data=dict(qux=val, quux=pc), + plot_category='foo', + plot_type='area', + plot_value='qux', + plot_color=colors[i], + plot_data=data('foo qux', -15), + plot_zero_value=0, + plot_curve=curves[i], + )) + cards.append((f, c)) +page.save() + +while True: + time.sleep(1) + for f, c in cards: + cat, val, pc = f.next() + c.data.qux = val + c.data.quux = pc + c.plot_data[-1] = [cat, val] + page.save() diff --git a/examples/stat_small_series_interval.py b/examples/stat_small_series_interval.py new file mode 100644 index 0000000000000000000000000000000000000000..bcd98b23636e2d9493323cbecb5705fb7b2a15f1 --- /dev/null +++ b/examples/stat_small_series_interval.py @@ -0,0 +1,37 @@ +# Stat / Series / Small / Interval +# Create a small stat card displaying a primary value and a series plot. +# #stat_card #interval #series +# --- +import time + +from faker import Faker + +from synth import FakeCategoricalSeries +from h2o_wave import site, ui, data + +page = site['/demo'] + +fake = Faker() +f = FakeCategoricalSeries() +cat, val, pc = f.next() +c = page.add('example', ui.small_series_stat_card( + box='1 1 1 1', + title=fake.cryptocurrency_name(), + value='=${{intl qux minimum_fraction_digits=2 maximum_fraction_digits=2}}', + data=dict(qux=val, quux=pc), + plot_category='foo', + plot_type='interval', + plot_value='qux', + plot_color='$red', + plot_data=data('foo qux', -20), + plot_zero_value=0, +)) +page.save() + +while True: + time.sleep(1) + cat, val, pc = f.next() + c.data.qux = val + c.data.quux = pc + c.plot_data[-1] = [cat, val] + page.save() diff --git a/examples/stat_table.py b/examples/stat_table.py new file mode 100644 index 0000000000000000000000000000000000000000..74e163428381744fe6ef0ffed860cf42f77358f1 --- /dev/null +++ b/examples/stat_table.py @@ -0,0 +1,28 @@ +# Stat / Table +# Create a card displaying title, subtitle, and tabular data. +# #stat_card #table +# --- + +from h2o_wave import site, ui + +from faker import Faker + +fake = Faker() + +page = site['/demo'] + +page.add('example', ui.stat_table_card( + box='1 1 4 8', + title='Contacts', + subtitle=f'Last updated: {fake.date()}', + columns=['Name', 'Job', 'City'], + items=[ + ui.stat_table_item( + label=fake.name(), + values=[fake.job(), fake.city()], + colors=['darkblue', '$amber'] + ) for i in range(10) + ] +)) + +page.save() diff --git a/examples/stat_tall_gauge.py b/examples/stat_tall_gauge.py new file mode 100644 index 0000000000000000000000000000000000000000..67260b44435fd0a2472753040fa257298e02ad74 --- /dev/null +++ b/examples/stat_tall_gauge.py @@ -0,0 +1,34 @@ +# Stat / Gauge / Tall +# Create a tall stat card displaying a primary value, an auxiliary value and a #progress gauge. +# #stat_card +# --- +import time + +from faker import Faker + +from synth import FakePercent +from h2o_wave import site, ui + +page = site['/demo'] + +fake = Faker() +f = FakePercent() +val, pc = f.next() +c = page.add('example', ui.tall_gauge_stat_card( + box='1 1 1 2', + title=fake.cryptocurrency_name(), + value='=${{intl foo minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value='={{intl bar style="percent" minimum_fraction_digits=2 maximum_fraction_digits=2}}', + plot_color='$red', + progress=pc, + data=dict(foo=val, bar=pc), +)) +page.save() + +while True: + time.sleep(1) + val, pc = f.next() + c.data.foo = val + c.data.bar = pc + c.progress = pc + page.save() diff --git a/examples/stat_tall_series_area.py b/examples/stat_tall_series_area.py new file mode 100644 index 0000000000000000000000000000000000000000..de584b02175f7c5d0ec92e988042abc9885bdfd9 --- /dev/null +++ b/examples/stat_tall_series_area.py @@ -0,0 +1,45 @@ +# Stat / Series / Tall / Area +# Create a tall stat card displaying a primary value, an auxiliary value and a #series plot. +# #stat_card +# --- +import time + +from faker import Faker + +from synth import FakeCategoricalSeries +from h2o_wave import site, ui, data + +page = site['/demo'] + +colors = '$red $pink $blue $azure $cyan $teal $mint $green $lime $yellow $amber $orange $tangerine'.split() +curves = 'linear smooth step step-after step-before'.split() +fake = Faker() +cards = [] +for i in range(len(curves)): + f = FakeCategoricalSeries() + cat, val, pc = f.next() + c = page.add(f'example{i}', ui.tall_series_stat_card( + box=f'{i + 1} 1 1 2', + title=fake.cryptocurrency_name(), + value='=${{intl qux minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value='={{intl quux style="percent" minimum_fraction_digits=1 maximum_fraction_digits=1}}', + data=dict(qux=val, quux=pc / 100), + plot_type='area', + plot_category='foo', + plot_value='qux', + plot_color=colors[i], + plot_data=data('foo qux', -15), + plot_zero_value=0, + plot_curve=curves[i], + )) + cards.append((f, c)) +page.save() + +while True: + time.sleep(1) + for f, c in cards: + cat, val, pc = f.next() + c.data.qux = val + c.data.quux = pc / 100 + c.plot_data[-1] = [cat, val] + page.save() diff --git a/examples/stat_tall_series_interval.py b/examples/stat_tall_series_interval.py new file mode 100644 index 0000000000000000000000000000000000000000..247a46b7642d2e0474c4e1f6e6b5243dd3cbd0b6 --- /dev/null +++ b/examples/stat_tall_series_interval.py @@ -0,0 +1,38 @@ +# Stat / Series / Tall / Interval +# Create a tall stat card displaying a primary value, an auxiliary value and a #series plot. +# #stat_card #interval +# --- +import time + +from faker import Faker + +from synth import FakeCategoricalSeries +from h2o_wave import site, ui, data + +page = site['/demo'] + +fake = Faker() +f = FakeCategoricalSeries() +cat, val, pc = f.next() +c = page.add('example', ui.tall_series_stat_card( + box='1 1 1 2', + title=fake.cryptocurrency_name(), + value='=${{intl qux minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value='={{intl quux style="percent" minimum_fraction_digits=1 maximum_fraction_digits=1}}', + data=dict(qux=val, quux=pc / 100), + plot_category='foo', + plot_type='interval', + plot_value='qux', + plot_color='$red', + plot_data=data('foo qux', -20), + plot_zero_value=0, +)) +page.save() + +while True: + time.sleep(1) + cat, val, pc = f.next() + c.data.qux = val + c.data.quux = pc / 100 + c.plot_data[-1] = [cat, val] + page.save() diff --git a/examples/stat_wide_bar.py b/examples/stat_wide_bar.py new file mode 100644 index 0000000000000000000000000000000000000000..7ba291e5c3506859e61a9bc921eb272582f8d930 --- /dev/null +++ b/examples/stat_wide_bar.py @@ -0,0 +1,34 @@ +# Stat / Bar / Wide +# Create a wide stat card displaying a primary value, an auxiliary value and a #progress bar. +# #stat_card +# --- +import time + +from faker import Faker + +from synth import FakePercent +from h2o_wave import site, ui + +page = site['/demo'] + +fake = Faker() +f = FakePercent() +val, pc = f.next() +c = page.add('example', ui.wide_bar_stat_card( + box='1 1 2 1', + title=fake.cryptocurrency_name(), + value='=${{intl foo minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value='={{intl bar style="percent" minimum_fraction_digits=2 maximum_fraction_digits=2}}', + plot_color='$red', + progress=pc, + data=dict(foo=val, bar=pc), +)) +page.save() + +while True: + time.sleep(1) + val, pc = f.next() + c.data.foo = val + c.data.bar = pc + c.progress = pc + page.save() diff --git a/examples/stat_wide_gauge.py b/examples/stat_wide_gauge.py new file mode 100644 index 0000000000000000000000000000000000000000..6c9fec5e62deac224bd7345dea66cf11634551d4 --- /dev/null +++ b/examples/stat_wide_gauge.py @@ -0,0 +1,34 @@ +# Stat / Gauge / Wide +# Create a wide stat card displaying a primary value, an auxiliary value and a #progress gauge. +# #stat_card +# --- +import time + +from faker import Faker + +from synth import FakePercent +from h2o_wave import site, ui + +page = site['/demo'] + +fake = Faker() +f = FakePercent() +val, pc = f.next() +c = page.add('example', ui.wide_gauge_stat_card( + box='1 1 2 1', + title=fake.cryptocurrency_name(), + value='=${{intl foo minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value='={{intl bar style="percent" minimum_fraction_digits=2 maximum_fraction_digits=2}}', + plot_color='$red', + progress=pc, + data=dict(foo=val, bar=pc), +)) +page.save() + +while True: + time.sleep(1) + val, pc = f.next() + c.data.foo = val + c.data.bar = pc + c.progress = pc + page.save() diff --git a/examples/stat_wide_pie.py b/examples/stat_wide_pie.py new file mode 100644 index 0000000000000000000000000000000000000000..4688eff7af6aae3f46c9541c17669d43e0dc3fc7 --- /dev/null +++ b/examples/stat_wide_pie.py @@ -0,0 +1,17 @@ +# Stat / Pie / Wide +# Create a wide stat pie card displaying a pie chart. +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +page.add('example', ui.wide_pie_stat_card( + box='1 1 3 3', + title='Wide Pie Stat', + pies=[ + ui.pie(label='Category 1', value='35%', fraction=0.35, color='#2cd0f5', aux_value='$ 35'), + ui.pie(label='Category 2', value='65%', fraction=0.65, color='$themePrimary', aux_value='$ 65'), + ], +)) + +page.save() diff --git a/examples/stat_wide_series_area.py b/examples/stat_wide_series_area.py new file mode 100644 index 0000000000000000000000000000000000000000..f3c5cd6fe69ef4a9618f84a954eec44adc6af2a2 --- /dev/null +++ b/examples/stat_wide_series_area.py @@ -0,0 +1,45 @@ +# Stat / Series / Wide / Area +# Create a wide stat card displaying a primary value, an auxiliary value and a #series plot. +# #stat_card +# --- +import time + +from faker import Faker + +from synth import FakeCategoricalSeries +from h2o_wave import site, ui, data + +page = site['/demo'] + +colors = '$red $pink $blue $azure $cyan $teal $mint $green $lime $yellow $amber $orange $tangerine'.split() +curves = 'linear smooth step step-after step-before'.split() +fake = Faker() +cards = [] +for i in range(len(curves)): + f = FakeCategoricalSeries() + cat, val, pc = f.next() + c = page.add(f'example{i}', ui.wide_series_stat_card( + box=f'1 {i + 1} 2 1', + title=fake.cryptocurrency_name(), + value='=${{intl qux minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value='={{intl quux style="percent" minimum_fraction_digits=1 maximum_fraction_digits=1}}', + data=dict(qux=val, quux=pc / 100), + plot_category='foo', + plot_type='area', + plot_value='qux', + plot_color=colors[i], + plot_data=data('foo qux', -15), + plot_zero_value=0, + plot_curve=curves[i], + )) + cards.append((f, c)) +page.save() + +while True: + time.sleep(1) + for f, c in cards: + cat, val, pc = f.next() + c.data.qux = val + c.data.quux = pc / 100 + c.plot_data[-1] = [cat, val] + page.save() diff --git a/examples/stat_wide_series_interval.py b/examples/stat_wide_series_interval.py new file mode 100644 index 0000000000000000000000000000000000000000..9cb8d8e6bb0a5cc250bbcbb756768cd6c3e789ec --- /dev/null +++ b/examples/stat_wide_series_interval.py @@ -0,0 +1,38 @@ +# Stat / Series / Wide / Interval +# Create a wide stat card displaying a primary value, an auxiliary value and a #series plot. +# #stat_card #interval +# --- +import time + +from faker import Faker + +from synth import FakeCategoricalSeries +from h2o_wave import site, ui, data + +page = site['/demo'] + +fake = Faker() +f = FakeCategoricalSeries() +cat, val, pc = f.next() +c = page.add('example', ui.wide_series_stat_card( + box='1 1 2 1', + title=fake.cryptocurrency_name(), + value='=${{intl qux minimum_fraction_digits=2 maximum_fraction_digits=2}}', + aux_value='={{intl quux style="percent" minimum_fraction_digits=1 maximum_fraction_digits=1}}', + data=dict(qux=val, quux=pc / 100), + plot_category='foo', + plot_type='interval', + plot_value='qux', + plot_color='$red', + plot_data=data('foo qux', -15), + plot_zero_value=0, +)) +page.save() + +while True: + time.sleep(1) + cat, val, pc = f.next() + c.data.qux = val + c.data.quux = pc / 100 + c.plot_data[-1] = [cat, val] + page.save() diff --git a/examples/stepper.py b/examples/stepper.py new file mode 100644 index 0000000000000000000000000000000000000000..ccd8d5fa346f77607b0e662023c9c98a23be657f --- /dev/null +++ b/examples/stepper.py @@ -0,0 +1,40 @@ +# Form / Stepper +# Use #Stepper to show #progress through numbered steps. +# #form +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + q.page['basic-stepper'] = ui.form_card( + box='1 1 4 2', + items=[ + ui.stepper(name='basic-stepper', items=[ + ui.step(label='Step 1'), + ui.step(label='Step 2'), + ui.step(label='Step 3'), + ]) + ] + ) + q.page['icon-stepper'] = ui.form_card( + box='1 3 4 2', + items=[ + ui.stepper(name='icon-stepper', items=[ + ui.step(label='Step 1', icon='MailLowImportance'), + ui.step(label='Step 2', icon='TaskManagerMirrored'), + ui.step(label='Step 3', icon='Cafe'), + ]) + ] + ) + q.page['almost-done-stepper'] = ui.form_card( + box='1 5 4 2', + items=[ + ui.stepper(name='almost-done-stepper', items=[ + ui.step(label='Step 1', done=True), + ui.step(label='Step 2', done=True), + ui.step(label='Step 3'), + ]) + ] + ) + await q.page.save() diff --git a/examples/swatch_picker.py b/examples/swatch_picker.py new file mode 100644 index 0000000000000000000000000000000000000000..8abea05a72d8228301b61bd63b2638aece368b98 --- /dev/null +++ b/examples/swatch_picker.py @@ -0,0 +1,26 @@ +# Form / Swatch Picker +# Use a swatch picker to allow users to choose a from a specific set of colors. +# #swatch_picker #form +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'swatch={q.args.swatch}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 5', items=[ + ui.color_picker( + name='swatch', + label='Pick a swatch', + choices=[ + '#011627', '#2EC4B6', '#E71D36', '#FF9F1C', '#50514F', + '#F25F5C', '#FFE066', '#247BA0', '#70C1B3', '#FDFFFC', + ]), + ui.button(name='show_inputs', label='Submit', primary=True), + ]) + await q.page.save() diff --git a/examples/synth.py b/examples/synth.py new file mode 100644 index 0000000000000000000000000000000000000000..dad29b6e289ecfeb33033a0860c501b90172a85c --- /dev/null +++ b/examples/synth.py @@ -0,0 +1,92 @@ +import datetime +import random + + +class FakeSeries: + def __init__(self, min=0.0, max=100.0, variation=10.0, start: int = None): + self.min = min + self.max = max + self.variation = variation + self.start = random.randint(min, max) if start is None else start + self.x = self.start + + def next(self): + x0 = self.x + x = x0 + (random.random() - 0.5) * self.variation + if not self.min <= x <= self.max: + x = self.start + self.x = x + dx = 0 if x0 == 0 else 100.0 * (x - x0) / x0 + return x, dx + + +class FakeTimeSeries: + def __init__(self, min=0.0, max=100.0, variation=10.0, start: int = None, delta_days=1): + self.series = FakeSeries(min, max, variation, start) + self.delta_days = delta_days + self.date = datetime.datetime.utcnow() - datetime.timedelta(days=10 * 365) + + def next(self): + x, dx = self.series.next() + self.date = self.date + datetime.timedelta(days=self.delta_days) + return self.date.isoformat() + 'Z', x, dx + + +class FakeMultiTimeSeries: + def __init__(self, min=0.0, max=100.0, variation=10.0, start: int = None, delta_days=1, groups=5): + self.series = [(f'G{c + 1}', FakeTimeSeries(min, max, variation, start, delta_days)) for c in range(groups)] + + def next(self): + data = [] + for g, series in self.series: + t, x, dx = series.next() + data.append((g, t, x, dx)) + return data + + +class FakeCategoricalSeries: + def __init__(self, min=0.0, max=100.0, variation=10.0, start: int = None): + self.series = FakeSeries(min, max, variation, start) + self.i = 0 + + def next(self): + x, dx = self.series.next() + self.i += 1 + return f'C{self.i}', x, dx + + +class FakeMultiCategoricalSeries: + def __init__(self, min=0.0, max=100.0, variation=10.0, start: int = None, groups=5): + self.series = [(f'G{c + 1}', FakeCategoricalSeries(min, max, variation, start)) for c in range(groups)] + + def next(self): + data = [] + for g, series in self.series: + c, x, dx = series.next() + data.append((g, c, x, dx)) + return data + + +class FakeScatter: + def __init__(self, min=0.0, max=100.0, variation=10.0, start: int = None): + self.x = FakeSeries(min, max, variation, start) + self.y = FakeSeries(min, max, variation, start) + + def next(self): + x, dx = self.x.next() + y, dy = self.y.next() + return x, y + + +class FakePercent: + def __init__(self, min=5.0, max=35.0, variation=4.0): + self.min = min + self.max = max + self.variation = variation + self.x = random.randint(min, max) + + def next(self): + self.x += random.random() * self.variation + if self.x >= self.max: + self.x = self.min + return self.x, (self.x - self.min) / (self.max - self.min) diff --git a/examples/tab.py b/examples/tab.py new file mode 100644 index 0000000000000000000000000000000000000000..727566853dda58bd15442b6fcbcd40fd465a8b63 --- /dev/null +++ b/examples/tab.py @@ -0,0 +1,47 @@ +# Tab +# This example demonstrates how you can observe and handle changes to the browser's +# [location hash](https://developer.mozilla.org/en-US/docs/Web/API/Location/hash). +# +# The location hash can be accessed using `q.args['#']`. +# #routing #tab +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + content = 'Welcome to our store!' + + location = q.args['#'] + if location: + if location == 'menu/spam': + content = "Sorry, we're out of spam!" + elif location == 'menu/ham': + content = "Sorry, we're out of ham!" + elif location == 'menu/eggs': + content = "Sorry, we're out of eggs!" + elif location == 'about': + content = 'Everything here is gluten-free!' + + if not q.client.initialized: + q.page['nav'] = ui.tab_card( + box='1 1 4 1', + items=[ + ui.tab(name='#menu/spam', label='Spam'), + ui.tab(name='#menu/ham', label='Ham'), + ui.tab(name='#menu/eggs', label='Eggs'), + ui.tab(name='#about', label='About'), + ], + value=f'#{location}' if location else None, + ) + q.page['blurb'] = ui.markdown_card( + box='1 2 4 2', + title='Store', + content=content, + ) + q.client.initialized = True + elif location: + blurb = q.page['blurb'] + blurb.content = content + + await q.page.save() diff --git a/examples/tab_delete.py b/examples/tab_delete.py new file mode 100644 index 0000000000000000000000000000000000000000..ecdd20a3eab80cf3f11839ec268202b609d4a212 --- /dev/null +++ b/examples/tab_delete.py @@ -0,0 +1,41 @@ +# Tabs / Navigation +# Navigate between two or more tabs. +# Delete the cards when switching between tabs. +# #tabs #navigation +# --- +from h2o_wave import main, app, Q, ui + +TABS = 'abcde' + + +async def display_tab(q): + q.page[f'example_{q.client.tab}'] = ui.markup_card( + box='1 2 4 3', + title=q.client.tab.upper(), + content='\n'.join([''.join([q.client.tab] * 10) for _ in range(50)]) + ) + await q.page.save() + + +async def remove_cards(q: Q): + for tab in TABS: + del q.page[f'example_{tab}'] + await q.page.save() + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.client.tab = 'a' + q.page['tabs'] = ui.tab_card( # Initialize once + box='1 1 4 1', + items=[ui.tab(name=f'#{t}', label=t.upper()) for t in TABS] + ) + q.client.initialized = True + + if q.args['#']: + q.client.tab = str(q.args['#']) + + await remove_cards(q) + await display_tab(q) + await q.page.save() diff --git a/examples/table.py b/examples/table.py new file mode 100644 index 0000000000000000000000000000000000000000..aabe6d3ae0c6e96b312266de0be06974fbea629f --- /dev/null +++ b/examples/table.py @@ -0,0 +1,81 @@ +# Table +# Use a #table to display tabular data. +# --- +import random + +from faker import Faker + +from h2o_wave import main, app, Q, ui + +fake = Faker() + +_id = 0 + + +class Issue: + def __init__(self, text: str, status: str, progress: float, icon: str, state: str, created: str): + global _id + _id += 1 + self.id = f'I{_id}' + self.text = text + self.status = status + self.views = 0 + self.progress = progress + self.icon = icon + self.created = created + self.state = state + + +# Create some issues +issues = [ + Issue( + text=fake.sentence(), + status=('Closed' if i % 2 == 0 else 'Open'), + progress=random.random(), + icon=('BoxCheckmarkSolid' if random.random() > 0.5 else 'BoxMultiplySolid'), + state=('RUNNING' if random.random() > 0.5 else 'DONE,SUCCESS'), + created='1655927271') for i in range(100) +] + +# Create columns for our issue table. +columns = [ + ui.table_column(name='text', label='Issue', sortable=True, searchable=True, max_width='300', cell_overflow='wrap'), + ui.table_column(name='status', label='Status', filterable=True), + ui.table_column(name='done', label='Done', cell_type=ui.icon_table_cell_type()), + ui.table_column(name='views', label='Views', sortable=True, data_type='number'), + ui.table_column(name='progress', label='Progress', cell_type=ui.progress_table_cell_type()), + ui.table_column(name='tag', label='State', min_width='170px', cell_type=ui.tag_table_cell_type(name='tags', tags=[ + ui.tag(label='RUNNING', color='#D2E3F8'), + ui.tag(label='DONE', color='$red'), + ui.tag(label='SUCCESS', color='$mint'), + ] + )), + ui.table_column(name='created', label='Created', sortable=True, data_type='time'), +] + + +@app('/demo') +async def serve(q: Q): + if q.args.issues: + q.page['form'] = ui.form_card(box='1 1 -1 -1', items=[ + ui.text(f'You clicked on: {q.args.issues}'), + ui.button(name='back', label='Back'), + ]) + else: + q.page['form'] = ui.form_card(box='1 1 -1 -1', items=[ + ui.table( + name='issues', + columns=columns, + rows=[ui.table_row( + name=issue.id, + cells=[issue.text, issue.status, issue.icon, str(issue.views), str(issue.progress), + issue.state, issue.created] + ) for issue in issues], + groupable=True, + downloadable=True, + resettable=True, + height='600px' + ) + ]) + + await q.page.save() diff --git a/examples/table_column_alignment.py b/examples/table_column_alignment.py new file mode 100644 index 0000000000000000000000000000000000000000..1198d52dab765149a23ead803efdb978c26188ae --- /dev/null +++ b/examples/table_column_alignment.py @@ -0,0 +1,58 @@ +# Table / Column Alignment +# Allow table values to be aligned per column as left (default), right or center +# #table +# --- +from faker import Faker + +from h2o_wave import main, app, Q, ui + +fake = Faker() + +_id = 0 + +# Create some names +class User: + def __init__(self, first_name: str, last_name: str, username: str, company: str): + global _id + _id += 1 + self.id = f'I{_id}' + self.first_name = first_name + self.last_name = last_name + self.username = username + self.company = company + +users = [ + User( + first_name=fake.first_name(), + last_name=fake.last_name(), + username=fake.user_name(), + company=fake.company() + ) for i in range (100) +] + +# Create columns for our issue table. +columns = [ + ui.table_column(name='first_name', label='First Name', align='center'), + ui.table_column(name='last_name', label='Last Name', align='right'), + ui.table_column(name='username', label='Username', align='left'), + ui.table_column(name='company', label='Company'), +] + + +@app('/demo') +async def serve(q: Q): + q.page['form'] = ui.form_card(box='1 1 -1 10', items=[ + ui.table( + name='users', + columns=columns, + rows=[ui.table_row( + name=user.id, + cells=[user.first_name, user.last_name, user.username, user.company] + ) for user in users], + downloadable=True, + resettable=True, + height='800px' + ) + ]) + + await q.page.save() diff --git a/examples/table_download.py b/examples/table_download.py new file mode 100644 index 0000000000000000000000000000000000000000..a2797a925fae64fd0b410fc6a25ec0d7373bb4ee --- /dev/null +++ b/examples/table_download.py @@ -0,0 +1,60 @@ +# Table / Download +# Allow downloading a table's data as CSV file. +# #table #download +# --- +import random +from faker import Faker +from h2o_wave import main, app, Q, ui + +fake = Faker() + +_id = 0 + + +class Issue: + def __init__(self, text: str, status: str, progress: float, icon: str, notifications: str): + global _id + _id += 1 + self.id = f'I{_id}' + self.text = text + self.status = status + self.views = 0 + self.progress = progress + self.icon = icon + self.notifications = notifications + + +# Create some issues +issues = [ + Issue( + text=fake.sentence(), + status=('Closed' if i % 2 == 0 else 'Open'), + progress=random.random(), + icon=('BoxCheckmarkSolid' if random.random() > 0.5 else 'BoxMultiplySolid'), + notifications=('Off' if random.random() > 0.5 else 'On')) for i in range(100) +] + +# Create columns for our issue table. +columns = [ + ui.table_column(name='text', label='Issue'), + ui.table_column(name='status', label='Status'), + ui.table_column(name='notifications', label='Notifications'), + ui.table_column(name='done', label='Done', cell_type=ui.icon_table_cell_type()), + ui.table_column(name='views', label='Views'), + ui.table_column(name='progress', label='Progress', cell_type=ui.progress_table_cell_type()), +] + + +@app('/demo') +async def serve(q: Q): + q.page['form'] = ui.form_card(box='1 1 -1 7', items=[ + ui.table( + name='issues', + columns=columns, + rows=[ui.table_row( + name=issue.id, cells=[issue.text, issue.status, issue.notifications, issue.icon, str(issue.views), + str(issue.progress)]) for issue in issues], + downloadable=True, + ) + ]) + await q.page.save() diff --git a/examples/table_events_select.py b/examples/table_events_select.py new file mode 100644 index 0000000000000000000000000000000000000000..d66d489e47f684a2d03c1f2ccfcf9e363406b7f0 --- /dev/null +++ b/examples/table_events_select.py @@ -0,0 +1,27 @@ +# Table / Events / Select +# Register the `select` #event to emit Wave event on each #table row selection. +# #table #events #select +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.events.table and q.events.table.select: + q.page['description'].content = f'{q.events.table.select}' + else: + q.page['table'] = ui.form_card(box='1 1 3 4', items=[ + ui.table( + name='table', + columns=[ui.table_column(name='text', label='Table select event')], + rows=[ + ui.table_row(name='row1', cells=['Row 1']), + ui.table_row(name='row2', cells=['Row 2']), + ui.table_row(name='row3', cells=['Row 3']) + ], + multiple=True, + events=['select'] + ) + ]) + q.page['description'] = ui.markdown_card(box='4 1 3 4', title='Selected rows', content='Nothing selected yet.') + await q.page.save() diff --git a/examples/table_filter.py b/examples/table_filter.py new file mode 100644 index 0000000000000000000000000000000000000000..817dacdaaf356e2207335ed38566d8fa4e30173a --- /dev/null +++ b/examples/table_filter.py @@ -0,0 +1,59 @@ +# Table / Filter +# Enable filtering values for specific columns. +# #table +# --- +import random +from faker import Faker +from h2o_wave import main, app, Q, ui + +fake = Faker() + +_id = 0 + + +class Issue: + def __init__(self, text: str, status: str, progress: float, icon: str, notifications: str): + global _id + _id += 1 + self.id = f'I{_id}' + self.text = text + self.status = status + self.views = 0 + self.progress = progress + self.icon = icon + self.notifications = notifications + + +# Create some issues +issues = [ + Issue( + text=fake.sentence(), + status=('Closed' if i % 2 == 0 else 'Open'), + progress=random.random(), + icon=('BoxCheckmarkSolid' if random.random() > 0.5 else 'BoxMultiplySolid'), + notifications=('Off' if random.random() > 0.5 else 'On')) for i in range(100) +] + +# Create columns for our issue table. +columns = [ + ui.table_column(name='text', label='Issue'), + ui.table_column(name='status', label='Status', filterable=True), + ui.table_column(name='notifications', label='Notifications', filterable=True), + ui.table_column(name='done', label='Done', cell_type=ui.icon_table_cell_type()), + ui.table_column(name='views', label='Views'), + ui.table_column(name='progress', label='Progress', cell_type=ui.progress_table_cell_type()), +] + + +@app('/demo') +async def serve(q: Q): + q.page['form'] = ui.form_card(box='1 1 -1 7', items=[ + ui.table( + name='issues', + columns=columns, + rows=[ui.table_row( + name=issue.id, + cells=[issue.text, issue.status, issue.notifications, issue.icon, str(issue.views), str(issue.progress)]) for issue in issues] + ) + ]) + await q.page.save() diff --git a/examples/table_filter_backend.py b/examples/table_filter_backend.py new file mode 100644 index 0000000000000000000000000000000000000000..4c93237e7c0b923168c1321f0508fc21ced5fec1 --- /dev/null +++ b/examples/table_filter_backend.py @@ -0,0 +1,53 @@ +# Table / Filter / Backend +# Filter table using Python. +# #table +# --- +import pandas as pd +from faker import Faker +from h2o_wave import main, app, Q, ui + +fake = Faker() + +N = 50 # number of rows + +# Make a synthetic data frame +addresses = pd.DataFrame(dict( + ID=[i + 1 for i in range(N)], + Name=[fake.name() for _ in range(N)], + Language=[fake.language_name() for _ in range(N)], + Job=[fake.job() for _ in range(N)], + Address=[fake.address() for _ in range(N)], + City=[fake.city() for _ in range(N)], +)) + +column_names = ['ID', 'Name', 'Language', 'Job', 'Address', 'City'] + + +def df_to_rows(df: pd.DataFrame): + return [ui.table_row(str(row['ID']), [str(row[name]) for name in column_names]) for i, row in df.iterrows()] + + +def search_df(df: pd.DataFrame, term: str): + str_cols = df.select_dtypes(include=[object]) + return df[str_cols.apply(lambda column: column.str.contains(term, case=False, na=False)).any(axis=1)] + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['form'] = ui.form_card(box='1 1 -1 7', items=[ + ui.textbox(name='search', label='Search address', placeholder='Enter a keyword...', trigger=True), + ui.table( + name='issues', + columns=[ui.table_column(name=name, label=name) for name in column_names], + rows=df_to_rows(addresses) + ) + ]) + q.client.initialized = True + else: + table = q.page['form'].issues + term: str = q.args.search + term = term.strip() if term else '' + table.rows = df_to_rows(search_df(addresses, term) if len(term) else addresses) + + await q.page.save() diff --git a/examples/table_groupby.py b/examples/table_groupby.py new file mode 100644 index 0000000000000000000000000000000000000000..4a8c8f80634700a0a98f61ea02c5a124793d0cc2 --- /dev/null +++ b/examples/table_groupby.py @@ -0,0 +1,60 @@ +# Table / Group by +# Allow grouping a table by column values. +# #table +# --- +import random +from faker import Faker +from h2o_wave import main, app, Q, ui + +fake = Faker() + +_id = 0 + + +class Issue: + def __init__(self, text: str, status: str, progress: float, icon: str, notifications: str): + global _id + _id += 1 + self.id = f'I{_id}' + self.text = text + self.status = status + self.views = 0 + self.progress = progress + self.icon = icon + self.notifications = notifications + + +# Create some issues +issues = [ + Issue( + text=fake.sentence(), + status=('Closed' if i % 2 == 0 else 'Open'), + progress=random.random(), + icon=('BoxCheckmarkSolid' if random.random() > 0.5 else 'BoxMultiplySolid'), + notifications=('Off' if random.random() > 0.5 else 'On')) for i in range(100) +] + +# Create columns for our issue table. +columns = [ + ui.table_column(name='text', label='Issue'), + ui.table_column(name='status', label='Status'), + ui.table_column(name='notifications', label='Notifications'), + ui.table_column(name='done', label='Done', cell_type=ui.icon_table_cell_type()), + ui.table_column(name='views', label='Views'), + ui.table_column(name='progress', label='Progress', cell_type=ui.progress_table_cell_type()), +] + + +@app('/demo') +async def serve(q: Q): + q.page['form'] = ui.form_card(box='1 1 -1 7', items=[ + ui.table( + name='issues', + columns=columns, + rows=[ui.table_row( + name=issue.id, + cells=[issue.text, issue.status, issue.notifications, issue.icon, str(issue.views), + str(issue.progress)]) for issue in issues], + groupable=True, + )]) + await q.page.save() diff --git a/examples/table_groups.py b/examples/table_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..e63530ef59542fc87f48852de92bc0e2dcf313ae --- /dev/null +++ b/examples/table_groups.py @@ -0,0 +1,28 @@ +# Table / Groups +# Manage data in custom groups +# #table +# --- + +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + q.page['form'] = ui.form_card(box='1 1 -1 6', items=[ + ui.table( + name='issues', + columns=[ui.table_column(name='text', label='Issues reported by')], + groups=[ + ui.table_group("Bob", [ + ui.table_row(name='row1', cells=['Issue1']), + ui.table_row(name='row2', cells=['Issue2']) + ]), + ui.table_group("John", [ + ui.table_row(name='row3', cells=['Issue3']), + ui.table_row(name='row4', cells=['Issue4']), + ui.table_row(name='row5', cells=['Issue5']), + ], collapsed=False)], + height='500px' + ) + ]) + await q.page.save() diff --git a/examples/table_markdown.py b/examples/table_markdown.py new file mode 100644 index 0000000000000000000000000000000000000000..0ffe022f96ff00da26c788d7b955c0a265bfd2ec --- /dev/null +++ b/examples/table_markdown.py @@ -0,0 +1,35 @@ +# Table / Markdown +# Creates a table with Markdown content. +# #table #markdown +# --- + +from h2o_wave import main, Q, ui, app + + +@app('/demo') +async def serve(q: Q): + q.page['example'] = ui.form_card(box='1 1 3 6', items=[ + ui.text_xl(content='Table with Markdown'), + ui.table( + name='table', + columns=[ + ui.table_column(name='description', label='Description', min_width='200', + cell_type=ui.markdown_table_cell_type(target='_blank')), + ui.table_column(name='markdown', label='Markdown', + cell_type=ui.markdown_table_cell_type(target='_blank')), + ], + height='450px', + rows=[ + ui.table_row(name='row1', cells=['Normal text', 'Hello World!']), + ui.table_row(name='row2', cells=['Bold text', 'This is a **bold** text.']), + ui.table_row(name='row3', cells=['Italicized text', 'This is a _italicized_ text.']), + ui.table_row(name='row4', cells=['Link', '']), + ui.table_row(name='row5', cells=['Absolute link with label', '[Wave website](http://wave.h2o.ai/)']), + ui.table_row(name='row6', cells=['Relative link with label', '[Go to /wave](/wave)']), + ui.table_row(name='row7', cells=['Email', '']), + ui.table_row(name='row8', cells=['Code', '``inline code``']), # change to monospaced font + ] + ) + ]) + + await q.page.save() diff --git a/examples/table_markdown_overflow.py b/examples/table_markdown_overflow.py new file mode 100644 index 0000000000000000000000000000000000000000..8f38197e6896924b409bb5cedbafcafbacfb538f --- /dev/null +++ b/examples/table_markdown_overflow.py @@ -0,0 +1,38 @@ +# Table / Markdown / Overflow +# Creates a table with Markdown content that overflows +# #table #markdown #overflow +# --- + +from h2o_wave import main, Q, ui, app + + +@app('/demo') +async def serve(q: Q): + q.page['example'] = ui.form_card(box='1 1 4 10', items=[ + ui.text_xl(content='Table with Markdown Overflow'), + ui.table( + name='table', + columns=[ + ui.table_column(name='markdown', label='Markdown (No overflow)', + sortable=True, searchable=True, max_width='250'), + ui.table_column(name='markdown_tooltip', label='Tooltip', + sortable=True, searchable=True, max_width='70', cell_overflow='tooltip', + cell_type=ui.markdown_table_cell_type(target='_blank')), + ui.table_column(name='markdown_wrap', label='Wrap', max_width = '70', + cell_type=ui.markdown_table_cell_type(target='_blank'), cell_overflow='wrap'), + ], + height='800px', + rows=[ + ui.table_row(name='row1', cells=['Normal text', 'Hello World! Make a tooltip!', 'Hello World! Wrap this!']), + ui.table_row(name='row2', cells=['Bold text', 'This is a **bold** text.', 'This is a **bold** text.']), + ui.table_row(name='row3', cells=['Italicized text', 'This is a _italicized_ text.', 'This is a _italicized_ text.']), + ui.table_row(name='row4', cells=['Link', '', '']), + ui.table_row(name='row5', cells=['Absolute link with label', '[Wave website as tooltip](http://wave.h2o.ai/)', '[Wave website wrapped](http://wave.h2o.ai/)']), + ui.table_row(name='row6', cells=['Relative link with label', '[Go to /wave](/wave)', '[Go to /wave](/wave)']), + ui.table_row(name='row7', cells=['Email', '', '']), + ui.table_row(name='row8', cells=['Code', '``inline code with tooltip``', '``inline code that wraps``']), # change to monospaced font + ] + ) + ]) + + await q.page.save() diff --git a/examples/table_markdown_pandas.py b/examples/table_markdown_pandas.py new file mode 100644 index 0000000000000000000000000000000000000000..3b879c68c06de57712f360f1863633aae21cad25 --- /dev/null +++ b/examples/table_markdown_pandas.py @@ -0,0 +1,39 @@ +# Table / Markdown / Pandas +# Display a #pandas #dataframe as a #markdown #table. +# --- +from h2o_wave import site, ui +import pandas as pd + +df = pd.DataFrame({'A': 1., + 'B': pd.Timestamp('20130102'), + 'C': pd.Series(1, index=list(range(4)), dtype='float32'), + 'D': pd.np.array([3] * 4, dtype='int32'), + 'E': pd.Categorical(["test", "train", "test", "train"]), + 'F': 'foo'}) + + +def make_markdown_row(values): + return f"| {' | '.join([str(x) for x in values])} |" + + +def make_markdown_table(fields, rows): + return '\n'.join([ + make_markdown_row(fields), + make_markdown_row('-' * len(fields)), + '\n'.join([make_markdown_row(row) for row in rows]), + ]) + + +page = site['/demo'] + +v = page.add('example', ui.form_card( + box='1 1 4 5', + items=[ + ui.text(make_markdown_table( + fields=df.columns.tolist(), + rows=df.values.tolist(), + )), + ], +)) + +page.save() diff --git a/examples/table_menu.py b/examples/table_menu.py new file mode 100644 index 0000000000000000000000000000000000000000..a39127015da42b2f407b3ace6d8ae851c0f23ff9 --- /dev/null +++ b/examples/table_menu.py @@ -0,0 +1,64 @@ +# Table / Menu +# Allow group of commands with context menu for each row. +# #table #commands #menu +# --- +from h2o_wave import main, app, Q, ui +from faker import Faker + +fake = Faker() + + +class TableRow: + _id = 0 + + def __init__(self): + TableRow._id += 1 + self.id = f'row_{TableRow._id}' + self.name = f'{fake.first_name()} {fake.last_name()}' + self.details = fake.sentence() + + +def show_table(q) -> None: + q.page['example'] = ui.form_card(box='1 1 4 4', items=[ + ui.table( + name='table', + columns=[ + ui.table_column(name='name', label='Name'), + ui.table_column( + name='actions', label='Actions', + cell_type=ui.menu_table_cell_type(name='commands', commands=[ + ui.command(name='details', label='Details'), + ui.command(name='delete', label='Delete'), + ]) + ) + ], + rows=[ui.table_row(name=r.id, cells=[r.name]) for r in q.client.rows] + ) + ]) + + +@app('/demo') +async def serve(q: Q): + if not q.app.initialized: + q.app.rows = [TableRow() for _ in range(3)] + q.app.initialized = True + if not q.client.initialized: + q.client.rows = q.app.rows + show_table(q) + q.client.initialized = True + + if q.args.delete: + q.client.rows = [row for row in q.client.rows if row.id != q.args.delete] + q.page['example'].table.rows = [ui.table_row(name=r.id, cells=[r.name]) for r in q.client.rows] + if q.args.details: + for row in q.client.rows: + if row.id == q.args.details: + q.page['example'] = ui.form_card(box='1 1 4 4', items=[ + ui.text(name='details', content=row.details), + ui.button(name='back', label='Back') + ]) + break + if q.args.back: + show_table(q) + + await q.page.save() diff --git a/examples/table_pagination.py b/examples/table_pagination.py new file mode 100644 index 0000000000000000000000000000000000000000..e036809c1f4373bc4503a830e6ee7367c601284d --- /dev/null +++ b/examples/table_pagination.py @@ -0,0 +1,110 @@ +# Table / Pagination +# Use a paginated #table to display large (100k+ rows) tabular data. +# #form #table #pagination +# --- + +import os +from typing import Dict, List +from h2o_wave import main, app, Q, ui +from copy import deepcopy +import csv + + +# Create a dummy data blueprint. +class Issue: + def __init__(self, text: str, status: str): + self.text = text + self.status = status + + +all_rows = [Issue(text=i + 1, status=('Closed' if i % 2 == 0 else 'Open')) for i in range(100)] +rows_per_page = 10 +total_rows = len(all_rows) + + +def get_rows(base: List, sort: Dict[str, bool] = None, search: Dict = None, filters: Dict[str, List[str]] = None) -> List: + # Make a deep copy in order to not mutate the original `all_issues` which serves as our baseline. + rows = deepcopy(base) + + # Sort by multiple columns. + if sort: + for col, reverse in sort.items(): + rows.sort(key=lambda i: getattr(i, col), reverse=reverse) + # Filter out all rows that do not contain searched string. + if search: + search_val = search['value'].lower() + cols = search['cols'] + rows = [row for row in rows if any(search_val in str(getattr(row, col)).lower() for col in cols)] + # Filter out rows that do not contain filtered column value. + if filters: + for col, filters in filters.items(): + rows = [row for row in rows if not filters or any(f in getattr(row, col) for f in filters)] + + return rows + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['meta'] = ui.meta_card(box='') + q.page['form'] = ui.form_card(box='1 1 -1 -1', items=[ + ui.table( + name='table', + columns=[ + ui.table_column(name='text', label='Text', sortable=True, searchable=True, link=False), + ui.table_column(name='status', label='Status', filterable=True, filters=['Open', 'Closed']), + ], + rows=[ui.table_row(str(r.text), [str(r.text), r.status]) for r in get_rows(all_rows)[0:rows_per_page]], + resettable=True, + downloadable=True, + pagination=ui.table_pagination(total_rows=len(all_rows), rows_per_page=rows_per_page), + # Make sure to register the necessary events for the feature you want to support, e.g. sorting. + # All the registered events have to be handled by the developer. + # `page_change` event is required to be handled for pagination to work. + events=['sort', 'filter', 'search', 'page_change', 'download', 'reset'] + ) + ]) + q.client.initialized = True + + # Check if user triggered any table action and save it to local state for allowing multiple + # actions to be performed on the data at the same time, e.g. sort the filtered data etc. + if q.events.table: + table = q.page['form'].table + if q.events.table.sort: + q.client.sort = q.events.table.sort + q.client.page_offset = 0 + if q.events.table.filter: + q.client.filters = q.events.table.filter + q.client.page_offset = 0 + if q.events.table.search is not None: + q.client.search = q.events.table.search + q.client.page_offset = 0 + if q.events.table.page_change: + q.client.page_offset = q.events.table.page_change.get('offset', 0) + if q.events.table.reset: + q.client.search = None + q.client.sort = None + q.client.filters = None + q.client.page_offset = 0 + table.pagination = ui.table_pagination(total_rows, rows_per_page) + + rows = get_rows(all_rows, q.client.sort, q.client.search, q.client.filters) + offset = q.client.page_offset or 0 + table.rows = [ui.table_row(str(r.text), [str(r.text), r.status]) for r in rows[offset: offset + rows_per_page]] + + # Update table pagination according to the new row count. + if q.client.search is not None or q.client.filters: + table.pagination = ui.table_pagination(len(rows), rows_per_page) + + if q.events.table.download: + # For multi-user apps, the tmp file name should be unique for each user, not hardcoded. + with open('data_download.csv', 'w') as csvfile: + csv_writer = csv.writer(csvfile, delimiter=',') + for r in rows: + csv_writer.writerow([r.text, r.status]) + download_url, = await q.site.upload(['data_download.csv']) + # Clean up the file after upload. + os.remove('data_download.csv') + q.page['meta'].script = ui.inline_script(f'window.open("{download_url}")') + + await q.page.save() diff --git a/examples/table_pagination_download.py b/examples/table_pagination_download.py new file mode 100644 index 0000000000000000000000000000000000000000..42e2f0d9b9d1b0aaab46b1cbfac9e036b8c78415 --- /dev/null +++ b/examples/table_pagination_download.py @@ -0,0 +1,50 @@ +# Table / Pagination / Download +# Use a #table with pagination to display large (100k+ rows) tabular data and provide data download option. +# #form #table #pagination #download +# --- + +from h2o_wave import main, app, Q, ui +import csv + + +rows = [str(i + 1) for i in range(100)] +rows_per_page = 10 + + +@app('/demo') +async def serve(q: Q): + if not q.app.initialized: + # Allow downloading all data since no filters/search/sort is allowed. + # Create and upload a CSV file for downloads. + # For multi-user apps, the tmp file name should be unique for each user, not hardcoded. + with open('data_download.csv', 'w') as csvfile: + csv_writer = csv.writer(csvfile, delimiter=',') + for r in rows: + csv_writer.writerow([r]) + q.app.data_download, = await q.site.upload(['data_download.csv']) + q.app.initialized = True + + if not q.client.initialized: + q.page['meta'] = ui.meta_card(box='') + q.page['form'] = ui.form_card(box='1 1 -1 -1', items=[ + ui.table( + name='table', + columns=[ui.table_column(name='text', label='Text', link=False)], + rows=[ui.table_row(name=r, cells=[r]) for r in rows[0:rows_per_page]], + pagination=ui.table_pagination(total_rows=len(rows), rows_per_page=rows_per_page), + height='580px', + downloadable=True, + events=['page_change', 'download'] + ) + ]) + q.client.initialized = True + + if q.events.table: + if q.events.table.download: + q.page['meta'].script = ui.inline_script(f'window.open("{q.app.data_download}")') + if q.events.table.page_change: + offset = q.events.table.page_change.get('offset', 0) + new_rows = rows[offset:offset + rows_per_page] + q.page['form'].table.rows = [ui.table_row(name=r, cells=[r]) for r in new_rows] + + await q.page.save() diff --git a/examples/table_pagination_filter.py b/examples/table_pagination_filter.py new file mode 100644 index 0000000000000000000000000000000000000000..7b1a797540d1a38d728df0846cf40369bd9fd20c --- /dev/null +++ b/examples/table_pagination_filter.py @@ -0,0 +1,54 @@ +# Table / Pagination / Filter +# Use a #table with pagination to display large (100k+ rows) tabular data and allow filtering along the way. +# #form #table #pagination #filter +# --- + +from h2o_wave import main, app, Q, ui + + +class Issue: + def __init__(self, text: str, status: str): + self.text = text + self.status = status + + +issues = [Issue(str(i), 'Open' if i % 2 == 0 else 'Closed') for i in range(100)] +rows_per_page = 10 + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['form'] = ui.form_card(box='1 1 -1 -1', items=[ + ui.table( + name='table', + columns=[ + ui.table_column(name='text', label='Text', link=False), + ui.table_column(name='status', label='Status', filterable=True, filters=['Open', 'Closed']), + ], + rows=[ui.table_row(name=i.text, cells=[i.text, i.status]) for i in issues[0:rows_per_page]], + pagination=ui.table_pagination(total_rows=len(issues), rows_per_page=rows_per_page), + height='580px', + events=['page_change', 'filter'] + ) + ]) + q.client.initialized = True + + if q.events.table: + offset = 0 + filtered = None + active_filter = q.events.table.filter or q.client.filter + if active_filter: + q.client.filter = active_filter + for col, filters in active_filter.items(): + filtered = [i for i in issues if not filters or any(f in getattr(i, col) for f in filters)] + if q.events.table.page_change: + offset = q.events.table.page_change.get('offset', 0) + + next_issues = filtered[offset:offset + rows_per_page] if filtered else issues[offset:offset + rows_per_page] + + table = q.page['form'].table + table.rows = [ui.table_row(name=i.text, cells=[i.text, i.status]) for i in next_issues] + table.pagination = ui.table_pagination(len(filtered) if filtered else len(issues), rows_per_page) + + await q.page.save() diff --git a/examples/table_pagination_groups.py b/examples/table_pagination_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..ffcd8131a55819a45d41cced920150eaaf7f2e54 --- /dev/null +++ b/examples/table_pagination_groups.py @@ -0,0 +1,36 @@ +# Table / Pagination / Groups +# Use a paginated #table to display large (100k+ rows) tabular data managed in custom groups. +# #form #table #pagination #groups +# --- + +from h2o_wave import main, app, Q, ui + +groups = [ui.table_group(f"Group-{i + 1}", [ + ui.table_row(name=f"row{i * 3 + 1}", cells=[f"Item-{i * 3 + 1}"]), + ui.table_row(name=f"row{i * 3 + 2}", cells=[f"Item-{i * 3 + 2}"]), + ui.table_row(name=f"row{i * 3 + 3}", cells=[f"Item-{i * 3 + 3}"]), +]) for i in range(100)] +groups_per_page = 10 + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['form'] = ui.form_card(box='1 1 -1 -1', items=[ + ui.table( + name='table', + columns=[ui.table_column(name='text', label='Text', link=False)], + groups=groups[0:groups_per_page], + pagination=ui.table_pagination(total_rows=len(groups), rows_per_page=groups_per_page), + height='590px', + events=['page_change'] + ) + ]) + q.client.initialized = True + + if q.events.table and q.events.table.page_change: + offset = q.events.table.page_change.get('offset', 0) + new_groups = groups[offset:offset + groups_per_page] + q.page['form'].table.groups = new_groups + + await q.page.save() diff --git a/examples/table_pagination_h2o3.py b/examples/table_pagination_h2o3.py new file mode 100644 index 0000000000000000000000000000000000000000..730078420857c329fcf31fda1bc26321c839fb23 --- /dev/null +++ b/examples/table_pagination_h2o3.py @@ -0,0 +1,235 @@ +# Table / Pagination / H2O-3 Dataframe +# Use a paginated #table to display large (100m+ rows) tabular data using a H2O-3 dataframe. +# #form #table #pagination #h2o3 +# --- + +import os +from time import time + +import h2o +from h2o_wave import Q, app, main, ui +from loguru import logger + +# This example requires H2O-3 to be running. + + +@app("/demo") +async def serve(q: Q): + logger.info(q.args) + logger.info(q.events) + + if not q.app.initialized: + # This is called the first time our app runs + # Variables created here will be the same of all users of the app + # Save a direct link to our H2O Dataframe for all users to use throughout the app + try: + h2o.connect(url="http://127.0.0.1:54321") + except: + q.page['err'] = ui.form_card(box='1 1 4 2', items=[ + ui.message_bar(type='error', text='Could not connect to H2O3. Please ensure H2O3 is running.'), + ]) + await q.page.save() + logger.error("H2O-3 is not running") + return + q.app.h2o_df = h2o.get_frame("py_6_sid_aff3") + + # EXAMPLE OF CREATING A LARGE DATAFRAME + # h2o_df = h2o.create_frame( + # rows=1000000, + # cols=5, + # categorical_fraction=0.6, + # integer_fraction=0, + # binary_fraction=0, + # real_range=100, + # integer_range=100, + # missing_fraction=0, + # seed=1234, + # ) + + q.app.rows_per_page = 10 # TODO: How many rows do you want to show users at a time + + # A list of booleans for if a column is sortable or not, by default + # we allow all and only numeric columns to be sorted based on H2O-3 functionality + # TODO: You may want to make a hardcoded list of [True, False] for your own use cases + q.app.column_sortable = q.app.h2o_df.isnumeric() + + # A list of booleans for if a column is filterable or not, by default, + # we allow all and only categorical columns to be sorted based on H2O-3 functionality + # TODO: You may want to make a hardcoded list of [True, False] for your own use cases + q.app.column_filterable = q.app.h2o_df.isfactor() + + # A list of booleans for if a column is searchable or not, by default, + # we allow all and only categorical and string columns to be sorted based on H2O-3 functionality + # TODO: You may want to make a hardcoded list of [True, False] for your own use cases + q.app.column_searchable = q.app.h2o_df.isfactor() + q.app.h2o_df.isstring() + + q.app.initialized = True + + if not q.client.initialized: + # This is called for each new browser that visits the app + # Multiple users can interact with the table at the same time without interrupting each other + # Users can make multiple changes to the table such as sorting and filtering + + q.client.search = None + q.client.sort = None + q.client.filters = None + q.client.page_offset = 0 + q.client.total_rows = len(q.app.h2o_df) + + # Create the default UI for this user + q.page["meta"] = ui.meta_card(box="") + q.page["table_card"] = ui.form_card( + box="1 1 -1 -1", + items=[ + ui.table( + name="h2o_table", # TODO: if you change this, you need to remember to update the serve function + columns=[ + ui.table_column( + name=q.app.h2o_df.columns[i], + label=q.app.h2o_df.columns[i], + sortable=q.app.column_sortable[i], + filterable=q.app.column_filterable[i], + searchable=q.app.column_searchable[i], + ) + for i in range(len(q.app.h2o_df.columns)) + ], + rows=get_table_rows(q), + resettable=True, + downloadable=True, + pagination=ui.table_pagination( + total_rows=q.client.total_rows, + rows_per_page=q.app.rows_per_page, + ), + events=[ + "page_change", + "sort", + "filter", + "search", + "reset", + "download", + ], + ) + ], + ) + q.client.initialized = True + + # Check if user triggered any table action and save it to local state for allowing multiple + # actions to be performed on the data at the same time, e.g. sort the filtered data etc. + if q.events.h2o_table: + logger.info("table event occurred") + + if q.events.h2o_table.page_change: + logger.info(f"table page change: {q.events.h2o_table.page_change}") + q.client.page_offset = q.events.h2o_table.page_change.get("offset", 0) + + if q.events.h2o_table.sort: + logger.info(f"table sort: {q.events.h2o_table.sort}") + q.client.sort = q.events.h2o_table.sort + q.client.page_offset = 0 + + if q.events.h2o_table.filter: + logger.info(f"table filter: {q.events.h2o_table.filter}") + q.client.filters = q.events.h2o_table.filter + q.client.page_offset = 0 + + if q.events.h2o_table.search is not None: + logger.info(f"table search: {q.events.h2o_table.search}") + q.client.search = q.events.h2o_table.search + q.client.page_offset = 0 + + if q.events.h2o_table.download: + await download_h2o_table(q) + + if q.events.h2o_table.reset: + logger.info("table reset") + q.client.search = None + q.client.sort = None + q.client.filters = None + q.client.page_offset = 0 + q.client.total_rows = len(q.app.h2o_df) + + # Update the rows in our UI + # TODO: if you change where your table is located, this needs updating + q.page["table_card"].h2o_table.rows = get_table_rows(q) + q.page["table_card"].h2o_table.pagination.total_rows = q.client.total_rows + + await q.page.save() + + +def get_table_rows(q: Q): + logger.info( + f"Creating new table for rows: {q.client.page_offset} to {q.client.page_offset + q.app.rows_per_page}" + ) + + working_frame = prepare_h2o_data(q) + + # Bring our limited UI rows locally to pandas to prepare for our ui.table + local_df = working_frame[ + q.client.page_offset:q.client.page_offset + q.app.rows_per_page, : + ].as_data_frame() + q.client.total_rows = len(working_frame) + + table_rows = [ + ui.table_row( + name=str( + q.client.page_offset + i + ), # name is the index on the h2o dataframe for appropriate lookup + cells=[str(local_df[col].values[i]) for col in local_df.columns.to_list()], + ) + for i in range(len(local_df)) + ] + + h2o.remove(working_frame) # remove our duplicate work + + return table_rows + + +async def download_h2o_table(q: Q): + # Create a unique file name as this is a multi-user app + local_file_path = f"h2o3_data_{str(int(time()))}.csv" + working_frame = prepare_h2o_data(q) + + h2o.download_csv(working_frame, local_file_path) + (wave_file_path,) = await q.site.upload([local_file_path]) + os.remove(local_file_path) + + q.page["meta"].script = ui.inline_script(f'window.open("{wave_file_path}")') + + +def prepare_h2o_data(q: Q): + + # This is used to prep the data we want to show on the screen or download, so it gets its own function + # If you have 5 users at the same time, there will be 6 large dataframes in h2o3 - ensure proper cluster size + working_frame = h2o.deep_copy(q.app.h2o_df, "working_df") + + if q.client.sort is not None: + # H2O-3 can only sort numeric values - if the developer allows users to sort + # string columns the end users will see unexpected results + + working_frame = working_frame.sort( + by=list(q.client.sort.keys()), ascending=list(q.client.sort.values()) + ) + + if q.client.filters is not None: + + for key in q.client.filters.keys(): + working_frame = working_frame[ + working_frame[key].match(q.client.filters[key]) + ] + + if q.client.search is not None: + # We check if our search term is in any of the searchable columns + # Start with and index of 0s and then filter to only keep rows with index > 0 + + index = h2o.create_frame( + rows=len(working_frame), cols=1, integer_fraction=1, integer_range=1 + ) + index["C1"] = 0 + for i in range(len(q.app.h2o_df.columns)): + if q.app.column_searchable[i]: + index = index + working_frame[q.app.h2o_df.columns[i]].grep( + pattern=q.client.search, ignore_case=True, output_logical=True + ) + + working_frame = working_frame[index] + return working_frame diff --git a/examples/table_pagination_minimal.py b/examples/table_pagination_minimal.py new file mode 100644 index 0000000000000000000000000000000000000000..33944a2654ad94a1aefa4366fcb5d1f492d3ea24 --- /dev/null +++ b/examples/table_pagination_minimal.py @@ -0,0 +1,33 @@ +# Table / Pagination / Minimal +# Use a #table with pagination to display large (100k+ rows) tabular data. +# #form #table #pagination +# --- + +from h2o_wave import main, app, Q, ui + + +rows = [str(i + 1) for i in range(100)] +rows_per_page = 10 + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['form'] = ui.form_card(box='1 1 -1 -1', items=[ + ui.table( + name='table', + columns=[ui.table_column(name='text', label='Text', link=False)], + rows=[ui.table_row(name=r, cells=[r]) for r in rows[0:rows_per_page]], + pagination=ui.table_pagination(total_rows=len(rows), rows_per_page=rows_per_page), + height='580px', + events=['page_change'] + ) + ]) + q.client.initialized = True + + if q.events.table and q.events.table.page_change: + offset = q.events.table.page_change.get('offset', 0) + new_rows = rows[offset:offset + rows_per_page] + q.page['form'].table.rows = [ui.table_row(name=r, cells=[r]) for r in new_rows] + + await q.page.save() diff --git a/examples/table_pagination_pandas.py b/examples/table_pagination_pandas.py new file mode 100644 index 0000000000000000000000000000000000000000..0b1e4e747d84b3f1bfb41f565342e73f4e2e6ac4 --- /dev/null +++ b/examples/table_pagination_pandas.py @@ -0,0 +1,109 @@ +# Table / Pagination / Pandas +# Use a paginated #table to display large (100k+ rows) tabular data using pandas dataframe. +# #form #table #pagination #pandas +# --- + +import os +from typing import Dict, List +from h2o_wave import main, app, Q, ui +import pandas as pd + + +all_issues_df = pd.DataFrame( + [[i + 1, 'Closed' if i % 2 == 0 else 'Open'] for i in range(100)], + columns=['text', 'status'] +) +rows_per_page = 10 +total_rows = len(all_issues_df) + + +def df_to_table_rows(df: pd.DataFrame) -> List[ui.TableRow]: + return [ui.table_row(name=str(r[0]), cells=[str(r[0]), r[1]]) for r in df.itertuples(index=False)] + + +def get_df(base: pd.DataFrame, sort: Dict[str, bool] = None, search: Dict = None, filters: Dict[str, List[str]] = None) -> pd.DataFrame: + # Make a deep copy in order to not mutate the original df which serves as our baseline. + df = base.copy() + + if sort: + # Reverse values since default sort of Wave table is different from Pandas. + ascending = [not v for v in list(sort.values())] + df = df.sort_values(by=list(sort.keys()), ascending=ascending) + # Filter out all rows that do not contain searched string in `text` cell. + if search: + search_val = search['value'].lower() + # Filter dataframe by search value case insensitive. + df = df[df.apply(lambda r: any(search_val in str(r[col]).lower() for col in search['cols']), axis=1)] + # Filter out rows that do not contain filtered column value. + if filters: + # We want only rows that have no filters applied or their col value matches active filters. + query = ' & '.join([f'({not bool(filters)} | {col} in {filters})' for col, filters in filters.items()]) + df = df.query(query) + + return df + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['meta'] = ui.meta_card(box='') + q.page['form'] = ui.form_card(box='1 1 -1 -1', items=[ + ui.table( + name='table', + columns=[ + ui.table_column(name='text', label='Text', sortable=True, searchable=True, link=False), + ui.table_column(name='status', label='Status', filterable=True, filters=['Open', 'Closed']), + ], + rows=df_to_table_rows(get_df(all_issues_df)[0:rows_per_page]), + resettable=True, + downloadable=True, + pagination=ui.table_pagination(total_rows, rows_per_page), + # Make sure to register the necessary events for the feature you want to support, e.g. sorting. + # All the registered events have to be handled by the developer. + # `page_change` event is required to be handled for pagination to work. + events=['sort', 'filter', 'search', 'page_change', 'download', 'reset'] + ) + ]) + q.client.initialized = True + + # Check if user triggered any table action and save it to local state for allowing multiple + # actions to be performed on the data at the same time, e.g. sort the filtered data etc. + if q.events.table: + table = q.page['form'].table + if q.events.table.sort: + q.client.sort = q.events.table.sort + q.client.page_offset = 0 + if q.events.table.filter: + q.client.filters = q.events.table.filter + q.client.page_offset = 0 + if q.events.table.search is not None: + q.client.search = q.events.table.search + q.client.page_offset = 0 + if q.events.table.page_change: + q.client.page_offset = q.events.table.page_change.get('offset', 0) + if q.events.table.reset: + q.client.search = None + q.client.sort = None + q.client.filters = None + q.client.page_offset = 0 + table.pagination = ui.table_pagination(total_rows, rows_per_page) + + offset = q.client.page_offset or 0 + df = get_df(all_issues_df, q.client.sort, q.client.search, q.client.filters) + + if q.events.table.download: + # Create and upload a CSV file for downloads. + # For multi-user apps, the tmp file name should be unique for each user, not hardcoded. + df.to_csv('data_download.csv') + download_url, = await q.site.upload(['data_download.csv']) + # Clean up. + os.remove('data_download.csv') + q.page['meta'].script = ui.inline_script(f'window.open("{download_url}")') + + # Update table pagination according to the new row count. + if q.client.search is not None or q.client.filters: + table.pagination = ui.table_pagination(len(df), rows_per_page) + + table.rows = df_to_table_rows(df[offset:offset + rows_per_page]) + + await q.page.save() diff --git a/examples/table_pagination_search.py b/examples/table_pagination_search.py new file mode 100644 index 0000000000000000000000000000000000000000..e413b70192a0ff6d0083aea19fadc89137c4635c --- /dev/null +++ b/examples/table_pagination_search.py @@ -0,0 +1,48 @@ +# Table / Pagination / Search +# Use a #table with pagination to display large (100k+ rows) tabular data and allow searching along the way. +# #form #table #pagination #search +# --- + +from h2o_wave import main, app, Q, ui + + +class Issue: + def __init__(self, text: str): + self.text = text + + +issues = [Issue(str(i + 1)) for i in range(100)] +rows_per_page = 10 + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['form'] = ui.form_card(box='1 1 -1 -1', items=[ + ui.table( + name='table', + columns=[ui.table_column(name='text', label='Text', searchable=True, link=False)], + rows=[ui.table_row(name=i.text, cells=[i.text]) for i in issues[0:rows_per_page]], + pagination=ui.table_pagination(total_rows=len(issues), rows_per_page=rows_per_page), + height='660px', + events=['page_change', 'search'] + ) + ]) + q.client.initialized = True + + if q.events.table: + offset = 0 + searched = issues + search = q.events.table.search or q.client.search + if search is not None: + search_val = search['value'].lower() + searched = [i for i in issues if any(search_val in str(getattr(i, col)).lower() for col in search['cols'])] + q.client.search = search + if q.events.table.page_change: + offset = q.events.table.page_change.get('offset', 0) + + table = q.page['form'].table + table.rows = [ui.table_row(name=i.text, cells=[i.text]) for i in searched[offset:offset + rows_per_page]] + table.pagination = ui.table_pagination(len(searched), rows_per_page) + + await q.page.save() diff --git a/examples/table_pagination_sort.py b/examples/table_pagination_sort.py new file mode 100644 index 0000000000000000000000000000000000000000..30105ce1b3a2cc4cb512525651e040dcb45a8bf1 --- /dev/null +++ b/examples/table_pagination_sort.py @@ -0,0 +1,45 @@ +# Table / Pagination / Sort +# Use a #table with pagination to display large (100k+ rows) tabular data and allow sorting along the way. +# #form #table #pagination #sort +# --- + +from h2o_wave import main, app, Q, ui + + +# Create a dummy data blueprint. +class Issue: + def __init__(self, text: str): + self.text = text + + +issues = [Issue(i + 1) for i in range(100)] +rows_per_page = 10 + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['form'] = ui.form_card(box='1 1 -1 -1', items=[ + ui.table( + name='table', + columns=[ui.table_column(name='text', label='Text', sortable=True, link=False)], + rows=[ui.table_row(name=str(i.text), cells=[str(i.text)]) for i in issues[0:rows_per_page]], + pagination=ui.table_pagination(total_rows=len(issues), rows_per_page=rows_per_page), + height='580px', + events=['page_change', 'sort'] + ) + ]) + q.client.initialized = True + + if q.events.table: + offset = 0 + if q.events.table.sort: + for col, reverse in q.events.table.sort.items(): + issues.sort(key=lambda i: getattr(i, col), reverse=reverse) + if q.events.table.page_change: + offset = q.events.table.page_change.get('offset', 0) + + next_issues = issues[offset:offset + rows_per_page] + q.page['form'].table.rows = [ui.table_row(name=str(i.text), cells=[str(i.text)]) for i in next_issues] + + await q.page.save() diff --git a/examples/table_pagination_wavedb.py b/examples/table_pagination_wavedb.py new file mode 100644 index 0000000000000000000000000000000000000000..5416a1099ceb9385f9d53cc065307a8739f83875 --- /dev/null +++ b/examples/table_pagination_wavedb.py @@ -0,0 +1,153 @@ +import os +from typing import List +from h2o_wave import main, app, Q, ui, connect +import csv + + +rows_per_page = 10 + +async def get_rows(q: Q, table = 'issues', columns = ['text', 'status'], count_only = False) -> List: + sql_query = f'SELECT {"count(*)" if count_only else ", ".join(columns)} FROM {table}' + + substitution_values = [] + # Filter out all rows that do not contain searched string. + if q.client.search: + search_val = q.client.search['value'].lower() + cols = q.client.search['cols'] + like_statements = [] + for col in cols: + if col in columns: + substitution_values.append('%' + search_val + '%') + like_statements.append(f'{col} LIKE ?') + + sql_query += ' WHERE (' + ' OR '.join(like_statements) + ')' + + # Filter out rows that do not contain filtered column value. + if q.client.filters: + filter_queries = [] + for col, filters in q.client.filters.items(): + if col in columns: + like_statements = [] + for f in filters: + substitution_values.append(f'%{f}%') + like_statements.append(f'{col} LIKE ?') + if like_statements: + filter_queries.append(' OR '.join(like_statements)) + if filter_queries: + sql_query += ' AND ' if 'WHERE' in sql_query else ' WHERE ' + sql_query += ' AND '.join(filter_queries) + + # Sort by multiple columns. + if q.client.sort: + # NOTE: This example sorts alphabetically since only "text" col is sortable. + sort_statements = [] + for col, asc in q.client.sort.items(): + if col in columns: + sort_statements.append(f'{col} {"ASC" if asc else "DESC" }') + if sort_statements: + sql_query += ' ORDER BY ' + ', '.join(sort_statements) + + if not count_only: + sql_query += f' LIMIT {rows_per_page} OFFSET {q.client.page_offset or 0} ' + + results, err = await q.app.db.exec(sql_query, *substitution_values) + if err: + raise RuntimeError(f'Failed querying the table data: {err}') + + return results + + +# NOTICE: You need a running instance of https://wave.h2o.ai/docs/wavedb for this app to run. +@app('/demo') +async def serve(q: Q): + # Run once per app lifetime. + if not q.app.initialized: + # Create a database connection. + connection = connect() + q.app.db = connection['demo_db'] + # Check if there is any data in the database. + _, err = await q.app.db.exec('CREATE TABLE IF NOT EXISTS issues (text TEXT, status TEXT)') + if err: + raise RuntimeError(f'Failed setting up database: {err}') + results, err = await q.app.db.exec('SELECT COUNT(*) FROM issues') + if err: + raise RuntimeError(f'Failed querying the database: {err}') + # Populate DB data if necessary. + if results and results[0] and results[0][0] != 100: + insert_statements = [] + for i in range (1,101): + insert_statements.append(f'INSERT INTO issues (text, status) VALUES ("Text {i}", "{"Closed" if i % 2 == 0 else "Open"} ")') + _, err = await q.app.db.exec_many(*insert_statements) + if err: + raise RuntimeError(f'Failed querying the database: {err}') + q.app.initialized = True + + # Run once per browser tab lifetime. + if not q.client.initialized: + q.page['meta'] = ui.meta_card(box='') + total_rows, err = await q.app.db.exec('SELECT COUNT(*) FROM issues') + if err: + raise RuntimeError(f'Failed querying the database: {err}') + rows = await get_rows(q) + q.page['form'] = ui.form_card(box='1 1 -1 -1', items=[ + ui.table( + name='table', + columns=[ + ui.table_column(name='text', label='Text', sortable=True, searchable=True, link=False), + ui.table_column(name='status', label='Status', filterable=True, filters=['Open', 'Closed']), + ], + rows=[ui.table_row(r[0], [r[0], r[1]]) for r in rows], + resettable=True, + downloadable=True, + pagination=ui.table_pagination(total_rows=total_rows[0][0], rows_per_page=rows_per_page), + # Make sure to register the necessary events for the feature you want to support, e.g. sorting. + # All the registered events have to be handled by the developer. + # `page_change` event is required to be handled for pagination to work. + events=['sort', 'filter', 'search', 'page_change', 'download', 'reset'] + ) + ]) + q.client.initialized = True + + # Check if user triggered any table action and save it to local state for allowing multiple + # actions to be performed on the data at the same time, e.g. sort the filtered data etc. + if q.events.table: + table = q.page['form'].table + if q.events.table.sort: + q.client.sort = q.events.table.sort + q.client.page_offset = 0 + if q.events.table.filter: + q.client.filters = q.events.table.filter + q.client.page_offset = 0 + if q.events.table.search is not None: + q.client.search = q.events.table.search + q.client.page_offset = 0 + if q.events.table.page_change: + q.client.page_offset = q.events.table.page_change.get('offset', 0) + if q.events.table.reset: + q.client.search = None + q.client.sort = None + q.client.filters = None + q.client.page_offset = 0 + total_filtered_rows = await get_rows(q, count_only=True) + table.pagination = ui.table_pagination(total_filtered_rows[0][0], rows_per_page) + + rows = await get_rows(q) + table.rows = [ui.table_row(r[0], [r[0], r[1]]) for r in rows] + + # Update table pagination according to the new row count. + if q.client.search is not None or q.client.filters: + total_filtered_rows = await get_rows(q, count_only=True) + table.pagination = ui.table_pagination(total_filtered_rows[0][0], rows_per_page) + + if q.events.table.download: + # For multi-user apps, the tmp file name should be unique for each user, not hardcoded. + with open('data_download.csv', 'w') as csvfile: + csv_writer = csv.writer(csvfile, delimiter=',') + for r in rows: + csv_writer.writerow([r.text, r.status]) + download_url, = await q.site.upload(['data_download.csv']) + # Clean up the file after upload. + os.remove('data_download.csv') + q.page['meta'].script = ui.inline_script(f'window.open("{download_url}")') + + await q.page.save() diff --git a/examples/table_search.py b/examples/table_search.py new file mode 100644 index 0000000000000000000000000000000000000000..6bd037371ae9a80bed21bba5a89719f12fe60cec --- /dev/null +++ b/examples/table_search.py @@ -0,0 +1,59 @@ +# Table / Search +# Enable searching a #table across specific columns. #search +# --- +import random +from faker import Faker +from h2o_wave import main, app, Q, ui + +fake = Faker() + +_id = 0 + + +class Issue: + def __init__(self, text: str, status: str, progress: float, icon: str, notifications: str): + global _id + _id += 1 + self.id = f'I{_id}' + self.text = text + self.status = status + self.views = 0 + self.progress = progress + self.icon = icon + self.notifications = notifications + + +# Create some issues +issues = [ + Issue( + text=fake.sentence(), + status=('Closed' if i % 2 == 0 else 'Open'), + progress=random.random(), + icon=('BoxCheckmarkSolid' if random.random() > 0.5 else 'BoxMultiplySolid'), + notifications=('Off' if random.random() > 0.5 else 'On')) for i in range(100) +] + +# Create columns for our issue table. +columns = [ + ui.table_column(name='text', label='Issue', searchable=True), + ui.table_column(name='status', label='Status'), + ui.table_column(name='notifications', label='Notifications'), + ui.table_column(name='done', label='Done', cell_type=ui.icon_table_cell_type()), + ui.table_column(name='views', label='Views'), + ui.table_column(name='progress', label='Progress', cell_type=ui.progress_table_cell_type()), +] + + +@app('/demo') +async def serve(q: Q): + q.page['form'] = ui.form_card(box='1 1 -1 7', items=[ + ui.table( + name='issues', + columns=columns, + rows=[ui.table_row( + name=issue.id, + cells=[issue.text, issue.status, issue.notifications, issue.icon, str(issue.views), + str(issue.progress)]) for issue in issues], + ) + ]) + await q.page.save() diff --git a/examples/table_select_multiple.py b/examples/table_select_multiple.py new file mode 100644 index 0000000000000000000000000000000000000000..e804318bdd9fb522323f416cdf55663b33a1dc7d --- /dev/null +++ b/examples/table_select_multiple.py @@ -0,0 +1,32 @@ +# Table / Preselection / Multiple +# Use a #table as an advanced multi-select. To allow multiple #selection, +# specify the pre-selected row names in 'values' or simply specify the `multiple=True`. +# #table #selection +# --- +from h2o_wave import main, app, Q, ui + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'selected={q.args.table}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 -1 5', items=[ + ui.table( + name='table', + columns=[ui.table_column(name='text', label='Table multiple selection', min_width='300px')], + rows=[ + ui.table_row(name='row1', cells=['Row 1']), + ui.table_row(name='row2', cells=['Row 2']), + ui.table_row(name='row3', cells=['Row 3']), + ui.table_row(name='row4', cells=['Row 4']), + ui.table_row(name='row5', cells=['Row 5']) + ], + values=['row2','row4'], + ), + ui.button(name='show_inputs', label='Submit', primary=True) + ]) + await q.page.save() + diff --git a/examples/table_select_single.py b/examples/table_select_single.py new file mode 100644 index 0000000000000000000000000000000000000000..7e37db40c73bf3eb5482f63e34d4e1913f59fc41 --- /dev/null +++ b/examples/table_select_single.py @@ -0,0 +1,32 @@ +# Table / Preselection / Single +# Use a #table as an advanced single-select. To allow single #selection, +# specify the name of the row you want to pre-select in 'value' attribute +# or simply specify the `isSingle=True`. +# #table #selection +# --- +from h2o_wave import main, app, Q, ui + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'selected={q.args.table}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 -1 5', items=[ + ui.table( + name='table', + columns=[ui.table_column(name='text', label='Table single selection', min_width='300px')], + rows=[ + ui.table_row(name='row1', cells=['Row 1']), + ui.table_row(name='row2', cells=['Row 2']), + ui.table_row(name='row3', cells=['Row 3']), + ui.table_row(name='row4', cells=['Row 4']), + ui.table_row(name='row5', cells=['Row 5']) + ], + value='row2', + ), + ui.button(name='show_inputs', label='Submit', primary=True) + ]) + await q.page.save() diff --git a/examples/table_sort.py b/examples/table_sort.py new file mode 100644 index 0000000000000000000000000000000000000000..42074549d8441d70752498ffaa3ea0728b9198b8 --- /dev/null +++ b/examples/table_sort.py @@ -0,0 +1,59 @@ +# Table / Sort +# Allow sorting a #table by specific columns. +# --- +import random +from faker import Faker +from h2o_wave import main, app, Q, ui + +fake = Faker() + +_id = 0 + + +class Issue: + def __init__(self, text: str, status: str, progress: float, icon: str, notifications: str): + global _id + _id += 1 + self.id = f'I{_id}' + self.text = text + self.status = status + self.views = 0 + self.progress = progress + self.icon = icon + self.notifications = notifications + + +# Create some issues +issues = [ + Issue( + text=fake.sentence(), + status=('Closed' if i % 2 == 0 else 'Open'), + progress=random.random(), + icon=('BoxCheckmarkSolid' if random.random() > 0.5 else 'BoxMultiplySolid'), + notifications=('Off' if random.random() > 0.5 else 'On')) for i in range(100) +] + +# Create columns for our issue table. +columns = [ + ui.table_column(name='text', label='Issue', sortable=True, ), + ui.table_column(name='status', label='Status'), + ui.table_column(name='notifications', label='Notifications'), + ui.table_column(name='done', label='Done', cell_type=ui.icon_table_cell_type()), + ui.table_column(name='views', label='Views', sortable=True), + ui.table_column(name='progress', label='Progress', cell_type=ui.progress_table_cell_type()), +] + + +@app('/demo') +async def serve(q: Q): + q.page['form'] = ui.form_card(box='1 1 -1 7', items=[ + ui.table( + name='issues', + columns=columns, + rows=[ui.table_row( + name=issue.id, + cells=[issue.text, issue.status, issue.notifications, issue.icon, str(issue.views), + str(issue.progress)]) for issue in issues], + ) + ]) + await q.page.save() diff --git a/examples/table_tags.py b/examples/table_tags.py new file mode 100644 index 0000000000000000000000000000000000000000..775cdb22670f7392fc423ffdd6668201e1272ae9 --- /dev/null +++ b/examples/table_tags.py @@ -0,0 +1,43 @@ +# Table / Tags +# Use tags in order to emphasize a specific value. For multiple tags in a single row use `,` as a delimiter. +# --- +from faker import Faker +from h2o_wave import main, app, Q, ui + +fake = Faker() + +_id = 0 + + +class Issue: + def __init__(self, text: str, tag: str): + global _id + _id += 1 + self.id = f'I{_id}' + self.text = text + self.tag = tag + + +# Create some issues +issues = [Issue(text=fake.sentence(), tag=('FAIL' if i % 2 == 0 else 'DONE,SUCCESS')) for i in range(10)] + +columns = [ + ui.table_column(name='text', label='Issue', min_width='400px'), + ui.table_column(name='tag', label='Badge', cell_type=ui.tag_table_cell_type(name='tags', tags=[ + ui.tag(label='FAIL', color='$red'), + ui.tag(label='DONE', color='#D2E3F8', label_color='#053975'), + ui.tag(label='SUCCESS', color='$mint'), + ])), +] + + +@app('/demo') +async def serve(q: Q): + q.page['example'] = ui.form_card(box='1 1 -1 -1', items=[ + ui.table( + name='issues', + columns=columns, + rows=[ui.table_row(name=issue.id, cells=[issue.text, issue.tag]) for issue in issues], + ) + ]) + await q.page.save() diff --git a/examples/tabs.py b/examples/tabs.py new file mode 100644 index 0000000000000000000000000000000000000000..8edfd8f83532c3bfc880b2571fb4038a1770d399 --- /dev/null +++ b/examples/tabs.py @@ -0,0 +1,32 @@ +# Form / Tabs +# Use #tabs within a #form to navigate between two or more distinct content categories. +# #navigation +# --- +from h2o_wave import main, app, Q, ui + +tabs = [ + ui.tab(name='email', label='Mail', icon='Mail'), + ui.tab(name='events', label='Events', icon='Calendar'), + ui.tab(name='spam', label='Spam'), +] + + +@app('/demo') +async def serve(q: Q): + if q.args.menu: + q.page['example'].items = [ + ui.tabs(name='menu', value=q.args.menu, items=tabs), + get_tab_content(q.args.menu), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 7', items=[ + ui.tabs(name='menu', value='email', items=tabs), + get_tab_content('email'), + ]) + await q.page.save() + + +def get_tab_content(category: str): + # Return a checklist of dummy items. + items = [f'{category.title()} {i}' for i in range(1, 11)] + return ui.checklist(name='items', choices=[ui.choice(name=item, label=item) for item in items]) diff --git a/examples/tags.py b/examples/tags.py new file mode 100644 index 0000000000000000000000000000000000000000..02478dc05c4ece39d21592f3b077716434d78012 --- /dev/null +++ b/examples/tags.py @@ -0,0 +1,20 @@ +# Tags +# Display a set of tags in a row. Each tag consists of a box with text inside. +# Can be used in different scenarios including highlighting a specific keyword or holding a numeric value with +# different colors to indicate error, warning, or success. +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +page['example'] = ui.form_card( + box='1 1 2 2', + items=[ + ui.tags([ + ui.tag(color='#610404', label='Error'), + ui.tag(color='#7F6001', label='Warning'), + ui.tag(color='#054007', label='Success'), + ]) + ]) + +page.save() diff --git a/examples/tall_article_preview.py b/examples/tall_article_preview.py new file mode 100644 index 0000000000000000000000000000000000000000..e2f960e2ae178bb567c4d0983be8af30ad134166 --- /dev/null +++ b/examples/tall_article_preview.py @@ -0,0 +1,39 @@ +# Tall Article Preview +# Create a tall article preview card if you intend to show a little preview +# and allow user to click through to more content (specify 'name' attr). +# --- +from h2o_wave import main, app, Q, ui + + +content = ''' +### Sub Header + +Nunc scelerisque tincidunt elit. Vestibulum non mi ipsum. Cras pretium suscipit tellus sit ametsa aliquet. +''' + + +@app('/demo') +async def serve(q: Q): + if q.args.tall_article: + q.page['example'] = ui.form_card(box='1 1 4 6', items=[ + ui.button(name='back', label='Go back', primary=True), + ]) + else: + q.page['example'] = ui.tall_article_preview_card( + box='1 1 4 6', + title='Tall article preview', + subtitle='Click the card', + value='$19', + name='tall_article', + image='https://images.pexels.com/photos/3225517/pexels-photo-3225517.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260', # noqa + content=content, + items=[ + ui.buttons(items=[ + ui.button(name='like', label='Like'), + ui.button(name='comment', label='Comment'), + ui.button(name='share', label='Share'), + ]), + ] + ) + + await q.page.save() diff --git a/examples/tall_info.py b/examples/tall_info.py new file mode 100644 index 0000000000000000000000000000000000000000..987733ae0e84ba5f9b30462b0ac4760c9a3e431d --- /dev/null +++ b/examples/tall_info.py @@ -0,0 +1,24 @@ +# Info / Tall +# Create a tall information card displaying a title, caption, and either an icon or image. +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.info_card: + q.page['example'] = ui.form_card(box='1 1 2 4', items=[ + ui.button(name='back', label='Go back', primary=True), + ]) + else: + q.page['example'] = ui.tall_info_card( + box='1 1 2 5', + name='info_card', + title='Info Card', + caption='Lorem ipsum dolor sit amet consectetur adipisicing elit.', + category='Category', + label='Click me', + image='https://images.pexels.com/photos/3225517/pexels-photo-3225517.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260', + ) + + await q.page.save() diff --git a/examples/tall_stats.py b/examples/tall_stats.py new file mode 100644 index 0000000000000000000000000000000000000000..b2a9381abb958c9b16e04cb506ee8dd66fc4b265 --- /dev/null +++ b/examples/tall_stats.py @@ -0,0 +1,17 @@ +# Tall stat +# Create a vertical label-value pairs collection. +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +page.add('example', ui.tall_stats_card( + box='1 1 2 4', + items=[ + ui.stat(label='PARAMETER NAME', value='125%'), + ui.stat(label='PARAMETER NAME', value='578 Users'), + ui.stat(label='PARAMETER NAME', value='25K') + ] +)) + +page.save() diff --git a/examples/template.py b/examples/template.py new file mode 100644 index 0000000000000000000000000000000000000000..2ad00f738c203985716127ab4906db1d9cbfd239 --- /dev/null +++ b/examples/template.py @@ -0,0 +1,27 @@ +# Template +# Use a #template card to render dynamic content using a #HTML template. +# --- +from h2o_wave import site, pack, ui + +page = site['/demo'] +page.drop() + +menu = ''' +
    +{{#each dishes}} +
  1. {{name}} costs {{price}}
  2. +{{/each}} +
+{{#each dishes}} +
  • {{name}} costs {{price}}
  • +{{/each}} + The quick brown fox jumped over the lazy dog. + +Unordered list: + +- The quick brown fox jumped over the lazy dog. +- The quick brown fox jumped over the lazy dog. +- The quick brown fox jumped over the lazy dog. + +Ordered list: + +1. The quick brown fox jumped over the lazy dog. +1. The quick brown fox jumped over the lazy dog. +1. The quick brown fox jumped over the lazy dog. + +Image: + +![Monty Python](https://upload.wikimedia.org/wikipedia/en/c/cb/Flyingcircus_2.jpg) + +Links: + +Here's a [link to an image](https://upload.wikimedia.org/wikipedia/en/c/cb/Flyingcircus_2.jpg). + +Table: + +| Column 1 | Column 2 | Column 3 | +| -------- | -------- | -------- | +| Item 1 | Item 2 | Item 3 | +| Item 1 | Item 2 | Item 3 | +| Item 1 | Item 2 | Item 3 | +''' + +page['example'] = ui.form_card( + box='1 1 4 10', + items=[ui.text(sample_markdown)] +) +page.save() diff --git a/examples/text_annotator.py b/examples/text_annotator.py new file mode 100644 index 0000000000000000000000000000000000000000..16383f41792a91758252d1acefa348dc6a97b75f --- /dev/null +++ b/examples/text_annotator.py @@ -0,0 +1,35 @@ +# Form / TextAnnotator +# Use text annotator when you need to highlight text phrases. +# #form #annotator +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.annotator: + q.page['example'].items = [ + ui.text(f'annotator={q.args.annotator}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 4 7', items=[ + ui.text_annotator( + name='annotator', + title='Select text to annotate', + tags=[ + ui.text_annotator_tag(name='p', label='Person', color='#F1CBCB'), + ui.text_annotator_tag(name='o', label='Org', color='#CAEACA'), + ], + items=[ + ui.text_annotator_item(text='Killer Mike', tag='p'), + ui.text_annotator_item(text=' is a member of the hip hop supergroup '), # no tag + ui.text_annotator_item(text='Run the Jewels', tag='o'), + ui.text_annotator_item(text='.\nIt is also known by the initials '), + ui.text_annotator_item(text='RTJ', tag='o'), + ui.text_annotator_item(text='.') + ], + ), + ui.button(name='submit', label='Submit', primary=True) + ]) + await q.page.save() diff --git a/examples/text_sizes.py b/examples/text_sizes.py new file mode 100644 index 0000000000000000000000000000000000000000..2fbc1a69cee17678ac8d7e832d2123a5bea35e54 --- /dev/null +++ b/examples/text_sizes.py @@ -0,0 +1,30 @@ +# Form / Text / Sizes +# Use #text size variants to display formatted text using predefined font sizes. +# #form +# --- +from h2o_wave import site, ui + +page = site['/demo'] + +page['example'] = ui.form_card( + box='1 1 4 7', + items=[ + ui.separator('Separator'), + ui.text_xl('Extra large text'), + ui.text_l('Large text'), + ui.text('Normal text'), + ui.text_m('Medium text'), + ui.text_s('Small text'), + ui.text_xs('Extra small text'), + + # Using `ui.text()` with a `size` argument produces similar results: + ui.separator('Separator'), + ui.text('Extra large text', size=ui.TextSize.XL), + ui.text('Large text', size=ui.TextSize.L), + ui.text('Normal text'), + ui.text('Medium text', size=ui.TextSize.M), + ui.text('Small text', size=ui.TextSize.S), + ui.text('Extra small text', size=ui.TextSize.XS), + ], +) +page.save() diff --git a/examples/textbox.py b/examples/textbox.py new file mode 100644 index 0000000000000000000000000000000000000000..39c29bdee321e568ee2811b5c1e198351362ea3f --- /dev/null +++ b/examples/textbox.py @@ -0,0 +1,45 @@ +# Form / Textbox +# Use a #textbox to allow users to provide text inputs. +# #form +# --- +from h2o_wave import main, app, Q, ui + + +@app('/demo') +async def serve(q: Q): + if q.args.show_inputs: + q.page['example'].items = [ + ui.text(f'textbox={q.args.textbox}'), + ui.text(f'textbox_disabled={q.args.textbox_disabled}'), + ui.text(f'textbox_readonly={q.args.textbox_readonly}'), + ui.text(f'textbox_required={q.args.textbox_required}'), + ui.text(f'textbox_error={q.args.textbox_error}'), + ui.text(f'textbox_mask={q.args.textbox_mask}'), + ui.text(f'textbox_icon={q.args.textbox_icon}'), + ui.text(f'textbox_prefix={q.args.textbox_prefix}'), + ui.text(f'textbox_suffix={q.args.textbox_suffix}'), + ui.text(f'textbox_placeholder={q.args.textbox_placeholder}'), + ui.text(f'textbox_disabled_placeholder={q.args.textbox_disabled_placeholder}'), + ui.text(f'textbox_multiline={q.args.textbox_multiline}'), + ui.text(f'textbox_spellcheck_disabled={q.args.textbox_spellcheck_disabled}'), + ui.button(name='show_form', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 -1 -1', items=[ + ui.textbox(name='textbox', label='Standard'), + ui.textbox(name='textbox_disabled', label='Disabled', value='I am disabled', disabled=True), + ui.textbox(name='textbox_readonly', label='Read-only', value='I am read-only', readonly=True), + ui.textbox(name='textbox_required', label='Required', required=True), + ui.textbox(name='textbox_error', label='With error message', error='I have an error'), + ui.textbox(name='textbox_mask', label='With input mask', mask='(999) 999 - 9999'), + ui.textbox(name='textbox_icon', label='With icon', icon='Calendar'), + ui.textbox(name='textbox_prefix', label='With prefix', prefix='http://'), + ui.textbox(name='textbox_suffix', label='With suffix', suffix='@h2o.ai'), + ui.textbox(name='textbox_placeholder', label='With placeholder', placeholder='I need some input'), + ui.textbox(name='textbox_disabled_placeholder', label='Disabled with placeholder', disabled=True, + placeholder='I am disabled'), + ui.textbox(name='textbox_multiline', label='Multiline textarea', multiline=True), + ui.textbox(name='textbox_spellcheck_disabled', label='Spellcheck disabled', spellcheck=False), + ui.button(name='show_inputs', label='Submit', primary=True), + ]) + await q.page.save() diff --git a/examples/textbox_trigger.py b/examples/textbox_trigger.py new file mode 100644 index 0000000000000000000000000000000000000000..47c7f1eba7d8cef700bd01fdc8394cbd21cacaa8 --- /dev/null +++ b/examples/textbox_trigger.py @@ -0,0 +1,40 @@ +# Form / Textbox / Trigger +# To handle live changes to a #textbox, enable the `trigger` attribute. +# #form #trigger +# --- +from typing import Optional +from h2o_wave import main, app, Q, ui + + +def to_pig_latin(text: Optional[str]): + if not text: + return '*Type in some text above to translate to Pig Latin!*' + words = text.lower().strip().split(' ') + texts = [] + for word in words: + if word[0] in 'aeiou': + texts.append(f'{word}yay') + else: + for letter in word: + if letter in 'aeiou': + texts.append(f'{word[word.index(letter):]}{word[:word.index(letter)]}ay') + break + return ' '.join(texts) + + +def get_form_items(txt: Optional[str]): + return [ + ui.textbox(name='text', label='English', multiline=True, trigger=True), + ui.label('Pig Latin'), + ui.text(to_pig_latin(txt)), + ] + + +@app('/demo') +async def serve(q: Q): + if not q.client.initialized: + q.page['example'] = ui.form_card(box='1 1 4 7', items=get_form_items(None)) + q.client.initialized = True + if q.args.text is not None: + q.page['example'].items = get_form_items(q.args.text) + await q.page.save() diff --git a/examples/theme_generator.py b/examples/theme_generator.py new file mode 100644 index 0000000000000000000000000000000000000000..9db081e701c3f5c56138987af400cbf331f8b9a1 --- /dev/null +++ b/examples/theme_generator.py @@ -0,0 +1,210 @@ +# Theme generator +# Use theme generator to quickly generate custom color schemes for your app. +# #theme_generator +# --- +from typing import Tuple +from h2o_wave import main, app, Q, ui, data + +from pygments import highlight +from pygments.formatters.html import HtmlFormatter +from pygments.lexers import get_lexer_by_name +from pygments.style import Style +from pygments.token import Name, String, Operator, Punctuation + +import math + +py_lexer = get_lexer_by_name('python') + + +def to_grayscale(color: float) -> float: + color /= 255 + return color / 12.92 if color <= 0.03928 else math.pow((color + 0.055) / 1.055, 2.4) + + +def get_luminance(r: float, g: float, b: float) -> float: + return to_grayscale(r) * 0.2126 + to_grayscale(g) * 0.7152 + to_grayscale(b) * 0.0722 + + +# Source: https://www.delftstack.com/howto/python/python-hex-to-rgb/. +def hex_to_rgb(hex_color: str) -> Tuple[int, ...]: + if len(hex_color) == 3: + hex_color = f'{hex_color[0]}{hex_color[0]}{hex_color[1]}{hex_color[1]}{hex_color[2]}{hex_color[2]}' + return tuple(int(hex_color[i:i + 2], 16) for i in (0, 2, 4)) + + +# Source: https://stackoverflow.com/questions/9733288/how-to-programmatically-calculate-the-contrast-ratio-between-two-colors. # noqa +def update_contrast_check(color1: str, color2: str, q:Q, min_contrast=4.5): + rgb1 = hex_to_rgb(q.client[color1].lstrip('#')) + rgb2 = hex_to_rgb(q.client[color2].lstrip('#')) + lum1 = get_luminance(rgb1[0], rgb1[1], rgb1[2]) + lum2 = get_luminance(rgb2[0], rgb2[1], rgb2[2]) + brightest = max(lum1, lum2) + darkest = min(lum1, lum2) + contrast = (brightest + 0.05) / (darkest + 0.05) + if contrast < min_contrast: + q.page['form'][f'{color1}_{color2}'].type = 'error' + q.page['form'][f'{color1}_{color2}'].text = f'Improve contrast between **{color1}** and **{color2}**.' + else: + q.page['form'][f'{color1}_{color2}'].type = 'success' + q.page['form'][f'{color1}_{color2}'].text = f'Contrast between **{color1}** and **{color2}** is great!' + + +def get_theme_code(q: Q): + contents = f''' +ui.theme( + name='', + primary='{q.client.primary}', + text='{q.client.text}', + card='{q.client.card}', + page='{q.client.page}', +) +''' + + # Reference: http://svn.python.org/projects/external/Pygments-0.10/docs/build/styles.html + class CustomStyle(Style): + styles = { + Name: q.client.text, + Operator: q.client.text, + Punctuation: q.client.text, + String: q.client.primary + } + + html_formatter = HtmlFormatter(full=True, style=CustomStyle, nobackground=True) + html = highlight(contents, py_lexer, html_formatter) + html = html.replace('

    ', '') + html = html.replace('', '') + html = html.replace('
    ', '
    ')
    +    return html
    +
    +
    +@app('/demo')
    +async def serve(q: Q):
    +    if not q.client.initialized:
    +        image = 'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&h=750&w=1260'
    +        q.client.primary = '#000000'
    +        q.client.page = '#e2e2e2'
    +        q.client.card = '#ffffff'
    +        q.client.text = '#000000'
    +        q.page['meta'] = ui.meta_card(box='', theme='custom', layouts=[
    +            ui.layout(
    +                breakpoint='xs',
    +                zones=[
    +                    ui.zone('header'),
    +                    ui.zone('content', direction=ui.ZoneDirection.ROW, zones=[
    +                        ui.zone('colors', size='400px'),
    +                        ui.zone('preview')
    +                    ]),
    +                    ui.zone('footer')
    +                ]
    +            )
    +        ])
    +        q.page['header'] = ui.header_card(box='header', title='Theme generator', subtitle='Color your app easily',
    +                                          icon='Color', icon_color='$card')
    +        q.page['form'] = ui.form_card(box='colors', items=[
    +            ui.color_picker(name='primary', label='Primary', trigger=True, alpha=False, inline=True, value=q.client.primary),
    +            ui.color_picker(name='text', label='Text', trigger=True, alpha=False, inline=True, value=q.client.text),
    +            ui.color_picker(name='card', label='Card', trigger=True, alpha=False, inline=True, value=q.client.card),
    +            ui.color_picker(name='page', label='Page', trigger=True, alpha=False, inline=True, value=q.client.page),
    +            ui.text_xl('Check contrast'),
    +            ui.message_bar(name='text_card', type='success', text='Contrast between **text** and **card** is great!'),
    +            ui.message_bar(name='card_primary', type='success', text='Contrast between **card** and **primary** is great!'),
    +            ui.message_bar(name='text_page', type='success', text='Contrast between **text** and **page** is great!'),
    +            ui.message_bar(name='page_primary', type='success', text='Contrast between **page** and **primary** is great!'),
    +            ui.text_xl('Copy code'),
    +            ui.frame(name='frame', content=get_theme_code(q), height='180px'),
    +        ])
    +        q.page['sample'] = ui.form_card(box='preview', items=[
    +            ui.text_xl(content='Sample App to show colors'),
    +            ui.progress(label='A progress bar'),
    +            ui.inline([
    +                ui.checkbox(name='checkbox1', label='A checkbox', value=True),
    +                ui.checkbox(name='checkbox2', label='Another checkbox'),
    +                ui.checkbox(name='checkbox3', label='Yet another checkbox'),
    +                ui.toggle(name='toggle', label='Toggle', value=True),
    +            ]),
    +            ui.inline([
    +                ui.date_picker(name='date_picker', label='Date picker'),
    +                ui.picker(name='picker', label='Picker', choices=[
    +                    ui.choice('choice1', label='Choice 1'),
    +                    ui.choice('choice2', label='Choice 2'),
    +                    ui.choice('choice3', label='Choice 3'),
    +                ]),
    +                ui.combobox(name='combobox', label='Combobox', choices=['Choice 1', 'Choice 2', 'Choice 3']),
    +                ui.persona(title='John Doe', subtitle='Data Scientist', size='s', image=image),
    +            ]),
    +            ui.slider(name='slider', label='Slider', value=70),
    +            ui.link(label='Link'),
    +            ui.inline(justify='between', items=[
    +                ui.stepper(name='stepper', width='500px', items=[
    +                    ui.step(label='Step 1', icon='MailLowImportance'),
    +                    ui.step(label='Step 2', icon='TaskManagerMirrored'),
    +                    ui.step(label='Step 3', icon='Cafe'),
    +                ]),
    +                ui.tabs(name='menu', value='email', items=[
    +                    ui.tab(name='email', label='Mail', icon='Mail'),
    +                    ui.tab(name='events', label='Events', icon='Calendar'),
    +                    ui.tab(name='spam', label='Spam'),
    +                ]),
    +            ]),
    +            ui.inline(items=[
    +                ui.table(
    +                    name='table',
    +                    width='50%',
    +                    columns=[
    +                        ui.table_column(name='name', label='Name', min_width='80px'),
    +                        ui.table_column(name='surname', label='Surname', filterable=True),
    +                        ui.table_column(name='age', label='Age', sortable=True, max_width='80px'),
    +                        ui.table_column(name='progress', label='Progress',
    +                                        cell_type=ui.progress_table_cell_type(color='$themePrimary')),
    +                    ],
    +                    rows=[
    +                        ui.table_row(name='row1', cells=['John', 'Doe', '25', '0.90']),
    +                        ui.table_row(name='row2', cells=['Ann', 'Doe', '35', '0.75']),
    +                        ui.table_row(name='row3', cells=['Casey', 'Smith', '40', '0.33']),
    +                    ],
    +                    height='330px',
    +                ),
    +                ui.visualization(
    +                    width='50%',
    +                    data=data('profession salary', 5, rows=[
    +                        ('medicine', 23000),
    +                        ('fire fighting', 18000),
    +                        ('pedagogy', 24000),
    +                        ('psychology', 22500),
    +                        ('computer science', 36000),
    +                    ], pack=True),
    +                    plot=ui.plot([ui.mark(type='interval', x='=profession', y='=salary', y_min=0)])
    +                ),
    +            ]),
    +            ui.buttons([
    +                ui.button(name='primary_button', label='Primary', primary=True),
    +                ui.button(name='standard_button', label='Standard'),
    +                ui.button(name='standard_disabled_button', label='Disabled', disabled=True),
    +                ui.button(name='icon_button', icon='Heart', caption='Tooltip text'),
    +            ]),
    +        ])
    +        q.page['footer'] = ui.footer_card(box='footer', caption='(c) 2021 H2O.ai. All rights reserved.')
    +        q.client.themes = [ui.theme(name='custom', text=q.client.text, card=q.client.card,
    +                                    page=q.client.page, primary=q.client.primary)]
    +        q.client.initialized = True
    +
    +    if q.args.primary:
    +        q.client.themes[0].primary = q.args.primary
    +        q.client.primary = q.args.primary
    +    if q.args.text:
    +        q.client.themes[0].text = q.args.text
    +        q.client.text = q.args.text
    +    if q.args.card:
    +        q.client.themes[0].card = q.args.card
    +        q.client.card = q.args.card
    +    if q.args.page:
    +        q.client.themes[0].page = q.args.page
    +        q.client.page = q.args.page
    +
    +    q.page['meta'].themes = q.client.themes
    +    update_contrast_check('text', 'card', q)
    +    update_contrast_check('card', 'primary', q)
    +    update_contrast_check('text', 'page', q)
    +    update_contrast_check('page', 'primary', q)
    +    q.page['form'].frame.content = get_theme_code(q)
    +    await q.page.save()
    diff --git a/examples/time_picker.py b/examples/time_picker.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..c91436214aff47a34c9db51709615c8d8956dd15
    --- /dev/null
    +++ b/examples/time_picker.py
    @@ -0,0 +1,32 @@
    +# Form / TimePicker
    +# Use time pickers to allow users to pick times.
    +# #form #timepicker
    +# ---
    +from h2o_wave import main, app, Q, ui
    +
    +
    +@app('/demo')
    +async def serve(q: Q):
    +    if q.args.show_inputs:
    +        q.page['example'].items = [
    +            ui.text(f'timepicker={q.args.timepicker}'),
    +            ui.text(f'timepicker_required={q.args.timepicker_required}'),
    +            ui.text(f'timepicker_disabled={q.args.timepicker_disabled}'),
    +            ui.text(f'timepicker_placeholder={q.args.timepicker_placeholder}'),
    +            ui.text(f'timepicker_h24={q.args.timepicker_h24}'),
    +            ui.text(f'timepicker_boundaries={q.args.timepicker_boundaries}'),
    +            ui.text(f'timepicker_step={q.args.timepicker_step}'),
    +            ui.button(name='show_form', label='Back', primary=True),
    +        ]
    +    else:
    +        q.page['example'] = ui.form_card(box='1 1 4 8', items=[
    +            ui.time_picker(name='timepicker', label="Standard time picker"),
    +            ui.time_picker(name='timepicker_required', label="Time picker - required", required=True),
    +            ui.time_picker(name='timepicker_disabled', label="Disabled time picker", value='11:15', disabled=True),
    +            ui.time_picker(name='timepicker_placeholder', label="Time picker with custom placeholder", placeholder='Select a time'),
    +            ui.time_picker(name='timepicker_h24', label="Time picker with 24 hour time format", hour_format='24', value="18:35"),
    +            ui.time_picker(name='timepicker_boundaries', label="Time picker with boundaries", min='10:00', max='18:00', value='13:36'),
    +            ui.time_picker(name='timepicker_step', label="Time picker with minutes step", minutes_step=10),
    +            ui.button(name='show_inputs', label='Submit', primary=True),
    +        ])
    +    await q.page.save()
    diff --git a/examples/todo.py b/examples/todo.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..fbe323dcdecf3e3e012b433e39dab56cd34ac6b8
    --- /dev/null
    +++ b/examples/todo.py
    @@ -0,0 +1,76 @@
    +# To-do List App
    +# A simple multi-user To-do list application.
    +# ---
    +from h2o_wave import main, app, Q, ui
    +from typing import List
    +
    +_id = 0
    +
    +
    +# A simple class that represents a to-do item.
    +class TodoItem:
    +    def __init__(self, label):
    +        global _id
    +        _id += 1
    +        self.id = f'todo_{_id}'
    +        self.done = False
    +        self.label = label
    +
    +
    +@app('/demo')
    +async def serve(q: Q):
    +    if q.args.new_todo:  # Display an input form.
    +        await new_todo(q)
    +    elif q.args.add_todo:  # Add an item.
    +        await add_todo(q)
    +    else:  # Show all items.
    +        await show_todos(q)
    +
    +
    +async def show_todos(q: Q):
    +    # Get items for this user.
    +    todos: List[TodoItem] = q.user.todos
    +
    +    # Create a sample list if we don't have any.
    +    if todos is None:
    +        q.user.todos = todos = [TodoItem('Do this'), TodoItem('Do that'), TodoItem('Do something else')]
    +
    +    # If the user checked/unchecked an item, update our list.
    +    for todo in todos:
    +        if todo.id in q.args:
    +            todo.done = q.args[todo.id]
    +
    +    # Create done/not-done checkboxes.
    +    done = [ui.checkbox(name=todo.id, label=todo.label, value=True, trigger=True) for todo in todos if todo.done]
    +    not_done = [ui.checkbox(name=todo.id, label=todo.label, trigger=True) for todo in todos if not todo.done]
    +
    +    # Display list
    +    q.page['form'] = ui.form_card(box='1 1 4 3', items=[
    +        ui.text_l('To Do'),
    +        ui.button(name='new_todo', label='Add To Do...', primary=True),
    +        *not_done,
    +        *([ui.separator('Done')] if len(done) else []),
    +        *done,
    +    ])
    +    await q.page.save()
    +
    +
    +async def add_todo(q: Q):
    +    # Insert a new item
    +    q.user.todos.insert(0, TodoItem(q.args.label or 'Untitled'))
    +
    +    # Go back to our list.
    +    await show_todos(q)
    +
    +
    +async def new_todo(q: Q):
    +    # Display an input form
    +    q.page['form'] = ui.form_card(box='1 1 4 3', items=[
    +        ui.text_l('Add To Do'),
    +        ui.textbox(name='label', label='What needs to be done?', multiline=True),
    +        ui.buttons([
    +            ui.button(name='add_todo', label='Add', primary=True),
    +            ui.button(name='show_todos', label='Back'),
    +        ]),
    +    ])
    +    await q.page.save()
    diff --git a/examples/toggle.py b/examples/toggle.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..376f2d9ab8ec300698e635f95d8627a0152384d9
    --- /dev/null
    +++ b/examples/toggle.py
    @@ -0,0 +1,26 @@
    +# Form / Toggle
    +# Use a #toggle to present users with two mutually exclusive options (to turn settings on and off).
    +# #form
    +# ---
    +from h2o_wave import main, app, Q, ui
    +
    +
    +@app('/demo')
    +async def serve(q: Q):
    +    if q.args.show_inputs:
    +        q.page['example'].items = [
    +            ui.text(f'toggle_unchecked={q.args.toggle_unchecked}'),
    +            ui.text(f'toggle_checked={q.args.toggle_checked}'),
    +            ui.text(f'toggle_unchecked_disabled={q.args.toggle_unchecked_disabled}'),
    +            ui.text(f'toggle_checked_disabled={q.args.toggle_checked_disabled}'),
    +            ui.button(name='show_form', label='Back', primary=True),
    +        ]
    +    else:
    +        q.page['example'] = ui.form_card(box='1 1 4 5', items=[
    +            ui.toggle(name='toggle_unchecked', label='Not checked'),
    +            ui.toggle(name='toggle_checked', label='Checked', value=True),
    +            ui.toggle(name='toggle_unchecked_disabled', label='Not checked (Disabled)', disabled=True),
    +            ui.toggle(name='toggle_checked_disabled', label='Checked (Disabled)', value=True, disabled=True),
    +            ui.button(name='show_inputs', label='Submit', primary=True),
    +        ])
    +    await q.page.save()
    diff --git a/examples/toolbar.py b/examples/toolbar.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..b9ea257526e7e979fbb40233c880b089f2cdfef3
    --- /dev/null
    +++ b/examples/toolbar.py
    @@ -0,0 +1,33 @@
    +# Toolbar
    +# Use toolbars to provide commands that operate on the content of a page.
    +# #toolbar #command
    +# ---
    +from h2o_wave import main, app, Q, ui
    +
    +
    +@app('/demo')
    +async def serve(q: Q):
    +    q.page['nav'] = ui.toolbar_card(
    +        box='1 1 4 1',
    +        items=[
    +            ui.command(
    +                name='new', label='New', icon='Add', items=[
    +                    ui.command(name='email', label='Email Message', icon='Mail'),
    +                    ui.command(name='calendar', label='Calendar Event', icon='Calendar'),
    +                ]
    +            ),
    +            ui.command(name='upload', label='Upload', icon='Upload'),
    +            ui.command(name='share', label='Share', icon='Share'),
    +            ui.command(name='download', label='Download', icon='Download'),
    +        ],
    +        secondary_items=[
    +            ui.command(name='tile', caption='Grid View', icon='Tiles'),
    +            ui.command(name='info', caption='Info', icon='Info'),
    +        ],
    +        overflow_items=[
    +            ui.command(name='move', label='Move to...', icon='MoveToFolder'),
    +            ui.command(name='copy', label='Copy to...', icon='Copy'),
    +            ui.command(name='rename', label='Rename', icon='Edit'),
    +        ],
    +    )
    +    await q.page.save()
    diff --git a/examples/toolbar_routing.py b/examples/toolbar_routing.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..2b8727f69290ceb9aa11bc9e4a3df5761aafd31d
    --- /dev/null
    +++ b/examples/toolbar_routing.py
    @@ -0,0 +1,40 @@
    +# Routing / Toolbar
    +# This example demonstrates how you can observe and handle changes to the browser's
    +# [location hash](https://developer.mozilla.org/en-US/docs/Web/API/Location/hash).
    +#
    +# The location hash can be accessed using `q.args['#']`.
    +# #routing #toolbar
    +# ---
    +from h2o_wave import main, app, Q, ui
    +
    +
    +@app('/demo')
    +async def serve(q: Q):
    +    if not q.client.initialized:
    +        q.page['nav'] = ui.toolbar_card(
    +            box='1 1 4 1',
    +            items=[
    +                ui.command(name='#menu/spam', label='Spam'),
    +                ui.command(name='#menu/ham', label='Ham'),
    +                ui.command(name='#menu/eggs', label='Eggs'),
    +                ui.command(name='#about', label='About'),
    +            ],
    +        )
    +        q.page['blurb'] = ui.markdown_card(
    +            box='1 2 4 2',
    +            title='Store',
    +            content='Welcome to our store!',
    +        )
    +        q.client.initialized = True
    +    hash = q.args['#']
    +    if hash:
    +        blurb = q.page['blurb']
    +        if hash == 'menu/spam':
    +            blurb.content = "Sorry, we're out of spam!"
    +        elif hash == 'menu/ham':
    +            blurb.content = "Sorry, we're out of ham!"
    +        elif hash == 'menu/eggs':
    +            blurb.content = "Sorry, we're out of eggs!"
    +        elif hash == 'about':
    +            blurb.content = 'Everything here is gluten-free!'
    +    await q.page.save()
    diff --git a/examples/tour-assets/h2o-logo.svg b/examples/tour-assets/h2o-logo.svg
    new file mode 100644
    index 0000000000000000000000000000000000000000..d6b04435700ffae6284031d15b2220ea53bdce7f
    --- /dev/null
    +++ b/examples/tour-assets/h2o-logo.svg
    @@ -0,0 +1 @@
    +
    \ No newline at end of file
    diff --git a/examples/tour-assets/loader.min.js b/examples/tour-assets/loader.min.js
    new file mode 100644
    index 0000000000000000000000000000000000000000..ab03d505e7f4fe9c5722a0a71c4ebb93809de34e
    --- /dev/null
    +++ b/examples/tour-assets/loader.min.js
    @@ -0,0 +1,4 @@
    +"use strict";var define,AMDLoader,_amdLoaderGlobal=this,_commonjsGlobal="object"==typeof global?global:{};!function(e){e.global=_amdLoaderGlobal;var t=(Object.defineProperty(r.prototype,"isWindows",{get:function(){return this._detect(),this._isWindows},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"isNode",{get:function(){return this._detect(),this._isNode},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"isElectronRenderer",{get:function(){return this._detect(),this._isElectronRenderer},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"isWebWorker",{get:function(){return this._detect(),this._isWebWorker},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"isElectronNodeIntegrationWebWorker",{get:function(){return this._detect(),this._isElectronNodeIntegrationWebWorker},enumerable:!1,configurable:!0}),r.prototype._detect=function(){this._detected||(this._detected=!0,this._isWindows=r._isWindows(),this._isNode="undefined"!=typeof module&&!!module.exports,this._isElectronRenderer="undefined"!=typeof process&&void 0!==process.versions&&void 0!==process.versions.electron&&"renderer"===process.type,this._isWebWorker="function"==typeof e.global.importScripts,this._isElectronNodeIntegrationWebWorker=this._isWebWorker&&"undefined"!=typeof process&&void 0!==process.versions&&void 0!==process.versions.electron&&"worker"===process.type)},r._isWindows=function(){return!!("undefined"!=typeof navigator&&navigator.userAgent&&0<=navigator.userAgent.indexOf("Windows"))||"undefined"!=typeof process&&"win32"===process.platform},r);function r(){this._detected=!1,this._isWindows=!1,this._isNode=!1,this._isElectronRenderer=!1,this._isWebWorker=!1,this._isElectronNodeIntegrationWebWorker=!1}e.Environment=t}(AMDLoader=AMDLoader||{}),function(r){var n=function(e,t,r){this.type=e,this.detail=t,this.timestamp=r};r.LoaderEvent=n;var e=(t.prototype.record=function(e,t){this._events.push(new n(e,t,r.Utilities.getHighPerformanceTimestamp()))},t.prototype.getEvents=function(){return this._events},t);function t(e){this._events=[new n(1,"",e)]}r.LoaderEventRecorder=e;o.prototype.record=function(e,t){},o.prototype.getEvents=function(){return[]},o.INSTANCE=new o,e=o;function o(){}r.NullLoaderEventRecorder=e}(AMDLoader=AMDLoader||{}),function(e){var t=(n.fileUriToFilePath=function(e,t){if(t=decodeURI(t).replace(/%23/g,"#"),e){if(/^file:\/\/\//.test(t))return t.substr(8);if(/^file:\/\//.test(t))return t.substr(5)}else if(/^file:\/\//.test(t))return t.substr(7);return t},n.startsWith=function(e,t){return e.length>=t.length&&e.substr(0,t.length)===t},n.endsWith=function(e,t){return e.length>=t.length&&e.substr(e.length-t.length)===t},n.containsQueryString=function(e){return/^[^\#]*\?/gi.test(e)},n.isAbsolutePath=function(e){return/^((http:\/\/)|(https:\/\/)|(file:\/\/)|(\/))/.test(e)},n.forEachProperty=function(e,t){if(e){var r=void 0;for(r in e)e.hasOwnProperty(r)&&t(r,e[r])}},n.isEmpty=function(e){var t=!0;return n.forEachProperty(e,function(){t=!1}),t},n.recursiveClone=function(e){if(!e||"object"!=typeof e||e instanceof RegExp||!Array.isArray(e)&&Object.getPrototypeOf(e)!==Object.prototype)return e;var r=Array.isArray(e)?[]:{};return n.forEachProperty(e,function(e,t){r[e]=t&&"object"==typeof t?n.recursiveClone(t):t}),r},n.generateAnonymousModule=function(){return"===anonymous"+n.NEXT_ANONYMOUS_ID+++"==="},n.isAnonymousModule=function(e){return n.startsWith(e,"===anonymous")},n.getHighPerformanceTimestamp=function(){return this.PERFORMANCE_NOW_PROBED||(this.PERFORMANCE_NOW_PROBED=!0,this.HAS_PERFORMANCE_NOW=e.global.performance&&"function"==typeof e.global.performance.now),(this.HAS_PERFORMANCE_NOW?e.global.performance:Date).now()},n.NEXT_ANONYMOUS_ID=1,n.PERFORMANCE_NOW_PROBED=!1,n.HAS_PERFORMANCE_NOW=!1,n);function n(){}e.Utilities=t}(AMDLoader=AMDLoader||{}),function(d){function r(e){if(e instanceof Error)return e;var t=new Error(e.message||String(e)||"Unknown Error");return e.stack&&(t.stack=e.stack),t}d.ensureError=r;var o=(n.validateConfigurationOptions=function(e){var t;return"string"!=typeof(e=e||{}).baseUrl&&(e.baseUrl=""),"boolean"!=typeof e.isBuild&&(e.isBuild=!1),"object"!=typeof e.paths&&(e.paths={}),"object"!=typeof e.config&&(e.config={}),void 0===e.catchError&&(e.catchError=!1),void 0===e.recordStats&&(e.recordStats=!1),"string"!=typeof e.urlArgs&&(e.urlArgs=""),"function"!=typeof e.onError&&(e.onError=function(e){if("loading"===e.phase)return console.error('Loading "'+e.moduleId+'" failed'),console.error(e),console.error("Here are the modules that depend on it:"),void console.error(e.neededBy);"factory"===e.phase&&(console.error('The factory method of "'+e.moduleId+'" has thrown an exception'),console.error(e))}),Array.isArray(e.ignoreDuplicateModules)||(e.ignoreDuplicateModules=[]),0=o.length)d._onLoadError(n,e);else{var t=o[i],r=d.getRecorder();if(d._config.isBuild()&&"empty:"===t)return d._buildInfoPath[n]=t,d.defineModule(d._moduleIdProvider.getStrModuleId(n),[],null,null,null),void d._onLoad(n);r.record(10,t),d._scriptLoader.load(d,t,function(){d._config.isBuild()&&(d._buildInfoPath[n]=t),r.record(11,t),d._onLoad(n)},function(e){r.record(12,t),s(e)})}})(null))},l.prototype._loadPluginDependency=function(e,t){var r,n=this;this._modules2[t.id]||this._knownModules2[t.id]||(this._knownModules2[t.id]=!0,(r=function(e){n.defineModule(n._moduleIdProvider.getStrModuleId(t.id),[],e,null,null)}).error=function(e){n._config.onError(n._createLoadError(t.id,e))},e.load(t.pluginParam,this._createRequire(a.ROOT),r,this._config.getOptionsLiteral()))},l.prototype._resolve=function(e){var t=this,r=e.dependencies;if(r)for(var n=0,o=r.length;n 
    +`)),e.unresolvedDependenciesCount--):(this._inverseDependencies2[i.id]=this._inverseDependencies2[i.id]||[],this._inverseDependencies2[i.id].push(e.id),i instanceof u?(s=this._modules2[i.pluginId])&&s.isComplete()?this._loadPluginDependency(s.exports,i):((s=this._inversePluginDependencies2.get(i.pluginId))||this._inversePluginDependencies2.set(i.pluginId,s=[]),s.push(i),this._loadModule(i.pluginId)):this._loadModule(i.id))}else e.unresolvedDependenciesCount--;else e.unresolvedDependenciesCount--;else e.exportsPassedIn=!0,e.unresolvedDependenciesCount--}0===e.unresolvedDependenciesCount&&this._onModuleComplete(e)},l.prototype._onModuleComplete=function(e){var t=this,r=this.getRecorder();if(!e.isComplete()){var n=e.dependencies,o=[];if(n)for(var i=0,s=n.length;i=0?!0:typeof process!="undefined"?process.platform==="win32":!1},E}();U.Environment=r})(re||(re={}));var re;(function(U){var r=function(){function N(o,w,g){this.type=o,this.detail=w,this.timestamp=g}return N}();U.LoaderEvent=r;var E=function(){function N(o){this._events=[new r(1,"",o)]}return N.prototype.record=function(o,w){this._events.push(new r(o,w,U.Utilities.getHighPerformanceTimestamp()))},N.prototype.getEvents=function(){return this._events},N}();U.LoaderEventRecorder=E;var e=function(){function N(){}return N.prototype.record=function(o,w){},N.prototype.getEvents=function(){return[]},N.INSTANCE=new N,N}();U.NullLoaderEventRecorder=e})(re||(re={}));var re;(function(U){var r=function(){function E(){}return E.fileUriToFilePath=function(e,N){if(N=decodeURI(N).replace(/%23/g,"#"),e){if(/^file:\/\/\//.test(N))return N.substr(8);if(/^file:\/\//.test(N))return N.substr(5)}else if(/^file:\/\//.test(N))return N.substr(7);return N},E.startsWith=function(e,N){return e.length>=N.length&&e.substr(0,N.length)===N},E.endsWith=function(e,N){return e.length>=N.length&&e.substr(e.length-N.length)===N},E.containsQueryString=function(e){return/^[^\#]*\?/gi.test(e)},E.isAbsolutePath=function(e){return/^((http:\/\/)|(https:\/\/)|(file:\/\/)|(\/))/.test(e)},E.forEachProperty=function(e,N){if(e){var o=void 0;for(o in e)e.hasOwnProperty(o)&&N(o,e[o])}},E.isEmpty=function(e){var N=!0;return E.forEachProperty(e,function(){N=!1}),N},E.recursiveClone=function(e){if(!e||typeof e!="object"||e instanceof RegExp||!Array.isArray(e)&&Object.getPrototypeOf(e)!==Object.prototype)return e;var N=Array.isArray(e)?[]:{};return E.forEachProperty(e,function(o,w){w&&typeof w=="object"?N[o]=E.recursiveClone(w):N[o]=w}),N},E.generateAnonymousModule=function(){return"===anonymous"+E.NEXT_ANONYMOUS_ID+++"==="},E.isAnonymousModule=function(e){return E.startsWith(e,"===anonymous")},E.getHighPerformanceTimestamp=function(){return this.PERFORMANCE_NOW_PROBED||(this.PERFORMANCE_NOW_PROBED=!0,this.HAS_PERFORMANCE_NOW=U.global.performance&&typeof U.global.performance.now=="function"),this.HAS_PERFORMANCE_NOW?U.global.performance.now():Date.now()},E.NEXT_ANONYMOUS_ID=1,E.PERFORMANCE_NOW_PROBED=!1,E.HAS_PERFORMANCE_NOW=!1,E}();U.Utilities=r})(re||(re={}));var re;(function(U){function r(N){if(N instanceof Error)return N;var o=new Error(N.message||String(N)||"Unknown Error");return N.stack&&(o.stack=N.stack),o}U.ensureError=r;var E=function(){function N(){}return N.validateConfigurationOptions=function(o){function w(c){if(c.phase==="loading"){console.error('Loading "'+c.moduleId+'" failed'),console.error(c),console.error("Here are the modules that depend on it:"),console.error(c.neededBy);return}if(c.phase==="factory"){console.error('The factory method of "'+c.moduleId+'" has thrown an exception'),console.error(c);return}}if(o=o||{},typeof o.baseUrl!="string"&&(o.baseUrl=""),typeof o.isBuild!="boolean"&&(o.isBuild=!1),typeof o.paths!="object"&&(o.paths={}),typeof o.config!="object"&&(o.config={}),typeof o.catchError=="undefined"&&(o.catchError=!1),typeof o.recordStats=="undefined"&&(o.recordStats=!1),typeof o.urlArgs!="string"&&(o.urlArgs=""),typeof o.onError!="function"&&(o.onError=w),Array.isArray(o.ignoreDuplicateModules)||(o.ignoreDuplicateModules=[]),o.baseUrl.length>0&&(U.Utilities.endsWith(o.baseUrl,"/")||(o.baseUrl+="/")),typeof o.cspNonce!="string"&&(o.cspNonce=""),typeof o.preferScriptTags=="undefined"&&(o.preferScriptTags=!1),Array.isArray(o.nodeModules)||(o.nodeModules=[]),o.nodeCachedData&&typeof o.nodeCachedData=="object"&&(typeof o.nodeCachedData.seed!="string"&&(o.nodeCachedData.seed="seed"),(typeof o.nodeCachedData.writeDelay!="number"||o.nodeCachedData.writeDelay<0)&&(o.nodeCachedData.writeDelay=1e3*7),!o.nodeCachedData.path||typeof o.nodeCachedData.path!="string")){var g=r(new Error("INVALID cached data configuration, 'path' MUST be set"));g.phase="configuration",o.onError(g),o.nodeCachedData=void 0}return o},N.mergeConfigurationOptions=function(o,w){o===void 0&&(o=null),w===void 0&&(w=null);var g=U.Utilities.recursiveClone(w||{});return U.Utilities.forEachProperty(o,function(c,m){c==="ignoreDuplicateModules"&&typeof g.ignoreDuplicateModules!="undefined"?g.ignoreDuplicateModules=g.ignoreDuplicateModules.concat(m):c==="paths"&&typeof g.paths!="undefined"?U.Utilities.forEachProperty(m,function(S,t){return g.paths[S]=t}):c==="config"&&typeof g.config!="undefined"?U.Utilities.forEachProperty(m,function(S,t){return g.config[S]=t}):g[c]=U.Utilities.recursiveClone(m)}),N.validateConfigurationOptions(g)},N}();U.ConfigurationOptionsUtil=E;var e=function(){function N(o,w){if(this._env=o,this.options=E.mergeConfigurationOptions(w),this._createIgnoreDuplicateModulesMap(),this._createNodeModulesMap(),this._createSortedPathsRules(),this.options.baseUrl===""){if(this.options.nodeRequire&&this.options.nodeRequire.main&&this.options.nodeRequire.main.filename&&this._env.isNode){var g=this.options.nodeRequire.main.filename,c=Math.max(g.lastIndexOf("/"),g.lastIndexOf("\\"));this.options.baseUrl=g.substring(0,c+1)}if(this.options.nodeMain&&this._env.isNode){var g=this.options.nodeMain,c=Math.max(g.lastIndexOf("/"),g.lastIndexOf("\\"));this.options.baseUrl=g.substring(0,c+1)}}}return N.prototype._createIgnoreDuplicateModulesMap=function(){this.ignoreDuplicateModulesMap={};for(var o=0;o=5)){if(s.length0?(L=s.slice(0,16),v=s.slice(16),t.record(60,S)):t.record(61,S),y()})}},c.prototype._verifyCachedData=function(m,S,t,d,h){var v=this;!d||m.cachedDataRejected||setTimeout(function(){var L=v._crypto.createHash("md5").update(S,"utf8").digest();d.equals(L)||(h.getConfig().onError(new Error("FAILED TO VERIFY CACHED DATA, deleting stale '"+t+"' now, but a RESTART IS REQUIRED")),v._fs.unlink(t,function(C){C&&h.getConfig().onError(C)}))},Math.ceil(5e3*(1+Math.random())))},c._BOM=65279,c._PREFIX="(function (require, define, __filename, __dirname) { ",c._SUFFIX=`
    +});`,c}();function w(c,m){if(m.__$__isRecorded)return m;var S=function(d){c.record(33,d);try{return m(d)}finally{c.record(34,d)}};return S.__$__isRecorded=!0,S}U.ensureRecordedNodeRequire=w;function g(c){return new r(c)}U.createScriptLoader=g})(re||(re={}));var re;(function(U){var r=function(){function g(c){var m=c.lastIndexOf("/");m!==-1?this.fromModulePath=c.substr(0,m+1):this.fromModulePath=""}return g._normalizeModuleId=function(c){var m=c,S;for(S=/\/\.\//;S.test(m);)m=m.replace(S,"/");for(m=m.replace(/^\.\//g,""),S=/\/(([^\/])|([^\/][^\/\.])|([^\/\.][^\/])|([^\/][^\/][^\/]+))\/\.\.\//;S.test(m);)m=m.replace(S,"/");return m=m.replace(/^(([^\/])|([^\/][^\/\.])|([^\/\.][^\/])|([^\/][^\/][^\/]+))\/\.\.\//,""),m},g.prototype.resolveModule=function(c){var m=c;return U.Utilities.isAbsolutePath(m)||(U.Utilities.startsWith(m,"./")||U.Utilities.startsWith(m,"../"))&&(m=g._normalizeModuleId(this.fromModulePath+m)),m},g.ROOT=new g(""),g}();U.ModuleIdResolver=r;var E=function(){function g(c,m,S,t,d,h){this.id=c,this.strId=m,this.dependencies=S,this._callback=t,this._errorback=d,this.moduleIdResolver=h,this.exports={},this.error=null,this.exportsPassedIn=!1,this.unresolvedDependenciesCount=this.dependencies.length,this._isComplete=!1}return g._safeInvokeFunction=function(c,m){try{return{returnedValue:c.apply(U.global,m),producedError:null}}catch(S){return{returnedValue:null,producedError:S}}},g._invokeFactory=function(c,m,S,t){return c.isBuild()&&!U.Utilities.isAnonymousModule(m)?{returnedValue:null,producedError:null}:c.shouldCatchError()?this._safeInvokeFunction(S,t):{returnedValue:S.apply(U.global,t),producedError:null}},g.prototype.complete=function(c,m,S){this._isComplete=!0;var t=null;if(this._callback)if(typeof this._callback=="function"){c.record(21,this.strId);var d=g._invokeFactory(m,this.strId,this._callback,S);t=d.producedError,c.record(22,this.strId),!t&&typeof d.returnedValue!="undefined"&&(!this.exportsPassedIn||U.Utilities.isEmpty(this.exports))&&(this.exports=d.returnedValue)}else this.exports=this._callback;if(t){var h=U.ensureError(t);h.phase="factory",h.moduleId=this.strId,this.error=h,m.onError(h)}this.dependencies=null,this._callback=null,this._errorback=null,this.moduleIdResolver=null},g.prototype.onDependencyError=function(c){return this._isComplete=!0,this.error=c,this._errorback?(this._errorback(c),!0):!1},g.prototype.isComplete=function(){return this._isComplete},g}();U.Module=E;var e=function(){function g(){this._nextId=0,this._strModuleIdToIntModuleId=new Map,this._intModuleIdToStrModuleId=[],this.getModuleId("exports"),this.getModuleId("module"),this.getModuleId("require")}return g.prototype.getMaxModuleId=function(){return this._nextId},g.prototype.getModuleId=function(c){var m=this._strModuleIdToIntModuleId.get(c);return typeof m=="undefined"&&(m=this._nextId++,this._strModuleIdToIntModuleId.set(c,m),this._intModuleIdToStrModuleId[m]=c),m},g.prototype.getStrModuleId=function(c){return this._intModuleIdToStrModuleId[c]},g}(),N=function(){function g(c){this.id=c}return g.EXPORTS=new g(0),g.MODULE=new g(1),g.REQUIRE=new g(2),g}();U.RegularDependency=N;var o=function(){function g(c,m,S){this.id=c,this.pluginId=m,this.pluginParam=S}return g}();U.PluginDependency=o;var w=function(){function g(c,m,S,t,d){d===void 0&&(d=0),this._env=c,this._scriptLoader=m,this._loaderAvailableTimestamp=d,this._defineFunc=S,this._requireFunc=t,this._moduleIdProvider=new e,this._config=new U.Configuration(this._env),this._hasDependencyCycle=!1,this._modules2=[],this._knownModules2=[],this._inverseDependencies2=[],this._inversePluginDependencies2=new Map,this._currentAnonymousDefineCall=null,this._recorder=null,this._buildInfoPath=[],this._buildInfoDefineStack=[],this._buildInfoDependencies=[]}return g.prototype.reset=function(){return new g(this._env,this._scriptLoader,this._defineFunc,this._requireFunc,this._loaderAvailableTimestamp)},g.prototype.getGlobalAMDDefineFunc=function(){return this._defineFunc},g.prototype.getGlobalAMDRequireFunc=function(){return this._requireFunc},g._findRelevantLocationInStack=function(c,m){for(var S=function(i){return i.replace(/\\/g,"/")},t=S(c),d=m.split(/\n/),h=0;h=0){var t=m.resolveModule(c.substr(0,S)),d=m.resolveModule(c.substr(S+1)),h=this._moduleIdProvider.getModuleId(t+"!"+d),v=this._moduleIdProvider.getModuleId(t);return new o(h,v,d)}return new N(this._moduleIdProvider.getModuleId(m.resolveModule(c)))},g.prototype._normalizeDependencies=function(c,m){for(var S=[],t=0,d=0,h=c.length;d0;){var C=L.shift(),y=this._modules2[C];y&&(v=y.onDependencyError(S)||v);var p=this._inverseDependencies2[C];if(p)for(var d=0,h=p.length;d0;){var L=v.shift(),C=L.dependencies;if(C)for(var d=0,h=C.length;d=t.length)m._onLoadError(c,L);else{var C=t[h],y=m.getRecorder();if(m._config.isBuild()&&C==="empty:"){m._buildInfoPath[c]=C,m.defineModule(m._moduleIdProvider.getStrModuleId(c),[],null,null,null),m._onLoad(c);return}y.record(10,C),m._scriptLoader.load(m,C,function(){m._config.isBuild()&&(m._buildInfoPath[c]=C),y.record(11,C),m._onLoad(c)},function(p){y.record(12,C),v(p)})}};v(null)}},g.prototype._loadPluginDependency=function(c,m){var S=this;if(!(this._modules2[m.id]||this._knownModules2[m.id])){this._knownModules2[m.id]=!0;var t=function(d){S.defineModule(S._moduleIdProvider.getStrModuleId(m.id),[],d,null,null)};t.error=function(d){S._config.onError(S._createLoadError(m.id,d))},c.load(m.pluginParam,this._createRequire(r.ROOT),t,this._config.getOptionsLiteral())}},g.prototype._resolve=function(c){var m=this,S=c.dependencies;if(S)for(var t=0,d=S.length;t 
    +`)),c.unresolvedDependenciesCount--;continue}if(this._inverseDependencies2[h.id]=this._inverseDependencies2[h.id]||[],this._inverseDependencies2[h.id].push(c.id),h instanceof o){var C=this._modules2[h.pluginId];if(C&&C.isComplete()){this._loadPluginDependency(C.exports,h);continue}var y=this._inversePluginDependencies2.get(h.pluginId);y||(y=[],this._inversePluginDependencies2.set(h.pluginId,y)),y.push(h),this._loadModule(h.pluginId);continue}this._loadModule(h.id)}c.unresolvedDependenciesCount===0&&this._onModuleComplete(c)},g.prototype._onModuleComplete=function(c){var m=this,S=this.getRecorder();if(!c.isComplete()){var t=c.dependencies,d=[];if(t)for(var h=0,v=t.length;hO===V){if(R===I)return!0;if(!R||!I||R.length!==I.length)return!1;for(let O=0,V=R.length;O0)V=K-1;else return K}return-(O+1)}r.binarySearch=o;function w(R,I){let F=0,O=R.length;if(O===0)return 0;for(;F=I.length)throw new TypeError("invalid index");let O=I[Math.floor(I.length*Math.random())],V=[],K=[],$=[];for(let z of I){const n=F(z,O);n<0?V.push(z):n>0?K.push(z):$.push(z)}return R!!I)}r.coalesce=m;function S(R){return!Array.isArray(R)||R.length===0}r.isFalsyOrEmpty=S;function t(R){return Array.isArray(R)&&R.length>0}r.isNonEmptyArray=t;function d(R,I=F=>F){const F=new Set;return R.filter(O=>{const V=I(O);return F.has(V)?!1:(F.add(V),!0)})}r.distinct=d;function h(R,I){const F=v(R,I);if(F!==-1)return R[F]}r.findLast=h;function v(R,I){for(let F=R.length-1;F>=0;F--){const O=R[F];if(I(O))return F}return-1}r.lastIndex=v;function L(R,I){return R.length>0?R[0]:I}r.firstOrDefault=L;function C(R){return[].concat(...R)}r.flatten=C;function y(R,I){let F=typeof I=="number"?R:0;typeof I=="number"?F=R:(F=0,I=R);const O=[];if(F<=I)for(let V=F;VI;V--)O.push(V);return O}r.range=y;function p(R,I,F){const O=R.slice(0,I),V=R.slice(I);return O.concat(F,V)}r.arrayInsert=p;function s(R,I){const F=R.indexOf(I);F>-1&&(R.splice(F,1),R.unshift(I))}r.pushToStart=s;function i(R,I){const F=R.indexOf(I);F>-1&&(R.splice(F,1),R.push(I))}r.pushToEnd=i;function a(R){return Array.isArray(R)?R:[R]}r.asArray=a;function l(R,I,F){const O=u(R,I),V=R.length,K=F.length;R.length=V+K;for(let $=V-1;$>=O;$--)R[$+K]=R[$];for(let $=0;$I(R(F),R(O))}r.compareBy=_;const b=(R,I)=>R-I;r.numberComparator=b;function A(R,I){if(R.length===0)return;let F=R[0];for(let O=1;O0&&(F=V)}return F}r.findMaxBy=A;function P(R,I){if(R.length===0)return;let F=R[0];for(let O=1;O=0&&(F=V)}return F}r.findLastMaxBy=P;function D(R,I){return A(R,(F,O)=>-I(F,O))}r.findMinBy=D;class k{constructor(I){this.items=I,this.firstIdx=0,this.lastIdx=this.items.length-1}takeWhile(I){let F=this.firstIdx;for(;F=0&&I(this.items[F]);)F--;const O=F===this.lastIdx?null:this.items.slice(F+1,this.lastIdx+1);return this.lastIdx=F,O}peek(){return this.items[this.firstIdx]}dequeue(){const I=this.items[this.firstIdx];return this.firstIdx++,I}takeCount(I){const F=this.items.slice(this.firstIdx,this.firstIdx+I);return this.firstIdx+=I,F}}r.ArrayQueue=k}),Y(Q[17],Z([0,1]),function(U,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.LRUCachedComputed=void 0;class E{constructor(N){this.computeFn=N,this.lastCache=void 0,this.lastArgKey=void 0}get(N){const o=JSON.stringify(N);return this.lastArgKey!==o&&(this.lastArgKey=o,this.lastCache=this.computeFn(N)),this.lastCache}}r.LRUCachedComputed=E}),Y(Q[18],Z([0,1]),function(U,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.CSSIcon=r.Codicon=r.getCodiconAriaLabel=void 0;function E(o){return o?o.replace(/\$\((.*?)\)/g,(w,g)=>` ${g} `).trim():""}r.getCodiconAriaLabel=E;class e{constructor(w,g,c){this.id=w,this.definition=g,this.description=c,e._allCodicons.push(this)}get classNames(){return"codicon codicon-"+this.id}get classNamesArray(){return["codicon","codicon-"+this.id]}get cssSelector(){return".codicon.codicon-"+this.id}static getAll(){return e._allCodicons}}r.Codicon=e,e._allCodicons=[],e.add=new e("add",{fontCharacter:"\\ea60"}),e.plus=new e("plus",e.add.definition),e.gistNew=new e("gist-new",e.add.definition),e.repoCreate=new e("repo-create",e.add.definition),e.lightbulb=new e("lightbulb",{fontCharacter:"\\ea61"}),e.lightBulb=new e("light-bulb",{fontCharacter:"\\ea61"}),e.repo=new e("repo",{fontCharacter:"\\ea62"}),e.repoDelete=new e("repo-delete",{fontCharacter:"\\ea62"}),e.gistFork=new e("gist-fork",{fontCharacter:"\\ea63"}),e.repoForked=new e("repo-forked",{fontCharacter:"\\ea63"}),e.gitPullRequest=new e("git-pull-request",{fontCharacter:"\\ea64"}),e.gitPullRequestAbandoned=new e("git-pull-request-abandoned",{fontCharacter:"\\ea64"}),e.recordKeys=new e("record-keys",{fontCharacter:"\\ea65"}),e.keyboard=new e("keyboard",{fontCharacter:"\\ea65"}),e.tag=new e("tag",{fontCharacter:"\\ea66"}),e.tagAdd=new e("tag-add",{fontCharacter:"\\ea66"}),e.tagRemove=new e("tag-remove",{fontCharacter:"\\ea66"}),e.person=new e("person",{fontCharacter:"\\ea67"}),e.personFollow=new e("person-follow",{fontCharacter:"\\ea67"}),e.personOutline=new e("person-outline",{fontCharacter:"\\ea67"}),e.personFilled=new e("person-filled",{fontCharacter:"\\ea67"}),e.gitBranch=new e("git-branch",{fontCharacter:"\\ea68"}),e.gitBranchCreate=new e("git-branch-create",{fontCharacter:"\\ea68"}),e.gitBranchDelete=new e("git-branch-delete",{fontCharacter:"\\ea68"}),e.sourceControl=new e("source-control",{fontCharacter:"\\ea68"}),e.mirror=new e("mirror",{fontCharacter:"\\ea69"}),e.mirrorPublic=new e("mirror-public",{fontCharacter:"\\ea69"}),e.star=new e("star",{fontCharacter:"\\ea6a"}),e.starAdd=new e("star-add",{fontCharacter:"\\ea6a"}),e.starDelete=new e("star-delete",{fontCharacter:"\\ea6a"}),e.starEmpty=new e("star-empty",{fontCharacter:"\\ea6a"}),e.comment=new e("comment",{fontCharacter:"\\ea6b"}),e.commentAdd=new e("comment-add",{fontCharacter:"\\ea6b"}),e.alert=new e("alert",{fontCharacter:"\\ea6c"}),e.warning=new e("warning",{fontCharacter:"\\ea6c"}),e.search=new e("search",{fontCharacter:"\\ea6d"}),e.searchSave=new e("search-save",{fontCharacter:"\\ea6d"}),e.logOut=new e("log-out",{fontCharacter:"\\ea6e"}),e.signOut=new e("sign-out",{fontCharacter:"\\ea6e"}),e.logIn=new e("log-in",{fontCharacter:"\\ea6f"}),e.signIn=new e("sign-in",{fontCharacter:"\\ea6f"}),e.eye=new e("eye",{fontCharacter:"\\ea70"}),e.eyeUnwatch=new e("eye-unwatch",{fontCharacter:"\\ea70"}),e.eyeWatch=new e("eye-watch",{fontCharacter:"\\ea70"}),e.circleFilled=new e("circle-filled",{fontCharacter:"\\ea71"}),e.primitiveDot=new e("primitive-dot",{fontCharacter:"\\ea71"}),e.closeDirty=new e("close-dirty",{fontCharacter:"\\ea71"}),e.debugBreakpoint=new e("debug-breakpoint",{fontCharacter:"\\ea71"}),e.debugBreakpointDisabled=new e("debug-breakpoint-disabled",{fontCharacter:"\\ea71"}),e.debugHint=new e("debug-hint",{fontCharacter:"\\ea71"}),e.primitiveSquare=new e("primitive-square",{fontCharacter:"\\ea72"}),e.edit=new e("edit",{fontCharacter:"\\ea73"}),e.pencil=new e("pencil",{fontCharacter:"\\ea73"}),e.info=new e("info",{fontCharacter:"\\ea74"}),e.issueOpened=new e("issue-opened",{fontCharacter:"\\ea74"}),e.gistPrivate=new e("gist-private",{fontCharacter:"\\ea75"}),e.gitForkPrivate=new e("git-fork-private",{fontCharacter:"\\ea75"}),e.lock=new e("lock",{fontCharacter:"\\ea75"}),e.mirrorPrivate=new e("mirror-private",{fontCharacter:"\\ea75"}),e.close=new e("close",{fontCharacter:"\\ea76"}),e.removeClose=new e("remove-close",{fontCharacter:"\\ea76"}),e.x=new e("x",{fontCharacter:"\\ea76"}),e.repoSync=new e("repo-sync",{fontCharacter:"\\ea77"}),e.sync=new e("sync",{fontCharacter:"\\ea77"}),e.clone=new e("clone",{fontCharacter:"\\ea78"}),e.desktopDownload=new e("desktop-download",{fontCharacter:"\\ea78"}),e.beaker=new e("beaker",{fontCharacter:"\\ea79"}),e.microscope=new e("microscope",{fontCharacter:"\\ea79"}),e.vm=new e("vm",{fontCharacter:"\\ea7a"}),e.deviceDesktop=new e("device-desktop",{fontCharacter:"\\ea7a"}),e.file=new e("file",{fontCharacter:"\\ea7b"}),e.fileText=new e("file-text",{fontCharacter:"\\ea7b"}),e.more=new e("more",{fontCharacter:"\\ea7c"}),e.ellipsis=new e("ellipsis",{fontCharacter:"\\ea7c"}),e.kebabHorizontal=new e("kebab-horizontal",{fontCharacter:"\\ea7c"}),e.mailReply=new e("mail-reply",{fontCharacter:"\\ea7d"}),e.reply=new e("reply",{fontCharacter:"\\ea7d"}),e.organization=new e("organization",{fontCharacter:"\\ea7e"}),e.organizationFilled=new e("organization-filled",{fontCharacter:"\\ea7e"}),e.organizationOutline=new e("organization-outline",{fontCharacter:"\\ea7e"}),e.newFile=new e("new-file",{fontCharacter:"\\ea7f"}),e.fileAdd=new e("file-add",{fontCharacter:"\\ea7f"}),e.newFolder=new e("new-folder",{fontCharacter:"\\ea80"}),e.fileDirectoryCreate=new e("file-directory-create",{fontCharacter:"\\ea80"}),e.trash=new e("trash",{fontCharacter:"\\ea81"}),e.trashcan=new e("trashcan",{fontCharacter:"\\ea81"}),e.history=new e("history",{fontCharacter:"\\ea82"}),e.clock=new e("clock",{fontCharacter:"\\ea82"}),e.folder=new e("folder",{fontCharacter:"\\ea83"}),e.fileDirectory=new e("file-directory",{fontCharacter:"\\ea83"}),e.symbolFolder=new e("symbol-folder",{fontCharacter:"\\ea83"}),e.logoGithub=new e("logo-github",{fontCharacter:"\\ea84"}),e.markGithub=new e("mark-github",{fontCharacter:"\\ea84"}),e.github=new e("github",{fontCharacter:"\\ea84"}),e.terminal=new e("terminal",{fontCharacter:"\\ea85"}),e.console=new e("console",{fontCharacter:"\\ea85"}),e.repl=new e("repl",{fontCharacter:"\\ea85"}),e.zap=new e("zap",{fontCharacter:"\\ea86"}),e.symbolEvent=new e("symbol-event",{fontCharacter:"\\ea86"}),e.error=new e("error",{fontCharacter:"\\ea87"}),e.stop=new e("stop",{fontCharacter:"\\ea87"}),e.variable=new e("variable",{fontCharacter:"\\ea88"}),e.symbolVariable=new e("symbol-variable",{fontCharacter:"\\ea88"}),e.array=new e("array",{fontCharacter:"\\ea8a"}),e.symbolArray=new e("symbol-array",{fontCharacter:"\\ea8a"}),e.symbolModule=new e("symbol-module",{fontCharacter:"\\ea8b"}),e.symbolPackage=new e("symbol-package",{fontCharacter:"\\ea8b"}),e.symbolNamespace=new e("symbol-namespace",{fontCharacter:"\\ea8b"}),e.symbolObject=new e("symbol-object",{fontCharacter:"\\ea8b"}),e.symbolMethod=new e("symbol-method",{fontCharacter:"\\ea8c"}),e.symbolFunction=new e("symbol-function",{fontCharacter:"\\ea8c"}),e.symbolConstructor=new e("symbol-constructor",{fontCharacter:"\\ea8c"}),e.symbolBoolean=new e("symbol-boolean",{fontCharacter:"\\ea8f"}),e.symbolNull=new e("symbol-null",{fontCharacter:"\\ea8f"}),e.symbolNumeric=new e("symbol-numeric",{fontCharacter:"\\ea90"}),e.symbolNumber=new e("symbol-number",{fontCharacter:"\\ea90"}),e.symbolStructure=new e("symbol-structure",{fontCharacter:"\\ea91"}),e.symbolStruct=new e("symbol-struct",{fontCharacter:"\\ea91"}),e.symbolParameter=new e("symbol-parameter",{fontCharacter:"\\ea92"}),e.symbolTypeParameter=new e("symbol-type-parameter",{fontCharacter:"\\ea92"}),e.symbolKey=new e("symbol-key",{fontCharacter:"\\ea93"}),e.symbolText=new e("symbol-text",{fontCharacter:"\\ea93"}),e.symbolReference=new e("symbol-reference",{fontCharacter:"\\ea94"}),e.goToFile=new e("go-to-file",{fontCharacter:"\\ea94"}),e.symbolEnum=new e("symbol-enum",{fontCharacter:"\\ea95"}),e.symbolValue=new e("symbol-value",{fontCharacter:"\\ea95"}),e.symbolRuler=new e("symbol-ruler",{fontCharacter:"\\ea96"}),e.symbolUnit=new e("symbol-unit",{fontCharacter:"\\ea96"}),e.activateBreakpoints=new e("activate-breakpoints",{fontCharacter:"\\ea97"}),e.archive=new e("archive",{fontCharacter:"\\ea98"}),e.arrowBoth=new e("arrow-both",{fontCharacter:"\\ea99"}),e.arrowDown=new e("arrow-down",{fontCharacter:"\\ea9a"}),e.arrowLeft=new e("arrow-left",{fontCharacter:"\\ea9b"}),e.arrowRight=new e("arrow-right",{fontCharacter:"\\ea9c"}),e.arrowSmallDown=new e("arrow-small-down",{fontCharacter:"\\ea9d"}),e.arrowSmallLeft=new e("arrow-small-left",{fontCharacter:"\\ea9e"}),e.arrowSmallRight=new e("arrow-small-right",{fontCharacter:"\\ea9f"}),e.arrowSmallUp=new e("arrow-small-up",{fontCharacter:"\\eaa0"}),e.arrowUp=new e("arrow-up",{fontCharacter:"\\eaa1"}),e.bell=new e("bell",{fontCharacter:"\\eaa2"}),e.bold=new e("bold",{fontCharacter:"\\eaa3"}),e.book=new e("book",{fontCharacter:"\\eaa4"}),e.bookmark=new e("bookmark",{fontCharacter:"\\eaa5"}),e.debugBreakpointConditionalUnverified=new e("debug-breakpoint-conditional-unverified",{fontCharacter:"\\eaa6"}),e.debugBreakpointConditional=new e("debug-breakpoint-conditional",{fontCharacter:"\\eaa7"}),e.debugBreakpointConditionalDisabled=new e("debug-breakpoint-conditional-disabled",{fontCharacter:"\\eaa7"}),e.debugBreakpointDataUnverified=new e("debug-breakpoint-data-unverified",{fontCharacter:"\\eaa8"}),e.debugBreakpointData=new e("debug-breakpoint-data",{fontCharacter:"\\eaa9"}),e.debugBreakpointDataDisabled=new e("debug-breakpoint-data-disabled",{fontCharacter:"\\eaa9"}),e.debugBreakpointLogUnverified=new e("debug-breakpoint-log-unverified",{fontCharacter:"\\eaaa"}),e.debugBreakpointLog=new e("debug-breakpoint-log",{fontCharacter:"\\eaab"}),e.debugBreakpointLogDisabled=new e("debug-breakpoint-log-disabled",{fontCharacter:"\\eaab"}),e.briefcase=new e("briefcase",{fontCharacter:"\\eaac"}),e.broadcast=new e("broadcast",{fontCharacter:"\\eaad"}),e.browser=new e("browser",{fontCharacter:"\\eaae"}),e.bug=new e("bug",{fontCharacter:"\\eaaf"}),e.calendar=new e("calendar",{fontCharacter:"\\eab0"}),e.caseSensitive=new e("case-sensitive",{fontCharacter:"\\eab1"}),e.check=new e("check",{fontCharacter:"\\eab2"}),e.checklist=new e("checklist",{fontCharacter:"\\eab3"}),e.chevronDown=new e("chevron-down",{fontCharacter:"\\eab4"}),e.dropDownButton=new e("drop-down-button",e.chevronDown.definition),e.chevronLeft=new e("chevron-left",{fontCharacter:"\\eab5"}),e.chevronRight=new e("chevron-right",{fontCharacter:"\\eab6"}),e.chevronUp=new e("chevron-up",{fontCharacter:"\\eab7"}),e.chromeClose=new e("chrome-close",{fontCharacter:"\\eab8"}),e.chromeMaximize=new e("chrome-maximize",{fontCharacter:"\\eab9"}),e.chromeMinimize=new e("chrome-minimize",{fontCharacter:"\\eaba"}),e.chromeRestore=new e("chrome-restore",{fontCharacter:"\\eabb"}),e.circleOutline=new e("circle-outline",{fontCharacter:"\\eabc"}),e.debugBreakpointUnverified=new e("debug-breakpoint-unverified",{fontCharacter:"\\eabc"}),e.circleSlash=new e("circle-slash",{fontCharacter:"\\eabd"}),e.circuitBoard=new e("circuit-board",{fontCharacter:"\\eabe"}),e.clearAll=new e("clear-all",{fontCharacter:"\\eabf"}),e.clippy=new e("clippy",{fontCharacter:"\\eac0"}),e.closeAll=new e("close-all",{fontCharacter:"\\eac1"}),e.cloudDownload=new e("cloud-download",{fontCharacter:"\\eac2"}),e.cloudUpload=new e("cloud-upload",{fontCharacter:"\\eac3"}),e.code=new e("code",{fontCharacter:"\\eac4"}),e.collapseAll=new e("collapse-all",{fontCharacter:"\\eac5"}),e.colorMode=new e("color-mode",{fontCharacter:"\\eac6"}),e.commentDiscussion=new e("comment-discussion",{fontCharacter:"\\eac7"}),e.compareChanges=new e("compare-changes",{fontCharacter:"\\eafd"}),e.creditCard=new e("credit-card",{fontCharacter:"\\eac9"}),e.dash=new e("dash",{fontCharacter:"\\eacc"}),e.dashboard=new e("dashboard",{fontCharacter:"\\eacd"}),e.database=new e("database",{fontCharacter:"\\eace"}),e.debugContinue=new e("debug-continue",{fontCharacter:"\\eacf"}),e.debugDisconnect=new e("debug-disconnect",{fontCharacter:"\\ead0"}),e.debugPause=new e("debug-pause",{fontCharacter:"\\ead1"}),e.debugRestart=new e("debug-restart",{fontCharacter:"\\ead2"}),e.debugStart=new e("debug-start",{fontCharacter:"\\ead3"}),e.debugStepInto=new e("debug-step-into",{fontCharacter:"\\ead4"}),e.debugStepOut=new e("debug-step-out",{fontCharacter:"\\ead5"}),e.debugStepOver=new e("debug-step-over",{fontCharacter:"\\ead6"}),e.debugStop=new e("debug-stop",{fontCharacter:"\\ead7"}),e.debug=new e("debug",{fontCharacter:"\\ead8"}),e.deviceCameraVideo=new e("device-camera-video",{fontCharacter:"\\ead9"}),e.deviceCamera=new e("device-camera",{fontCharacter:"\\eada"}),e.deviceMobile=new e("device-mobile",{fontCharacter:"\\eadb"}),e.diffAdded=new e("diff-added",{fontCharacter:"\\eadc"}),e.diffIgnored=new e("diff-ignored",{fontCharacter:"\\eadd"}),e.diffModified=new e("diff-modified",{fontCharacter:"\\eade"}),e.diffRemoved=new e("diff-removed",{fontCharacter:"\\eadf"}),e.diffRenamed=new e("diff-renamed",{fontCharacter:"\\eae0"}),e.diff=new e("diff",{fontCharacter:"\\eae1"}),e.discard=new e("discard",{fontCharacter:"\\eae2"}),e.editorLayout=new e("editor-layout",{fontCharacter:"\\eae3"}),e.emptyWindow=new e("empty-window",{fontCharacter:"\\eae4"}),e.exclude=new e("exclude",{fontCharacter:"\\eae5"}),e.extensions=new e("extensions",{fontCharacter:"\\eae6"}),e.eyeClosed=new e("eye-closed",{fontCharacter:"\\eae7"}),e.fileBinary=new e("file-binary",{fontCharacter:"\\eae8"}),e.fileCode=new e("file-code",{fontCharacter:"\\eae9"}),e.fileMedia=new e("file-media",{fontCharacter:"\\eaea"}),e.filePdf=new e("file-pdf",{fontCharacter:"\\eaeb"}),e.fileSubmodule=new e("file-submodule",{fontCharacter:"\\eaec"}),e.fileSymlinkDirectory=new e("file-symlink-directory",{fontCharacter:"\\eaed"}),e.fileSymlinkFile=new e("file-symlink-file",{fontCharacter:"\\eaee"}),e.fileZip=new e("file-zip",{fontCharacter:"\\eaef"}),e.files=new e("files",{fontCharacter:"\\eaf0"}),e.filter=new e("filter",{fontCharacter:"\\eaf1"}),e.flame=new e("flame",{fontCharacter:"\\eaf2"}),e.foldDown=new e("fold-down",{fontCharacter:"\\eaf3"}),e.foldUp=new e("fold-up",{fontCharacter:"\\eaf4"}),e.fold=new e("fold",{fontCharacter:"\\eaf5"}),e.folderActive=new e("folder-active",{fontCharacter:"\\eaf6"}),e.folderOpened=new e("folder-opened",{fontCharacter:"\\eaf7"}),e.gear=new e("gear",{fontCharacter:"\\eaf8"}),e.gift=new e("gift",{fontCharacter:"\\eaf9"}),e.gistSecret=new e("gist-secret",{fontCharacter:"\\eafa"}),e.gist=new e("gist",{fontCharacter:"\\eafb"}),e.gitCommit=new e("git-commit",{fontCharacter:"\\eafc"}),e.gitCompare=new e("git-compare",{fontCharacter:"\\eafd"}),e.gitMerge=new e("git-merge",{fontCharacter:"\\eafe"}),e.githubAction=new e("github-action",{fontCharacter:"\\eaff"}),e.githubAlt=new e("github-alt",{fontCharacter:"\\eb00"}),e.globe=new e("globe",{fontCharacter:"\\eb01"}),e.grabber=new e("grabber",{fontCharacter:"\\eb02"}),e.graph=new e("graph",{fontCharacter:"\\eb03"}),e.gripper=new e("gripper",{fontCharacter:"\\eb04"}),e.heart=new e("heart",{fontCharacter:"\\eb05"}),e.home=new e("home",{fontCharacter:"\\eb06"}),e.horizontalRule=new e("horizontal-rule",{fontCharacter:"\\eb07"}),e.hubot=new e("hubot",{fontCharacter:"\\eb08"}),e.inbox=new e("inbox",{fontCharacter:"\\eb09"}),e.issueClosed=new e("issue-closed",{fontCharacter:"\\eba4"}),e.issueReopened=new e("issue-reopened",{fontCharacter:"\\eb0b"}),e.issues=new e("issues",{fontCharacter:"\\eb0c"}),e.italic=new e("italic",{fontCharacter:"\\eb0d"}),e.jersey=new e("jersey",{fontCharacter:"\\eb0e"}),e.json=new e("json",{fontCharacter:"\\eb0f"}),e.kebabVertical=new e("kebab-vertical",{fontCharacter:"\\eb10"}),e.key=new e("key",{fontCharacter:"\\eb11"}),e.law=new e("law",{fontCharacter:"\\eb12"}),e.lightbulbAutofix=new e("lightbulb-autofix",{fontCharacter:"\\eb13"}),e.linkExternal=new e("link-external",{fontCharacter:"\\eb14"}),e.link=new e("link",{fontCharacter:"\\eb15"}),e.listOrdered=new e("list-ordered",{fontCharacter:"\\eb16"}),e.listUnordered=new e("list-unordered",{fontCharacter:"\\eb17"}),e.liveShare=new e("live-share",{fontCharacter:"\\eb18"}),e.loading=new e("loading",{fontCharacter:"\\eb19"}),e.location=new e("location",{fontCharacter:"\\eb1a"}),e.mailRead=new e("mail-read",{fontCharacter:"\\eb1b"}),e.mail=new e("mail",{fontCharacter:"\\eb1c"}),e.markdown=new e("markdown",{fontCharacter:"\\eb1d"}),e.megaphone=new e("megaphone",{fontCharacter:"\\eb1e"}),e.mention=new e("mention",{fontCharacter:"\\eb1f"}),e.milestone=new e("milestone",{fontCharacter:"\\eb20"}),e.mortarBoard=new e("mortar-board",{fontCharacter:"\\eb21"}),e.move=new e("move",{fontCharacter:"\\eb22"}),e.multipleWindows=new e("multiple-windows",{fontCharacter:"\\eb23"}),e.mute=new e("mute",{fontCharacter:"\\eb24"}),e.noNewline=new e("no-newline",{fontCharacter:"\\eb25"}),e.note=new e("note",{fontCharacter:"\\eb26"}),e.octoface=new e("octoface",{fontCharacter:"\\eb27"}),e.openPreview=new e("open-preview",{fontCharacter:"\\eb28"}),e.package_=new e("package",{fontCharacter:"\\eb29"}),e.paintcan=new e("paintcan",{fontCharacter:"\\eb2a"}),e.pin=new e("pin",{fontCharacter:"\\eb2b"}),e.play=new e("play",{fontCharacter:"\\eb2c"}),e.run=new e("run",{fontCharacter:"\\eb2c"}),e.plug=new e("plug",{fontCharacter:"\\eb2d"}),e.preserveCase=new e("preserve-case",{fontCharacter:"\\eb2e"}),e.preview=new e("preview",{fontCharacter:"\\eb2f"}),e.project=new e("project",{fontCharacter:"\\eb30"}),e.pulse=new e("pulse",{fontCharacter:"\\eb31"}),e.question=new e("question",{fontCharacter:"\\eb32"}),e.quote=new e("quote",{fontCharacter:"\\eb33"}),e.radioTower=new e("radio-tower",{fontCharacter:"\\eb34"}),e.reactions=new e("reactions",{fontCharacter:"\\eb35"}),e.references=new e("references",{fontCharacter:"\\eb36"}),e.refresh=new e("refresh",{fontCharacter:"\\eb37"}),e.regex=new e("regex",{fontCharacter:"\\eb38"}),e.remoteExplorer=new e("remote-explorer",{fontCharacter:"\\eb39"}),e.remote=new e("remote",{fontCharacter:"\\eb3a"}),e.remove=new e("remove",{fontCharacter:"\\eb3b"}),e.replaceAll=new e("replace-all",{fontCharacter:"\\eb3c"}),e.replace=new e("replace",{fontCharacter:"\\eb3d"}),e.repoClone=new e("repo-clone",{fontCharacter:"\\eb3e"}),e.repoForcePush=new e("repo-force-push",{fontCharacter:"\\eb3f"}),e.repoPull=new e("repo-pull",{fontCharacter:"\\eb40"}),e.repoPush=new e("repo-push",{fontCharacter:"\\eb41"}),e.report=new e("report",{fontCharacter:"\\eb42"}),e.requestChanges=new e("request-changes",{fontCharacter:"\\eb43"}),e.rocket=new e("rocket",{fontCharacter:"\\eb44"}),e.rootFolderOpened=new e("root-folder-opened",{fontCharacter:"\\eb45"}),e.rootFolder=new e("root-folder",{fontCharacter:"\\eb46"}),e.rss=new e("rss",{fontCharacter:"\\eb47"}),e.ruby=new e("ruby",{fontCharacter:"\\eb48"}),e.saveAll=new e("save-all",{fontCharacter:"\\eb49"}),e.saveAs=new e("save-as",{fontCharacter:"\\eb4a"}),e.save=new e("save",{fontCharacter:"\\eb4b"}),e.screenFull=new e("screen-full",{fontCharacter:"\\eb4c"}),e.screenNormal=new e("screen-normal",{fontCharacter:"\\eb4d"}),e.searchStop=new e("search-stop",{fontCharacter:"\\eb4e"}),e.server=new e("server",{fontCharacter:"\\eb50"}),e.settingsGear=new e("settings-gear",{fontCharacter:"\\eb51"}),e.settings=new e("settings",{fontCharacter:"\\eb52"}),e.shield=new e("shield",{fontCharacter:"\\eb53"}),e.smiley=new e("smiley",{fontCharacter:"\\eb54"}),e.sortPrecedence=new e("sort-precedence",{fontCharacter:"\\eb55"}),e.splitHorizontal=new e("split-horizontal",{fontCharacter:"\\eb56"}),e.splitVertical=new e("split-vertical",{fontCharacter:"\\eb57"}),e.squirrel=new e("squirrel",{fontCharacter:"\\eb58"}),e.starFull=new e("star-full",{fontCharacter:"\\eb59"}),e.starHalf=new e("star-half",{fontCharacter:"\\eb5a"}),e.symbolClass=new e("symbol-class",{fontCharacter:"\\eb5b"}),e.symbolColor=new e("symbol-color",{fontCharacter:"\\eb5c"}),e.symbolCustomColor=new e("symbol-customcolor",{fontCharacter:"\\eb5c"}),e.symbolConstant=new e("symbol-constant",{fontCharacter:"\\eb5d"}),e.symbolEnumMember=new e("symbol-enum-member",{fontCharacter:"\\eb5e"}),e.symbolField=new e("symbol-field",{fontCharacter:"\\eb5f"}),e.symbolFile=new e("symbol-file",{fontCharacter:"\\eb60"}),e.symbolInterface=new e("symbol-interface",{fontCharacter:"\\eb61"}),e.symbolKeyword=new e("symbol-keyword",{fontCharacter:"\\eb62"}),e.symbolMisc=new e("symbol-misc",{fontCharacter:"\\eb63"}),e.symbolOperator=new e("symbol-operator",{fontCharacter:"\\eb64"}),e.symbolProperty=new e("symbol-property",{fontCharacter:"\\eb65"}),e.wrench=new e("wrench",{fontCharacter:"\\eb65"}),e.wrenchSubaction=new e("wrench-subaction",{fontCharacter:"\\eb65"}),e.symbolSnippet=new e("symbol-snippet",{fontCharacter:"\\eb66"}),e.tasklist=new e("tasklist",{fontCharacter:"\\eb67"}),e.telescope=new e("telescope",{fontCharacter:"\\eb68"}),e.textSize=new e("text-size",{fontCharacter:"\\eb69"}),e.threeBars=new e("three-bars",{fontCharacter:"\\eb6a"}),e.thumbsdown=new e("thumbsdown",{fontCharacter:"\\eb6b"}),e.thumbsup=new e("thumbsup",{fontCharacter:"\\eb6c"}),e.tools=new e("tools",{fontCharacter:"\\eb6d"}),e.triangleDown=new e("triangle-down",{fontCharacter:"\\eb6e"}),e.triangleLeft=new e("triangle-left",{fontCharacter:"\\eb6f"}),e.triangleRight=new e("triangle-right",{fontCharacter:"\\eb70"}),e.triangleUp=new e("triangle-up",{fontCharacter:"\\eb71"}),e.twitter=new e("twitter",{fontCharacter:"\\eb72"}),e.unfold=new e("unfold",{fontCharacter:"\\eb73"}),e.unlock=new e("unlock",{fontCharacter:"\\eb74"}),e.unmute=new e("unmute",{fontCharacter:"\\eb75"}),e.unverified=new e("unverified",{fontCharacter:"\\eb76"}),e.verified=new e("verified",{fontCharacter:"\\eb77"}),e.versions=new e("versions",{fontCharacter:"\\eb78"}),e.vmActive=new e("vm-active",{fontCharacter:"\\eb79"}),e.vmOutline=new e("vm-outline",{fontCharacter:"\\eb7a"}),e.vmRunning=new e("vm-running",{fontCharacter:"\\eb7b"}),e.watch=new e("watch",{fontCharacter:"\\eb7c"}),e.whitespace=new e("whitespace",{fontCharacter:"\\eb7d"}),e.wholeWord=new e("whole-word",{fontCharacter:"\\eb7e"}),e.window=new e("window",{fontCharacter:"\\eb7f"}),e.wordWrap=new e("word-wrap",{fontCharacter:"\\eb80"}),e.zoomIn=new e("zoom-in",{fontCharacter:"\\eb81"}),e.zoomOut=new e("zoom-out",{fontCharacter:"\\eb82"}),e.listFilter=new e("list-filter",{fontCharacter:"\\eb83"}),e.listFlat=new e("list-flat",{fontCharacter:"\\eb84"}),e.listSelection=new e("list-selection",{fontCharacter:"\\eb85"}),e.selection=new e("selection",{fontCharacter:"\\eb85"}),e.listTree=new e("list-tree",{fontCharacter:"\\eb86"}),e.debugBreakpointFunctionUnverified=new e("debug-breakpoint-function-unverified",{fontCharacter:"\\eb87"}),e.debugBreakpointFunction=new e("debug-breakpoint-function",{fontCharacter:"\\eb88"}),e.debugBreakpointFunctionDisabled=new e("debug-breakpoint-function-disabled",{fontCharacter:"\\eb88"}),e.debugStackframeActive=new e("debug-stackframe-active",{fontCharacter:"\\eb89"}),e.debugStackframeDot=new e("debug-stackframe-dot",{fontCharacter:"\\eb8a"}),e.debugStackframe=new e("debug-stackframe",{fontCharacter:"\\eb8b"}),e.debugStackframeFocused=new e("debug-stackframe-focused",{fontCharacter:"\\eb8b"}),e.debugBreakpointUnsupported=new e("debug-breakpoint-unsupported",{fontCharacter:"\\eb8c"}),e.symbolString=new e("symbol-string",{fontCharacter:"\\eb8d"}),e.debugReverseContinue=new e("debug-reverse-continue",{fontCharacter:"\\eb8e"}),e.debugStepBack=new e("debug-step-back",{fontCharacter:"\\eb8f"}),e.debugRestartFrame=new e("debug-restart-frame",{fontCharacter:"\\eb90"}),e.callIncoming=new e("call-incoming",{fontCharacter:"\\eb92"}),e.callOutgoing=new e("call-outgoing",{fontCharacter:"\\eb93"}),e.menu=new e("menu",{fontCharacter:"\\eb94"}),e.expandAll=new e("expand-all",{fontCharacter:"\\eb95"}),e.feedback=new e("feedback",{fontCharacter:"\\eb96"}),e.groupByRefType=new e("group-by-ref-type",{fontCharacter:"\\eb97"}),e.ungroupByRefType=new e("ungroup-by-ref-type",{fontCharacter:"\\eb98"}),e.account=new e("account",{fontCharacter:"\\eb99"}),e.bellDot=new e("bell-dot",{fontCharacter:"\\eb9a"}),e.debugConsole=new e("debug-console",{fontCharacter:"\\eb9b"}),e.library=new e("library",{fontCharacter:"\\eb9c"}),e.output=new e("output",{fontCharacter:"\\eb9d"}),e.runAll=new e("run-all",{fontCharacter:"\\eb9e"}),e.syncIgnored=new e("sync-ignored",{fontCharacter:"\\eb9f"}),e.pinned=new e("pinned",{fontCharacter:"\\eba0"}),e.githubInverted=new e("github-inverted",{fontCharacter:"\\eba1"}),e.debugAlt=new e("debug-alt",{fontCharacter:"\\eb91"}),e.serverProcess=new e("server-process",{fontCharacter:"\\eba2"}),e.serverEnvironment=new e("server-environment",{fontCharacter:"\\eba3"}),e.pass=new e("pass",{fontCharacter:"\\eba4"}),e.stopCircle=new e("stop-circle",{fontCharacter:"\\eba5"}),e.playCircle=new e("play-circle",{fontCharacter:"\\eba6"}),e.record=new e("record",{fontCharacter:"\\eba7"}),e.debugAltSmall=new e("debug-alt-small",{fontCharacter:"\\eba8"}),e.vmConnect=new e("vm-connect",{fontCharacter:"\\eba9"}),e.cloud=new e("cloud",{fontCharacter:"\\ebaa"}),e.merge=new e("merge",{fontCharacter:"\\ebab"}),e.exportIcon=new e("export",{fontCharacter:"\\ebac"}),e.graphLeft=new e("graph-left",{fontCharacter:"\\ebad"}),e.magnet=new e("magnet",{fontCharacter:"\\ebae"}),e.notebook=new e("notebook",{fontCharacter:"\\ebaf"}),e.redo=new e("redo",{fontCharacter:"\\ebb0"}),e.checkAll=new e("check-all",{fontCharacter:"\\ebb1"}),e.pinnedDirty=new e("pinned-dirty",{fontCharacter:"\\ebb2"}),e.passFilled=new e("pass-filled",{fontCharacter:"\\ebb3"}),e.circleLargeFilled=new e("circle-large-filled",{fontCharacter:"\\ebb4"}),e.circleLargeOutline=new e("circle-large-outline",{fontCharacter:"\\ebb5"}),e.combine=new e("combine",{fontCharacter:"\\ebb6"}),e.gather=new e("gather",{fontCharacter:"\\ebb6"}),e.table=new e("table",{fontCharacter:"\\ebb7"}),e.variableGroup=new e("variable-group",{fontCharacter:"\\ebb8"}),e.typeHierarchy=new e("type-hierarchy",{fontCharacter:"\\ebb9"}),e.typeHierarchySub=new e("type-hierarchy-sub",{fontCharacter:"\\ebba"}),e.typeHierarchySuper=new e("type-hierarchy-super",{fontCharacter:"\\ebbb"}),e.gitPullRequestCreate=new e("git-pull-request-create",{fontCharacter:"\\ebbc"}),e.runAbove=new e("run-above",{fontCharacter:"\\ebbd"}),e.runBelow=new e("run-below",{fontCharacter:"\\ebbe"}),e.notebookTemplate=new e("notebook-template",{fontCharacter:"\\ebbf"}),e.debugRerun=new e("debug-rerun",{fontCharacter:"\\ebc0"}),e.workspaceTrusted=new e("workspace-trusted",{fontCharacter:"\\ebc1"}),e.workspaceUntrusted=new e("workspace-untrusted",{fontCharacter:"\\ebc2"}),e.workspaceUnspecified=new e("workspace-unspecified",{fontCharacter:"\\ebc3"}),e.terminalCmd=new e("terminal-cmd",{fontCharacter:"\\ebc4"}),e.terminalDebian=new e("terminal-debian",{fontCharacter:"\\ebc5"}),e.terminalLinux=new e("terminal-linux",{fontCharacter:"\\ebc6"}),e.terminalPowershell=new e("terminal-powershell",{fontCharacter:"\\ebc7"}),e.terminalTmux=new e("terminal-tmux",{fontCharacter:"\\ebc8"}),e.terminalUbuntu=new e("terminal-ubuntu",{fontCharacter:"\\ebc9"}),e.terminalBash=new e("terminal-bash",{fontCharacter:"\\ebca"}),e.arrowSwap=new e("arrow-swap",{fontCharacter:"\\ebcb"}),e.copy=new e("copy",{fontCharacter:"\\ebcc"}),e.personAdd=new e("person-add",{fontCharacter:"\\ebcd"}),e.filterFilled=new e("filter-filled",{fontCharacter:"\\ebce"}),e.wand=new e("wand",{fontCharacter:"\\ebcf"}),e.debugLineByLine=new e("debug-line-by-line",{fontCharacter:"\\ebd0"}),e.inspect=new e("inspect",{fontCharacter:"\\ebd1"}),e.layers=new e("layers",{fontCharacter:"\\ebd2"}),e.layersDot=new e("layers-dot",{fontCharacter:"\\ebd3"}),e.layersActive=new e("layers-active",{fontCharacter:"\\ebd4"}),e.compass=new e("compass",{fontCharacter:"\\ebd5"}),e.compassDot=new e("compass-dot",{fontCharacter:"\\ebd6"}),e.compassActive=new e("compass-active",{fontCharacter:"\\ebd7"}),e.azure=new e("azure",{fontCharacter:"\\ebd8"}),e.issueDraft=new e("issue-draft",{fontCharacter:"\\ebd9"}),e.gitPullRequestClosed=new e("git-pull-request-closed",{fontCharacter:"\\ebda"}),e.gitPullRequestDraft=new e("git-pull-request-draft",{fontCharacter:"\\ebdb"}),e.debugAll=new e("debug-all",{fontCharacter:"\\ebdc"}),e.debugCoverage=new e("debug-coverage",{fontCharacter:"\\ebdd"}),e.runErrors=new e("run-errors",{fontCharacter:"\\ebde"}),e.folderLibrary=new e("folder-library",{fontCharacter:"\\ebdf"}),e.debugContinueSmall=new e("debug-continue-small",{fontCharacter:"\\ebe0"}),e.beakerStop=new e("beaker-stop",{fontCharacter:"\\ebe1"}),e.graphLine=new e("graph-line",{fontCharacter:"\\ebe2"}),e.graphScatter=new e("graph-scatter",{fontCharacter:"\\ebe3"}),e.pieChart=new e("pie-chart",{fontCharacter:"\\ebe4"}),e.bracket=new e("bracket",e.json.definition),e.bracketDot=new e("bracket-dot",{fontCharacter:"\\ebe5"}),e.bracketError=new e("bracket-error",{fontCharacter:"\\ebe6"}),e.lockSmall=new e("lock-small",{fontCharacter:"\\ebe7"}),e.azureDevops=new e("azure-devops",{fontCharacter:"\\ebe8"}),e.verifiedFilled=new e("verified-filled",{fontCharacter:"\\ebe9"}),e.newLine=new e("newline",{fontCharacter:"\\ebea"}),e.layout=new e("layout",{fontCharacter:"\\ebeb"}),e.layoutActivitybarLeft=new e("layout-activitybar-left",{fontCharacter:"\\ebec"}),e.layoutActivitybarRight=new e("layout-activitybar-right",{fontCharacter:"\\ebed"}),e.layoutPanelLeft=new e("layout-panel-left",{fontCharacter:"\\ebee"}),e.layoutPanelCenter=new e("layout-panel-center",{fontCharacter:"\\ebef"}),e.layoutPanelJustify=new e("layout-panel-justify",{fontCharacter:"\\ebf0"}),e.layoutPanelRight=new e("layout-panel-right",{fontCharacter:"\\ebf1"}),e.layoutPanel=new e("layout-panel",{fontCharacter:"\\ebf2"}),e.layoutSidebarLeft=new e("layout-sidebar-left",{fontCharacter:"\\ebf3"}),e.layoutSidebarRight=new e("layout-sidebar-right",{fontCharacter:"\\ebf4"}),e.layoutStatusbar=new e("layout-statusbar",{fontCharacter:"\\ebf5"}),e.layoutMenubar=new e("layout-menubar",{fontCharacter:"\\ebf6"}),e.layoutCentered=new e("layout-centered",{fontCharacter:"\\ebf7"}),e.target=new e("target",{fontCharacter:"\\ebf8"}),e.indent=new e("indent",{fontCharacter:"\\ebf9"}),e.recordSmall=new e("record-small",{fontCharacter:"\\ebfa"}),e.errorSmall=new e("error-small",{fontCharacter:"\\ebfb"}),e.arrowCircleDown=new e("arrow-circle-down",{fontCharacter:"\\ebfc"}),e.arrowCircleLeft=new e("arrow-circle-left",{fontCharacter:"\\ebfd"}),e.arrowCircleRight=new e("arrow-circle-right",{fontCharacter:"\\ebfe"}),e.arrowCircleUp=new e("arrow-circle-up",{fontCharacter:"\\ebff"}),e.dialogError=new e("dialog-error",e.error.definition),e.dialogWarning=new e("dialog-warning",e.warning.definition),e.dialogInfo=new e("dialog-info",e.info.definition),e.dialogClose=new e("dialog-close",e.close.definition),e.treeItemExpanded=new e("tree-item-expanded",e.chevronDown.definition),e.treeFilterOnTypeOn=new e("tree-filter-on-type-on",e.listFilter.definition),e.treeFilterOnTypeOff=new e("tree-filter-on-type-off",e.listSelection.definition),e.treeFilterClear=new e("tree-filter-clear",e.close.definition),e.treeItemLoading=new e("tree-item-loading",e.loading.definition),e.menuSelection=new e("menu-selection",e.check.definition),e.menuSubmenu=new e("menu-submenu",e.chevronRight.definition),e.menuBarMore=new e("menubar-more",e.more.definition),e.scrollbarButtonLeft=new e("scrollbar-button-left",e.triangleLeft.definition),e.scrollbarButtonRight=new e("scrollbar-button-right",e.triangleRight.definition),e.scrollbarButtonUp=new e("scrollbar-button-up",e.triangleUp.definition),e.scrollbarButtonDown=new e("scrollbar-button-down",e.triangleDown.definition),e.toolBarMore=new e("toolbar-more",e.more.definition),e.quickInputBack=new e("quick-input-back",e.arrowLeft.definition);var N;(function(o){o.iconNameSegment="[A-Za-z0-9]+",o.iconNameExpression="[A-Za-z0-9-]+",o.iconModifierExpression="~[A-Za-z]+",o.iconNameCharacter="[A-Za-z0-9~-]";const w=new RegExp(`^(${o.iconNameExpression})(${o.iconModifierExpression})?$`);function g(S){if(S instanceof e)return["codicon","codicon-"+S.id];const t=w.exec(S.id);if(!t)return g(e.error);let[,d,h]=t;const v=["codicon","codicon-"+d];return h&&v.push("codicon-modifier-"+h.substr(1)),v}o.asClassNameArray=g;function c(S){return g(S).join(" ")}o.asClassName=c;function m(S){return"."+g(S).join(".")}o.asCSSSelector=m})(N=r.CSSIcon||(r.CSSIcon={}))}),Y(Q[19],Z([0,1]),function(U,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.DiffChange=void 0;class E{constructor(N,o,w,g){this.originalStart=N,this.originalLength=o,this.modifiedStart=w,this.modifiedLength=g}getOriginalEnd(){return this.originalStart+this.originalLength}getModifiedEnd(){return this.modifiedStart+this.modifiedLength}}r.DiffChange=E}),Y(Q[10],Z([0,1]),function(U,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.NotSupportedError=r.illegalState=r.illegalArgument=r.canceled=r.CancellationError=r.isCancellationError=r.transformErrorForSerialization=r.onUnexpectedExternalError=r.onUnexpectedError=r.errorHandler=r.ErrorHandler=void 0;class E{constructor(){this.listeners=[],this.unexpectedErrorHandler=function(v){setTimeout(()=>{throw v.stack?new Error(v.message+`
    +
    +`+v.stack):v},0)}}emit(v){this.listeners.forEach(L=>{L(v)})}onUnexpectedError(v){this.unexpectedErrorHandler(v),this.emit(v)}onUnexpectedExternalError(v){this.unexpectedErrorHandler(v)}}r.ErrorHandler=E,r.errorHandler=new E;function e(h){g(h)||r.errorHandler.onUnexpectedError(h)}r.onUnexpectedError=e;function N(h){g(h)||r.errorHandler.onUnexpectedExternalError(h)}r.onUnexpectedExternalError=N;function o(h){if(h instanceof Error){let{name:v,message:L}=h;const C=h.stacktrace||h.stack;return{$isError:!0,name:v,message:L,stack:C}}return h}r.transformErrorForSerialization=o;const w="Canceled";function g(h){return h instanceof c?!0:h instanceof Error&&h.name===w&&h.message===w}r.isCancellationError=g;class c extends Error{constructor(){super(w);this.name=this.message}}r.CancellationError=c;function m(){const h=new Error(w);return h.name=h.message,h}r.canceled=m;function S(h){return h?new Error(`Illegal argument: ${h}`):new Error("Illegal argument")}r.illegalArgument=S;function t(h){return h?new Error(`Illegal state: ${h}`):new Error("Illegal state")}r.illegalState=t;class d extends Error{constructor(v){super("NotSupported");v&&(this.message=v)}}r.NotSupportedError=d}),Y(Q[20],Z([0,1]),function(U,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.once=void 0;function E(e){const N=this;let o=!1,w;return function(){return o||(o=!0,w=e.apply(N,arguments)),w}}r.once=E}),Y(Q[21],Z([0,1]),function(U,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.Iterable=void 0;var E;(function(e){function N(a){return a&&typeof a=="object"&&typeof a[Symbol.iterator]=="function"}e.is=N;const o=Object.freeze([]);function w(){return o}e.empty=w;function*g(a){yield a}e.single=g;function c(a){return a||o}e.from=c;function m(a){return!a||a[Symbol.iterator]().next().done===!0}e.isEmpty=m;function S(a){return a[Symbol.iterator]().next().value}e.first=S;function t(a,l){for(const f of a)if(l(f))return!0;return!1}e.some=t;function d(a,l){for(const f of a)if(l(f))return f}e.find=d;function*h(a,l){for(const f of a)l(f)&&(yield f)}e.filter=h;function*v(a,l){let f=0;for(const u of a)yield l(u,f++)}e.map=v;function*L(...a){for(const l of a)for(const f of l)yield f}e.concat=L;function*C(a){for(const l of a)for(const f of l)yield f}e.concatNested=C;function y(a,l,f){let u=f;for(const _ of a)u=l(u,_);return u}e.reduce=y;function*p(a,l,f=a.length){for(l<0&&(l+=a.length),f<0?f+=a.length:f>a.length&&(f=a.length);lu===_){const u=a[Symbol.iterator](),_=l[Symbol.iterator]();for(;;){const b=u.next(),A=_.next();if(b.done!==A.done)return!1;if(b.done)return!0;if(!f(b.value,A.value))return!1}}e.equals=i})(E=r.Iterable||(r.Iterable={}))}),Y(Q[22],Z([0,1]),function(U,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.KeyChord=r.KeyCodeUtils=r.IMMUTABLE_KEY_CODE_TO_CODE=r.IMMUTABLE_CODE_TO_KEY_CODE=r.NATIVE_WINDOWS_KEY_CODE_TO_KEY_CODE=r.EVENT_KEY_CODE_MAP=void 0;class E{constructor(){this._keyCodeToStr=[],this._strToKeyCode=Object.create(null)}define(d,h){this._keyCodeToStr[d]=h,this._strToKeyCode[h.toLowerCase()]=d}keyCodeToStr(d){return this._keyCodeToStr[d]}strToKeyCode(d){return this._strToKeyCode[d.toLowerCase()]||0}}const e=new E,N=new E,o=new E;r.EVENT_KEY_CODE_MAP=new Array(230),r.NATIVE_WINDOWS_KEY_CODE_TO_KEY_CODE={};const w=[],g=Object.create(null),c=Object.create(null);r.IMMUTABLE_CODE_TO_KEY_CODE=[],r.IMMUTABLE_KEY_CODE_TO_CODE=[];for(let t=0;t<=193;t++)r.IMMUTABLE_CODE_TO_KEY_CODE[t]=-1;for(let t=0;t<=127;t++)r.IMMUTABLE_KEY_CODE_TO_CODE[t]=-1;(function(){const t="",d=[[0,1,0,"None",0,"unknown",0,"VK_UNKNOWN",t,t],[0,1,1,"Hyper",0,t,0,t,t,t],[0,1,2,"Super",0,t,0,t,t,t],[0,1,3,"Fn",0,t,0,t,t,t],[0,1,4,"FnLock",0,t,0,t,t,t],[0,1,5,"Suspend",0,t,0,t,t,t],[0,1,6,"Resume",0,t,0,t,t,t],[0,1,7,"Turbo",0,t,0,t,t,t],[0,1,8,"Sleep",0,t,0,"VK_SLEEP",t,t],[0,1,9,"WakeUp",0,t,0,t,t,t],[31,0,10,"KeyA",31,"A",65,"VK_A",t,t],[32,0,11,"KeyB",32,"B",66,"VK_B",t,t],[33,0,12,"KeyC",33,"C",67,"VK_C",t,t],[34,0,13,"KeyD",34,"D",68,"VK_D",t,t],[35,0,14,"KeyE",35,"E",69,"VK_E",t,t],[36,0,15,"KeyF",36,"F",70,"VK_F",t,t],[37,0,16,"KeyG",37,"G",71,"VK_G",t,t],[38,0,17,"KeyH",38,"H",72,"VK_H",t,t],[39,0,18,"KeyI",39,"I",73,"VK_I",t,t],[40,0,19,"KeyJ",40,"J",74,"VK_J",t,t],[41,0,20,"KeyK",41,"K",75,"VK_K",t,t],[42,0,21,"KeyL",42,"L",76,"VK_L",t,t],[43,0,22,"KeyM",43,"M",77,"VK_M",t,t],[44,0,23,"KeyN",44,"N",78,"VK_N",t,t],[45,0,24,"KeyO",45,"O",79,"VK_O",t,t],[46,0,25,"KeyP",46,"P",80,"VK_P",t,t],[47,0,26,"KeyQ",47,"Q",81,"VK_Q",t,t],[48,0,27,"KeyR",48,"R",82,"VK_R",t,t],[49,0,28,"KeyS",49,"S",83,"VK_S",t,t],[50,0,29,"KeyT",50,"T",84,"VK_T",t,t],[51,0,30,"KeyU",51,"U",85,"VK_U",t,t],[52,0,31,"KeyV",52,"V",86,"VK_V",t,t],[53,0,32,"KeyW",53,"W",87,"VK_W",t,t],[54,0,33,"KeyX",54,"X",88,"VK_X",t,t],[55,0,34,"KeyY",55,"Y",89,"VK_Y",t,t],[56,0,35,"KeyZ",56,"Z",90,"VK_Z",t,t],[22,0,36,"Digit1",22,"1",49,"VK_1",t,t],[23,0,37,"Digit2",23,"2",50,"VK_2",t,t],[24,0,38,"Digit3",24,"3",51,"VK_3",t,t],[25,0,39,"Digit4",25,"4",52,"VK_4",t,t],[26,0,40,"Digit5",26,"5",53,"VK_5",t,t],[27,0,41,"Digit6",27,"6",54,"VK_6",t,t],[28,0,42,"Digit7",28,"7",55,"VK_7",t,t],[29,0,43,"Digit8",29,"8",56,"VK_8",t,t],[30,0,44,"Digit9",30,"9",57,"VK_9",t,t],[21,0,45,"Digit0",21,"0",48,"VK_0",t,t],[3,1,46,"Enter",3,"Enter",13,"VK_RETURN",t,t],[9,1,47,"Escape",9,"Escape",27,"VK_ESCAPE",t,t],[1,1,48,"Backspace",1,"Backspace",8,"VK_BACK",t,t],[2,1,49,"Tab",2,"Tab",9,"VK_TAB",t,t],[10,1,50,"Space",10,"Space",32,"VK_SPACE",t,t],[83,0,51,"Minus",83,"-",189,"VK_OEM_MINUS","-","OEM_MINUS"],[81,0,52,"Equal",81,"=",187,"VK_OEM_PLUS","=","OEM_PLUS"],[87,0,53,"BracketLeft",87,"[",219,"VK_OEM_4","[","OEM_4"],[89,0,54,"BracketRight",89,"]",221,"VK_OEM_6","]","OEM_6"],[88,0,55,"Backslash",88,"\\",220,"VK_OEM_5","\\","OEM_5"],[0,0,56,"IntlHash",0,t,0,t,t,t],[80,0,57,"Semicolon",80,";",186,"VK_OEM_1",";","OEM_1"],[90,0,58,"Quote",90,"'",222,"VK_OEM_7","'","OEM_7"],[86,0,59,"Backquote",86,"`",192,"VK_OEM_3","`","OEM_3"],[82,0,60,"Comma",82,",",188,"VK_OEM_COMMA",",","OEM_COMMA"],[84,0,61,"Period",84,".",190,"VK_OEM_PERIOD",".","OEM_PERIOD"],[85,0,62,"Slash",85,"/",191,"VK_OEM_2","/","OEM_2"],[8,1,63,"CapsLock",8,"CapsLock",20,"VK_CAPITAL",t,t],[59,1,64,"F1",59,"F1",112,"VK_F1",t,t],[60,1,65,"F2",60,"F2",113,"VK_F2",t,t],[61,1,66,"F3",61,"F3",114,"VK_F3",t,t],[62,1,67,"F4",62,"F4",115,"VK_F4",t,t],[63,1,68,"F5",63,"F5",116,"VK_F5",t,t],[64,1,69,"F6",64,"F6",117,"VK_F6",t,t],[65,1,70,"F7",65,"F7",118,"VK_F7",t,t],[66,1,71,"F8",66,"F8",119,"VK_F8",t,t],[67,1,72,"F9",67,"F9",120,"VK_F9",t,t],[68,1,73,"F10",68,"F10",121,"VK_F10",t,t],[69,1,74,"F11",69,"F11",122,"VK_F11",t,t],[70,1,75,"F12",70,"F12",123,"VK_F12",t,t],[0,1,76,"PrintScreen",0,t,0,t,t,t],[79,1,77,"ScrollLock",79,"ScrollLock",145,"VK_SCROLL",t,t],[7,1,78,"Pause",7,"PauseBreak",19,"VK_PAUSE",t,t],[19,1,79,"Insert",19,"Insert",45,"VK_INSERT",t,t],[14,1,80,"Home",14,"Home",36,"VK_HOME",t,t],[11,1,81,"PageUp",11,"PageUp",33,"VK_PRIOR",t,t],[20,1,82,"Delete",20,"Delete",46,"VK_DELETE",t,t],[13,1,83,"End",13,"End",35,"VK_END",t,t],[12,1,84,"PageDown",12,"PageDown",34,"VK_NEXT",t,t],[17,1,85,"ArrowRight",17,"RightArrow",39,"VK_RIGHT","Right",t],[15,1,86,"ArrowLeft",15,"LeftArrow",37,"VK_LEFT","Left",t],[18,1,87,"ArrowDown",18,"DownArrow",40,"VK_DOWN","Down",t],[16,1,88,"ArrowUp",16,"UpArrow",38,"VK_UP","Up",t],[78,1,89,"NumLock",78,"NumLock",144,"VK_NUMLOCK",t,t],[108,1,90,"NumpadDivide",108,"NumPad_Divide",111,"VK_DIVIDE",t,t],[103,1,91,"NumpadMultiply",103,"NumPad_Multiply",106,"VK_MULTIPLY",t,t],[106,1,92,"NumpadSubtract",106,"NumPad_Subtract",109,"VK_SUBTRACT",t,t],[104,1,93,"NumpadAdd",104,"NumPad_Add",107,"VK_ADD",t,t],[3,1,94,"NumpadEnter",3,t,0,t,t,t],[94,1,95,"Numpad1",94,"NumPad1",97,"VK_NUMPAD1",t,t],[95,1,96,"Numpad2",95,"NumPad2",98,"VK_NUMPAD2",t,t],[96,1,97,"Numpad3",96,"NumPad3",99,"VK_NUMPAD3",t,t],[97,1,98,"Numpad4",97,"NumPad4",100,"VK_NUMPAD4",t,t],[98,1,99,"Numpad5",98,"NumPad5",101,"VK_NUMPAD5",t,t],[99,1,100,"Numpad6",99,"NumPad6",102,"VK_NUMPAD6",t,t],[100,1,101,"Numpad7",100,"NumPad7",103,"VK_NUMPAD7",t,t],[101,1,102,"Numpad8",101,"NumPad8",104,"VK_NUMPAD8",t,t],[102,1,103,"Numpad9",102,"NumPad9",105,"VK_NUMPAD9",t,t],[93,1,104,"Numpad0",93,"NumPad0",96,"VK_NUMPAD0",t,t],[107,1,105,"NumpadDecimal",107,"NumPad_Decimal",110,"VK_DECIMAL",t,t],[92,0,106,"IntlBackslash",92,"OEM_102",226,"VK_OEM_102",t,t],[58,1,107,"ContextMenu",58,"ContextMenu",93,t,t,t],[0,1,108,"Power",0,t,0,t,t,t],[0,1,109,"NumpadEqual",0,t,0,t,t,t],[71,1,110,"F13",71,"F13",124,"VK_F13",t,t],[72,1,111,"F14",72,"F14",125,"VK_F14",t,t],[73,1,112,"F15",73,"F15",126,"VK_F15",t,t],[74,1,113,"F16",74,"F16",127,"VK_F16",t,t],[75,1,114,"F17",75,"F17",128,"VK_F17",t,t],[76,1,115,"F18",76,"F18",129,"VK_F18",t,t],[77,1,116,"F19",77,"F19",130,"VK_F19",t,t],[0,1,117,"F20",0,t,0,"VK_F20",t,t],[0,1,118,"F21",0,t,0,"VK_F21",t,t],[0,1,119,"F22",0,t,0,"VK_F22",t,t],[0,1,120,"F23",0,t,0,"VK_F23",t,t],[0,1,121,"F24",0,t,0,"VK_F24",t,t],[0,1,122,"Open",0,t,0,t,t,t],[0,1,123,"Help",0,t,0,t,t,t],[0,1,124,"Select",0,t,0,t,t,t],[0,1,125,"Again",0,t,0,t,t,t],[0,1,126,"Undo",0,t,0,t,t,t],[0,1,127,"Cut",0,t,0,t,t,t],[0,1,128,"Copy",0,t,0,t,t,t],[0,1,129,"Paste",0,t,0,t,t,t],[0,1,130,"Find",0,t,0,t,t,t],[0,1,131,"AudioVolumeMute",112,"AudioVolumeMute",173,"VK_VOLUME_MUTE",t,t],[0,1,132,"AudioVolumeUp",113,"AudioVolumeUp",175,"VK_VOLUME_UP",t,t],[0,1,133,"AudioVolumeDown",114,"AudioVolumeDown",174,"VK_VOLUME_DOWN",t,t],[105,1,134,"NumpadComma",105,"NumPad_Separator",108,"VK_SEPARATOR",t,t],[110,0,135,"IntlRo",110,"ABNT_C1",193,"VK_ABNT_C1",t,t],[0,1,136,"KanaMode",0,t,0,t,t,t],[0,0,137,"IntlYen",0,t,0,t,t,t],[0,1,138,"Convert",0,t,0,t,t,t],[0,1,139,"NonConvert",0,t,0,t,t,t],[0,1,140,"Lang1",0,t,0,t,t,t],[0,1,141,"Lang2",0,t,0,t,t,t],[0,1,142,"Lang3",0,t,0,t,t,t],[0,1,143,"Lang4",0,t,0,t,t,t],[0,1,144,"Lang5",0,t,0,t,t,t],[0,1,145,"Abort",0,t,0,t,t,t],[0,1,146,"Props",0,t,0,t,t,t],[0,1,147,"NumpadParenLeft",0,t,0,t,t,t],[0,1,148,"NumpadParenRight",0,t,0,t,t,t],[0,1,149,"NumpadBackspace",0,t,0,t,t,t],[0,1,150,"NumpadMemoryStore",0,t,0,t,t,t],[0,1,151,"NumpadMemoryRecall",0,t,0,t,t,t],[0,1,152,"NumpadMemoryClear",0,t,0,t,t,t],[0,1,153,"NumpadMemoryAdd",0,t,0,t,t,t],[0,1,154,"NumpadMemorySubtract",0,t,0,t,t,t],[0,1,155,"NumpadClear",126,"Clear",12,"VK_CLEAR",t,t],[0,1,156,"NumpadClearEntry",0,t,0,t,t,t],[5,1,0,t,5,"Ctrl",17,"VK_CONTROL",t,t],[4,1,0,t,4,"Shift",16,"VK_SHIFT",t,t],[6,1,0,t,6,"Alt",18,"VK_MENU",t,t],[57,1,0,t,57,"Meta",0,"VK_COMMAND",t,t],[5,1,157,"ControlLeft",5,t,0,"VK_LCONTROL",t,t],[4,1,158,"ShiftLeft",4,t,0,"VK_LSHIFT",t,t],[6,1,159,"AltLeft",6,t,0,"VK_LMENU",t,t],[57,1,160,"MetaLeft",57,t,0,"VK_LWIN",t,t],[5,1,161,"ControlRight",5,t,0,"VK_RCONTROL",t,t],[4,1,162,"ShiftRight",4,t,0,"VK_RSHIFT",t,t],[6,1,163,"AltRight",6,t,0,"VK_RMENU",t,t],[57,1,164,"MetaRight",57,t,0,"VK_RWIN",t,t],[0,1,165,"BrightnessUp",0,t,0,t,t,t],[0,1,166,"BrightnessDown",0,t,0,t,t,t],[0,1,167,"MediaPlay",0,t,0,t,t,t],[0,1,168,"MediaRecord",0,t,0,t,t,t],[0,1,169,"MediaFastForward",0,t,0,t,t,t],[0,1,170,"MediaRewind",0,t,0,t,t,t],[114,1,171,"MediaTrackNext",119,"MediaTrackNext",176,"VK_MEDIA_NEXT_TRACK",t,t],[115,1,172,"MediaTrackPrevious",120,"MediaTrackPrevious",177,"VK_MEDIA_PREV_TRACK",t,t],[116,1,173,"MediaStop",121,"MediaStop",178,"VK_MEDIA_STOP",t,t],[0,1,174,"Eject",0,t,0,t,t,t],[117,1,175,"MediaPlayPause",122,"MediaPlayPause",179,"VK_MEDIA_PLAY_PAUSE",t,t],[0,1,176,"MediaSelect",123,"LaunchMediaPlayer",181,"VK_MEDIA_LAUNCH_MEDIA_SELECT",t,t],[0,1,177,"LaunchMail",124,"LaunchMail",180,"VK_MEDIA_LAUNCH_MAIL",t,t],[0,1,178,"LaunchApp2",125,"LaunchApp2",183,"VK_MEDIA_LAUNCH_APP2",t,t],[0,1,179,"LaunchApp1",0,t,0,"VK_MEDIA_LAUNCH_APP1",t,t],[0,1,180,"SelectTask",0,t,0,t,t,t],[0,1,181,"LaunchScreenSaver",0,t,0,t,t,t],[0,1,182,"BrowserSearch",115,"BrowserSearch",170,"VK_BROWSER_SEARCH",t,t],[0,1,183,"BrowserHome",116,"BrowserHome",172,"VK_BROWSER_HOME",t,t],[112,1,184,"BrowserBack",117,"BrowserBack",166,"VK_BROWSER_BACK",t,t],[113,1,185,"BrowserForward",118,"BrowserForward",167,"VK_BROWSER_FORWARD",t,t],[0,1,186,"BrowserStop",0,t,0,"VK_BROWSER_STOP",t,t],[0,1,187,"BrowserRefresh",0,t,0,"VK_BROWSER_REFRESH",t,t],[0,1,188,"BrowserFavorites",0,t,0,"VK_BROWSER_FAVORITES",t,t],[0,1,189,"ZoomToggle",0,t,0,t,t,t],[0,1,190,"MailReply",0,t,0,t,t,t],[0,1,191,"MailForward",0,t,0,t,t,t],[0,1,192,"MailSend",0,t,0,t,t,t],[109,1,0,t,109,"KeyInComposition",229,t,t,t],[111,1,0,t,111,"ABNT_C2",194,"VK_ABNT_C2",t,t],[91,1,0,t,91,"OEM_8",223,"VK_OEM_8",t,t],[0,1,0,t,0,t,0,"VK_KANA",t,t],[0,1,0,t,0,t,0,"VK_HANGUL",t,t],[0,1,0,t,0,t,0,"VK_JUNJA",t,t],[0,1,0,t,0,t,0,"VK_FINAL",t,t],[0,1,0,t,0,t,0,"VK_HANJA",t,t],[0,1,0,t,0,t,0,"VK_KANJI",t,t],[0,1,0,t,0,t,0,"VK_CONVERT",t,t],[0,1,0,t,0,t,0,"VK_NONCONVERT",t,t],[0,1,0,t,0,t,0,"VK_ACCEPT",t,t],[0,1,0,t,0,t,0,"VK_MODECHANGE",t,t],[0,1,0,t,0,t,0,"VK_SELECT",t,t],[0,1,0,t,0,t,0,"VK_PRINT",t,t],[0,1,0,t,0,t,0,"VK_EXECUTE",t,t],[0,1,0,t,0,t,0,"VK_SNAPSHOT",t,t],[0,1,0,t,0,t,0,"VK_HELP",t,t],[0,1,0,t,0,t,0,"VK_APPS",t,t],[0,1,0,t,0,t,0,"VK_PROCESSKEY",t,t],[0,1,0,t,0,t,0,"VK_PACKET",t,t],[0,1,0,t,0,t,0,"VK_DBE_SBCSCHAR",t,t],[0,1,0,t,0,t,0,"VK_DBE_DBCSCHAR",t,t],[0,1,0,t,0,t,0,"VK_ATTN",t,t],[0,1,0,t,0,t,0,"VK_CRSEL",t,t],[0,1,0,t,0,t,0,"VK_EXSEL",t,t],[0,1,0,t,0,t,0,"VK_EREOF",t,t],[0,1,0,t,0,t,0,"VK_PLAY",t,t],[0,1,0,t,0,t,0,"VK_ZOOM",t,t],[0,1,0,t,0,t,0,"VK_NONAME",t,t],[0,1,0,t,0,t,0,"VK_PA1",t,t],[0,1,0,t,0,t,0,"VK_OEM_CLEAR",t,t]];let h=[],v=[];for(const L of d){const[C,y,p,s,i,a,l,f,u,_]=L;if(v[p]||(v[p]=!0,w[p]=s,g[s]=p,c[s.toLowerCase()]=p,y&&(r.IMMUTABLE_CODE_TO_KEY_CODE[p]=i,i!==0&&i!==3&&i!==5&&i!==4&&i!==6&&i!==57&&(r.IMMUTABLE_KEY_CODE_TO_CODE[i]=p))),!h[i]){if(h[i]=!0,!a)throw new Error(`String representation missing for key code ${i} around scan code ${s}`);e.define(i,a),N.define(i,u||a),o.define(i,_||u||a)}l&&(r.EVENT_KEY_CODE_MAP[l]=i),f&&(r.NATIVE_WINDOWS_KEY_CODE_TO_KEY_CODE[f]=i)}r.IMMUTABLE_KEY_CODE_TO_CODE[3]=46})();var m;(function(t){function d(p){return e.keyCodeToStr(p)}t.toString=d;function h(p){return e.strToKeyCode(p)}t.fromString=h;function v(p){return N.keyCodeToStr(p)}t.toUserSettingsUS=v;function L(p){return o.keyCodeToStr(p)}t.toUserSettingsGeneral=L;function C(p){return N.strToKeyCode(p)||o.strToKeyCode(p)}t.fromUserSettings=C;function y(p){if(p>=93&&p<=108)return null;switch(p){case 16:return"Up";case 18:return"Down";case 15:return"Left";case 17:return"Right"}return e.keyCodeToStr(p)}t.toElectronAccelerator=y})(m=r.KeyCodeUtils||(r.KeyCodeUtils={}));function S(t,d){const h=(d&65535)<<16>>>0;return(t|h)>>>0}r.KeyChord=S}),Y(Q[23],Z([0,1]),function(U,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.Lazy=void 0;class E{constructor(N){this.executor=N,this._didRun=!1}getValue(){if(!this._didRun)try{this._value=this.executor()}catch(N){this._error=N}finally{this._didRun=!0}if(this._error)throw this._error;return this._value}get rawValue(){return this._value}}r.Lazy=E}),Y(Q[8],Z([0,1,20,21]),function(U,r,E,e){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.ImmortalReference=r.SafeDisposable=r.MutableDisposable=r.Disposable=r.DisposableStore=r.toDisposable=r.combinedDisposable=r.dispose=r.isDisposable=r.MultiDisposeError=r.markAsSingleton=r.setDisposableTracker=void 0;const N=!1;let o=null;function w(l){o=l}if(r.setDisposableTracker=w,N){const l="__is_disposable_tracked__";w(new class{trackDisposable(f){const u=new Error("Potentially leaked disposable").stack;setTimeout(()=>{f[l]||console.log(u)},3e3)}setParent(f,u){if(f&&f!==p.None)try{f[l]=!0}catch{}}markAsDisposed(f){if(f&&f!==p.None)try{f[l]=!0}catch{}}markAsSingleton(f){}})}function g(l){return o==null||o.trackDisposable(l),l}function c(l){o==null||o.markAsDisposed(l)}function m(l,f){o==null||o.setParent(l,f)}function S(l,f){if(!!o)for(const u of l)o.setParent(u,f)}function t(l){return o==null||o.markAsSingleton(l),l}r.markAsSingleton=t;class d extends Error{constructor(f){super(`Encountered errors while disposing of store. Errors: [${f.join(", ")}]`);this.errors=f}}r.MultiDisposeError=d;function h(l){return typeof l.dispose=="function"&&l.dispose.length===0}r.isDisposable=h;function v(l){if(e.Iterable.is(l)){let f=[];for(const u of l)if(u)try{u.dispose()}catch(_){f.push(_)}if(f.length===1)throw f[0];if(f.length>1)throw new d(f);return Array.isArray(l)?[]:l}else if(l)return l.dispose(),l}r.dispose=v;function L(...l){const f=C(()=>v(l));return S(l,f),f}r.combinedDisposable=L;function C(l){const f=g({dispose:(0,E.once)(()=>{c(f),l()})});return f}r.toDisposable=C;class y{constructor(){this._toDispose=new Set,this._isDisposed=!1,g(this)}dispose(){this._isDisposed||(c(this),this._isDisposed=!0,this.clear())}get isDisposed(){return this._isDisposed}clear(){try{v(this._toDispose.values())}finally{this._toDispose.clear()}}add(f){if(!f)return f;if(f===this)throw new Error("Cannot register a disposable on itself!");return m(f,this),this._isDisposed?y.DISABLE_DISPOSED_WARNING||console.warn(new Error("Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!").stack):this._toDispose.add(f),f}}r.DisposableStore=y,y.DISABLE_DISPOSED_WARNING=!1;class p{constructor(){this._store=new y,g(this),m(this._store,this)}dispose(){c(this),this._store.dispose()}_register(f){if(f===this)throw new Error("Cannot register a disposable on itself!");return this._store.add(f)}}r.Disposable=p,p.None=Object.freeze({dispose(){}});class s{constructor(){this._isDisposed=!1,g(this)}get value(){return this._isDisposed?void 0:this._value}set value(f){var u;this._isDisposed||f===this._value||((u=this._value)===null||u===void 0||u.dispose(),f&&m(f,this),this._value=f)}clear(){this.value=void 0}dispose(){var f;this._isDisposed=!0,c(this),(f=this._value)===null||f===void 0||f.dispose(),this._value=void 0}clearAndLeak(){const f=this._value;return this._value=void 0,f&&m(f,null),f}}r.MutableDisposable=s;class i{constructor(){this.dispose=()=>{},this.unset=()=>{},this.isset=()=>!1,g(this)}set(f){let u=f;return this.unset=()=>u=void 0,this.isset=()=>u!==void 0,this.dispose=()=>{u&&(u(),u=void 0,c(this))},this}}r.SafeDisposable=i;class a{constructor(f){this.object=f}dispose(){}}r.ImmortalReference=a}),Y(Q[24],Z([0,1]),function(U,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.LinkedList=void 0;class E{constructor(o){this.element=o,this.next=E.Undefined,this.prev=E.Undefined}}E.Undefined=new E(void 0);class e{constructor(){this._first=E.Undefined,this._last=E.Undefined,this._size=0}get size(){return this._size}isEmpty(){return this._first===E.Undefined}clear(){let o=this._first;for(;o!==E.Undefined;){const w=o.next;o.prev=E.Undefined,o.next=E.Undefined,o=w}this._first=E.Undefined,this._last=E.Undefined,this._size=0}unshift(o){return this._insert(o,!1)}push(o){return this._insert(o,!0)}_insert(o,w){const g=new E(o);if(this._first===E.Undefined)this._first=g,this._last=g;else if(w){const m=this._last;this._last=g,g.prev=m,m.next=g}else{const m=this._first;this._first=g,g.next=m,m.prev=g}this._size+=1;let c=!1;return()=>{c||(c=!0,this._remove(g))}}shift(){if(this._first!==E.Undefined){const o=this._first.element;return this._remove(this._first),o}}pop(){if(this._last!==E.Undefined){const o=this._last.element;return this._remove(this._last),o}}_remove(o){if(o.prev!==E.Undefined&&o.next!==E.Undefined){const w=o.prev;w.next=o.next,o.next.prev=w}else o.prev===E.Undefined&&o.next===E.Undefined?(this._first=E.Undefined,this._last=E.Undefined):o.next===E.Undefined?(this._last=this._last.prev,this._last.next=E.Undefined):o.prev===E.Undefined&&(this._first=this._first.next,this._first.prev=E.Undefined);this._size-=1}*[Symbol.iterator](){let o=this._first;for(;o!==E.Undefined;)yield o.element,o=o.next}}r.LinkedList=e}),Y(Q[5],Z([0,1]),function(U,r){"use strict";var E;Object.defineProperty(r,"__esModule",{value:!0}),r.isAndroid=r.isEdge=r.isSafari=r.isFirefox=r.isChrome=r.isLittleEndian=r.OS=r.setTimeout0=r.language=r.userAgent=r.isIOS=r.isWebWorker=r.isWeb=r.isNative=r.isLinux=r.isMacintosh=r.isWindows=r.globals=void 0;const e="en";let N=!1,o=!1,w=!1,g=!1,c=!1,m=!1,S=!1,t=!1,d=!1,h,v=e,L,C;r.globals=typeof self=="object"?self:typeof global=="object"?global:{};let y;typeof r.globals.vscode!="undefined"&&typeof r.globals.vscode.process!="undefined"?y=r.globals.vscode.process:typeof process!="undefined"&&(y=process);const p=typeof((E=y==null?void 0:y.versions)===null||E===void 0?void 0:E.electron)=="string",s=p&&(y==null?void 0:y.type)==="renderer";if(typeof navigator=="object"&&!s)C=navigator.userAgent,N=C.indexOf("Windows")>=0,o=C.indexOf("Macintosh")>=0,t=(C.indexOf("Macintosh")>=0||C.indexOf("iPad")>=0||C.indexOf("iPhone")>=0)&&!!navigator.maxTouchPoints&&navigator.maxTouchPoints>0,w=C.indexOf("Linux")>=0,m=!0,h=navigator.language,v=h;else if(typeof y=="object"){N=y.platform==="win32",o=y.platform==="darwin",w=y.platform==="linux",g=w&&!!y.env.SNAP&&!!y.env.SNAP_REVISION,S=p,d=!!y.env.CI||!!y.env.BUILD_ARTIFACTSTAGINGDIRECTORY,h=e,v=e;const u=y.env.VSCODE_NLS_CONFIG;if(u)try{const _=JSON.parse(u),b=_.availableLanguages["*"];h=_.locale,v=b||e,L=_._translationsConfigFile}catch{}c=!0}else console.error("Unable to resolve platform.");let i=0;o?i=1:N?i=3:w&&(i=2),r.isWindows=N,r.isMacintosh=o,r.isLinux=w,r.isNative=c,r.isWeb=m,r.isWebWorker=m&&typeof r.globals.importScripts=="function",r.isIOS=t,r.userAgent=C,r.language=v,r.setTimeout0=(()=>{if(typeof r.globals.postMessage=="function"&&!r.globals.importScripts){let u=[];r.globals.addEventListener("message",b=>{if(b.data&&b.data.vscodeScheduleAsyncWork)for(let A=0,P=u.length;A{const A=++_;u.push({id:A,callback:b}),r.globals.postMessage({vscodeScheduleAsyncWork:A},"*")}}return u=>setTimeout(u)})(),r.OS=o||t?2:N?1:3;let a=!0,l=!1;function f(){if(!l){l=!0;const u=new Uint8Array(2);u[0]=1,u[1]=2,a=new Uint16Array(u.buffer)[0]===(2<<8)+1}return a}r.isLittleEndian=f,r.isChrome=!!(r.userAgent&&r.userAgent.indexOf("Chrome")>=0),r.isFirefox=!!(r.userAgent&&r.userAgent.indexOf("Firefox")>=0),r.isSafari=!!(!r.isChrome&&r.userAgent&&r.userAgent.indexOf("Safari")>=0),r.isEdge=!!(r.userAgent&&r.userAgent.indexOf("Edg/")>=0),r.isAndroid=!!(r.userAgent&&r.userAgent.indexOf("Android")>=0)}),Y(Q[25],Z([0,1,5]),function(U,r,E){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.platform=r.env=r.cwd=void 0;let e;if(typeof E.globals.vscode!="undefined"&&typeof E.globals.vscode.process!="undefined"){const N=E.globals.vscode.process;e={get platform(){return N.platform},get arch(){return N.arch},get env(){return N.env},cwd(){return N.cwd()}}}else typeof process!="undefined"?e={get platform(){return process.platform},get arch(){return process.arch},get env(){return process.env},cwd(){return process.env.VSCODE_CWD||process.cwd()}}:e={get platform(){return E.isWindows?"win32":E.isMacintosh?"darwin":"linux"},get arch(){},get env(){return{}},cwd(){return"/"}};r.cwd=e.cwd,r.env=e.env,r.platform=e.platform}),Y(Q[26],Z([0,1,25]),function(U,r,E){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.sep=r.extname=r.basename=r.dirname=r.relative=r.resolve=r.normalize=r.posix=r.win32=void 0;const e=65,N=97,o=90,w=122,g=46,c=47,m=92,S=58,t=63;class d extends Error{constructor(i,a,l){let f;typeof a=="string"&&a.indexOf("not ")===0?(f="must not be",a=a.replace(/^not /,"")):f="must be";const u=i.indexOf(".")!==-1?"property":"argument";let _=`The "${i}" ${u} ${f} of type ${a}`;_+=`. Received type ${typeof l}`;super(_);this.code="ERR_INVALID_ARG_TYPE"}}function h(s,i){if(typeof s!="string")throw new d(i,"string",s)}function v(s){return s===c||s===m}function L(s){return s===c}function C(s){return s>=e&&s<=o||s>=N&&s<=w}function y(s,i,a,l){let f="",u=0,_=-1,b=0,A=0;for(let P=0;P<=s.length;++P){if(P2){const D=f.lastIndexOf(a);D===-1?(f="",u=0):(f=f.slice(0,D),u=f.length-1-f.lastIndexOf(a)),_=P,b=0;continue}else if(f.length!==0){f="",u=0,_=P,b=0;continue}}i&&(f+=f.length>0?`${a}..`:"..",u=2)}else f.length>0?f+=`${a}${s.slice(_+1,P)}`:f=s.slice(_+1,P),u=P-_-1;_=P,b=0}else A===g&&b!==-1?++b:b=-1}return f}function p(s,i){if(i===null||typeof i!="object")throw new d("pathObject","Object",i);const a=i.dir||i.root,l=i.base||`${i.name||""}${i.ext||""}`;return a?a===i.root?`${a}${l}`:`${a}${s}${l}`:l}r.win32={resolve(...s){let i="",a="",l=!1;for(let f=s.length-1;f>=-1;f--){let u;if(f>=0){if(u=s[f],h(u,"path"),u.length===0)continue}else i.length===0?u=E.cwd():(u=E.env[`=${i}`]||E.cwd(),(u===void 0||u.slice(0,2).toLowerCase()!==i.toLowerCase()&&u.charCodeAt(2)===m)&&(u=`${i}\\`));const _=u.length;let b=0,A="",P=!1;const D=u.charCodeAt(0);if(_===1)v(D)&&(b=1,P=!0);else if(v(D))if(P=!0,v(u.charCodeAt(1))){let k=2,R=k;for(;k<_&&!v(u.charCodeAt(k));)k++;if(k<_&&k!==R){const I=u.slice(R,k);for(R=k;k<_&&v(u.charCodeAt(k));)k++;if(k<_&&k!==R){for(R=k;k<_&&!v(u.charCodeAt(k));)k++;(k===_||k!==R)&&(A=`\\\\${I}\\${u.slice(R,k)}`,b=k)}}}else b=1;else C(D)&&u.charCodeAt(1)===S&&(A=u.slice(0,2),b=2,_>2&&v(u.charCodeAt(2))&&(P=!0,b=3));if(A.length>0)if(i.length>0){if(A.toLowerCase()!==i.toLowerCase())continue}else i=A;if(l){if(i.length>0)break}else if(a=`${u.slice(b)}\\${a}`,l=P,P&&i.length>0)break}return a=y(a,!l,"\\",v),l?`${i}\\${a}`:`${i}${a}`||"."},normalize(s){h(s,"path");const i=s.length;if(i===0)return".";let a=0,l,f=!1;const u=s.charCodeAt(0);if(i===1)return L(u)?"\\":s;if(v(u))if(f=!0,v(s.charCodeAt(1))){let b=2,A=b;for(;b2&&v(s.charCodeAt(2))&&(f=!0,a=3));let _=a0&&v(s.charCodeAt(i-1))&&(_+="\\"),l===void 0?f?`\\${_}`:_:f?`${l}\\${_}`:`${l}${_}`},isAbsolute(s){h(s,"path");const i=s.length;if(i===0)return!1;const a=s.charCodeAt(0);return v(a)||i>2&&C(a)&&s.charCodeAt(1)===S&&v(s.charCodeAt(2))},join(...s){if(s.length===0)return".";let i,a;for(let u=0;u0&&(i===void 0?i=a=_:i+=`\\${_}`)}if(i===void 0)return".";let l=!0,f=0;if(typeof a=="string"&&v(a.charCodeAt(0))){++f;const u=a.length;u>1&&v(a.charCodeAt(1))&&(++f,u>2&&(v(a.charCodeAt(2))?++f:l=!1))}if(l){for(;f=2&&(i=`\\${i.slice(f)}`)}return r.win32.normalize(i)},relative(s,i){if(h(s,"from"),h(i,"to"),s===i)return"";const a=r.win32.resolve(s),l=r.win32.resolve(i);if(a===l||(s=a.toLowerCase(),i=l.toLowerCase(),s===i))return"";let f=0;for(;ff&&s.charCodeAt(u-1)===m;)u--;const _=u-f;let b=0;for(;bb&&i.charCodeAt(A-1)===m;)A--;const P=A-b,D=_D){if(i.charCodeAt(b+R)===m)return l.slice(b+R+1);if(R===2)return l.slice(b+R)}_>D&&(s.charCodeAt(f+R)===m?k=R:R===2&&(k=3)),k===-1&&(k=0)}let I="";for(R=f+k+1;R<=u;++R)(R===u||s.charCodeAt(R)===m)&&(I+=I.length===0?"..":"\\..");return b+=k,I.length>0?`${I}${l.slice(b,A)}`:(l.charCodeAt(b)===m&&++b,l.slice(b,A))},toNamespacedPath(s){if(typeof s!="string")return s;if(s.length===0)return"";const i=r.win32.resolve(s);if(i.length<=2)return s;if(i.charCodeAt(0)===m){if(i.charCodeAt(1)===m){const a=i.charCodeAt(2);if(a!==t&&a!==g)return`\\\\?\\UNC\\${i.slice(2)}`}}else if(C(i.charCodeAt(0))&&i.charCodeAt(1)===S&&i.charCodeAt(2)===m)return`\\\\?\\${i}`;return s},dirname(s){h(s,"path");const i=s.length;if(i===0)return".";let a=-1,l=0;const f=s.charCodeAt(0);if(i===1)return v(f)?s:".";if(v(f)){if(a=l=1,v(s.charCodeAt(1))){let b=2,A=b;for(;b2&&v(s.charCodeAt(2))?3:2,l=a);let u=-1,_=!0;for(let b=i-1;b>=l;--b)if(v(s.charCodeAt(b))){if(!_){u=b;break}}else _=!1;if(u===-1){if(a===-1)return".";u=a}return s.slice(0,u)},basename(s,i){i!==void 0&&h(i,"ext"),h(s,"path");let a=0,l=-1,f=!0,u;if(s.length>=2&&C(s.charCodeAt(0))&&s.charCodeAt(1)===S&&(a=2),i!==void 0&&i.length>0&&i.length<=s.length){if(i===s)return"";let _=i.length-1,b=-1;for(u=s.length-1;u>=a;--u){const A=s.charCodeAt(u);if(v(A)){if(!f){a=u+1;break}}else b===-1&&(f=!1,b=u+1),_>=0&&(A===i.charCodeAt(_)?--_==-1&&(l=u):(_=-1,l=b))}return a===l?l=b:l===-1&&(l=s.length),s.slice(a,l)}for(u=s.length-1;u>=a;--u)if(v(s.charCodeAt(u))){if(!f){a=u+1;break}}else l===-1&&(f=!1,l=u+1);return l===-1?"":s.slice(a,l)},extname(s){h(s,"path");let i=0,a=-1,l=0,f=-1,u=!0,_=0;s.length>=2&&s.charCodeAt(1)===S&&C(s.charCodeAt(0))&&(i=l=2);for(let b=s.length-1;b>=i;--b){const A=s.charCodeAt(b);if(v(A)){if(!u){l=b+1;break}continue}f===-1&&(u=!1,f=b+1),A===g?a===-1?a=b:_!==1&&(_=1):a!==-1&&(_=-1)}return a===-1||f===-1||_===0||_===1&&a===f-1&&a===l+1?"":s.slice(a,f)},format:p.bind(null,"\\"),parse(s){h(s,"path");const i={root:"",dir:"",base:"",ext:"",name:""};if(s.length===0)return i;const a=s.length;let l=0,f=s.charCodeAt(0);if(a===1)return v(f)?(i.root=i.dir=s,i):(i.base=i.name=s,i);if(v(f)){if(l=1,v(s.charCodeAt(1))){let k=2,R=k;for(;k0&&(i.root=s.slice(0,l));let u=-1,_=l,b=-1,A=!0,P=s.length-1,D=0;for(;P>=l;--P){if(f=s.charCodeAt(P),v(f)){if(!A){_=P+1;break}continue}b===-1&&(A=!1,b=P+1),f===g?u===-1?u=P:D!==1&&(D=1):u!==-1&&(D=-1)}return b!==-1&&(u===-1||D===0||D===1&&u===b-1&&u===_+1?i.base=i.name=s.slice(_,b):(i.name=s.slice(_,u),i.base=s.slice(_,b),i.ext=s.slice(u,b))),_>0&&_!==l?i.dir=s.slice(0,_-1):i.dir=i.root,i},sep:"\\",delimiter:";",win32:null,posix:null},r.posix={resolve(...s){let i="",a=!1;for(let l=s.length-1;l>=-1&&!a;l--){const f=l>=0?s[l]:E.cwd();h(f,"path"),f.length!==0&&(i=`${f}/${i}`,a=f.charCodeAt(0)===c)}return i=y(i,!a,"/",L),a?`/${i}`:i.length>0?i:"."},normalize(s){if(h(s,"path"),s.length===0)return".";const i=s.charCodeAt(0)===c,a=s.charCodeAt(s.length-1)===c;return s=y(s,!i,"/",L),s.length===0?i?"/":a?"./":".":(a&&(s+="/"),i?`/${s}`:s)},isAbsolute(s){return h(s,"path"),s.length>0&&s.charCodeAt(0)===c},join(...s){if(s.length===0)return".";let i;for(let a=0;a0&&(i===void 0?i=l:i+=`/${l}`)}return i===void 0?".":r.posix.normalize(i)},relative(s,i){if(h(s,"from"),h(i,"to"),s===i||(s=r.posix.resolve(s),i=r.posix.resolve(i),s===i))return"";const a=1,l=s.length,f=l-a,u=1,_=i.length-u,b=f<_?f:_;let A=-1,P=0;for(;Pb){if(i.charCodeAt(u+P)===c)return i.slice(u+P+1);if(P===0)return i.slice(u+P)}else f>b&&(s.charCodeAt(a+P)===c?A=P:P===0&&(A=0));let D="";for(P=a+A+1;P<=l;++P)(P===l||s.charCodeAt(P)===c)&&(D+=D.length===0?"..":"/..");return`${D}${i.slice(u+A)}`},toNamespacedPath(s){return s},dirname(s){if(h(s,"path"),s.length===0)return".";const i=s.charCodeAt(0)===c;let a=-1,l=!0;for(let f=s.length-1;f>=1;--f)if(s.charCodeAt(f)===c){if(!l){a=f;break}}else l=!1;return a===-1?i?"/":".":i&&a===1?"//":s.slice(0,a)},basename(s,i){i!==void 0&&h(i,"ext"),h(s,"path");let a=0,l=-1,f=!0,u;if(i!==void 0&&i.length>0&&i.length<=s.length){if(i===s)return"";let _=i.length-1,b=-1;for(u=s.length-1;u>=0;--u){const A=s.charCodeAt(u);if(A===c){if(!f){a=u+1;break}}else b===-1&&(f=!1,b=u+1),_>=0&&(A===i.charCodeAt(_)?--_==-1&&(l=u):(_=-1,l=b))}return a===l?l=b:l===-1&&(l=s.length),s.slice(a,l)}for(u=s.length-1;u>=0;--u)if(s.charCodeAt(u)===c){if(!f){a=u+1;break}}else l===-1&&(f=!1,l=u+1);return l===-1?"":s.slice(a,l)},extname(s){h(s,"path");let i=-1,a=0,l=-1,f=!0,u=0;for(let _=s.length-1;_>=0;--_){const b=s.charCodeAt(_);if(b===c){if(!f){a=_+1;break}continue}l===-1&&(f=!1,l=_+1),b===g?i===-1?i=_:u!==1&&(u=1):i!==-1&&(u=-1)}return i===-1||l===-1||u===0||u===1&&i===l-1&&i===a+1?"":s.slice(i,l)},format:p.bind(null,"/"),parse(s){h(s,"path");const i={root:"",dir:"",base:"",ext:"",name:""};if(s.length===0)return i;const a=s.charCodeAt(0)===c;let l;a?(i.root="/",l=1):l=0;let f=-1,u=0,_=-1,b=!0,A=s.length-1,P=0;for(;A>=l;--A){const D=s.charCodeAt(A);if(D===c){if(!b){u=A+1;break}continue}_===-1&&(b=!1,_=A+1),D===g?f===-1?f=A:P!==1&&(P=1):f!==-1&&(P=-1)}if(_!==-1){const D=u===0&&a?1:u;f===-1||P===0||P===1&&f===_-1&&f===u+1?i.base=i.name=s.slice(D,_):(i.name=s.slice(D,f),i.base=s.slice(D,_),i.ext=s.slice(f,_))}return u>0?i.dir=s.slice(0,u-1):a&&(i.dir="/"),i},sep:"/",delimiter:":",win32:null,posix:null},r.posix.win32=r.win32.win32=r.win32,r.posix.posix=r.win32.posix=r.posix,r.normalize=E.platform==="win32"?r.win32.normalize:r.posix.normalize,r.resolve=E.platform==="win32"?r.win32.resolve:r.posix.resolve,r.relative=E.platform==="win32"?r.win32.relative:r.posix.relative,r.dirname=E.platform==="win32"?r.win32.dirname:r.posix.dirname,r.basename=E.platform==="win32"?r.win32.basename:r.posix.basename,r.extname=E.platform==="win32"?r.win32.extname:r.posix.extname,r.sep=E.platform==="win32"?r.win32.sep:r.posix.sep}),Y(Q[11],Z([0,1,5]),function(U,r,E){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.StopWatch=void 0;const e=E.globals.performance&&typeof E.globals.performance.now=="function";class N{constructor(w){this._highResolution=e&&w,this._startTime=this._now(),this._stopTime=-1}static create(w=!0){return new N(w)}stop(){this._stopTime=this._now()}elapsed(){return this._stopTime!==-1?this._stopTime-this._startTime:this._now()-this._startTime}_now(){return this._highResolution?E.globals.performance.now():Date.now()}}r.StopWatch=N}),Y(Q[6],Z([0,1,10,8,24,11]),function(U,r,E,e,N,o){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.Relay=r.EventBufferer=r.DebounceEmitter=r.PauseableEmitter=r.Emitter=r.Event=void 0;let w=!1,g=!1;var c;(function(s){s.None=()=>e.Disposable.None;function i(X){if(g){const{onListenerDidAdd:q}=X,G=d.create();let H=0;X.onListenerDidAdd=()=>{++H==2&&(console.warn("snapshotted emitter LIKELY used public and SHOULD HAVE BEEN created with DisposableStore. snapshotted here"),G.print()),q==null||q()}}}function a(X){return(q,G=null,H)=>{let J=!1,x;return x=X(ee=>{if(!J)return x?x.dispose():J=!0,q.call(G,ee)},null,H),J&&x.dispose(),x}}s.once=a;function l(X,q,G){return P((H,J=null,x)=>X(ee=>H.call(J,q(ee)),null,x),G)}s.map=l;function f(X,q,G){return P((H,J=null,x)=>X(ee=>{q(ee),H.call(J,ee)},null,x),G)}s.forEach=f;function u(X,q,G){return P((H,J=null,x)=>X(ee=>q(ee)&&H.call(J,ee),null,x),G)}s.filter=u;function _(X){return X}s.signal=_;function b(...X){return(q,G=null,H)=>(0,e.combinedDisposable)(...X.map(J=>J(x=>q.call(G,x),null,H)))}s.any=b;function A(X,q,G,H){let J=G;return l(X,x=>(J=q(J,x),J),H)}s.reduce=A;function P(X,q){let G;const H={onFirstListenerAdd(){G=X(J.fire,J)},onLastListenerRemove(){G.dispose()}};q||i(H);const J=new v(H);return q&&q.add(J),J.event}function D(X,q,G=100,H=!1,J,x){let ee,ie,he,_e=0;const be={leakWarningThreshold:J,onFirstListenerAdd(){ee=X(Ce=>{_e++,ie=q(ie,Ce),H&&!he&&(me.fire(ie),ie=void 0),clearTimeout(he),he=setTimeout(()=>{const le=ie;ie=void 0,he=void 0,(!H||_e>1)&&me.fire(le),_e=0},G)})},onLastListenerRemove(){ee.dispose()}};x||i(be);const me=new v(be);return x&&x.add(me),me.event}s.debounce=D;function k(X,q=(H,J)=>H===J,G){let H=!0,J;return u(X,x=>{const ee=H||!q(x,J);return H=!1,J=x,ee},G)}s.latch=k;function R(X,q,G){return[s.filter(X,q,G),s.filter(X,H=>!q(H),G)]}s.split=R;function I(X,q=!1,G=[]){let H=G.slice(),J=X(ie=>{H?H.push(ie):ee.fire(ie)});const x=()=>{H&&H.forEach(ie=>ee.fire(ie)),H=null},ee=new v({onFirstListenerAdd(){J||(J=X(ie=>ee.fire(ie)))},onFirstListenerDidAdd(){H&&(q?setTimeout(x):x())},onLastListenerRemove(){J&&J.dispose(),J=null}});return ee.event}s.buffer=I;class F{constructor(q){this.event=q}map(q){return new F(l(this.event,q))}forEach(q){return new F(f(this.event,q))}filter(q){return new F(u(this.event,q))}reduce(q,G){return new F(A(this.event,q,G))}latch(){return new F(k(this.event))}debounce(q,G=100,H=!1,J){return new F(D(this.event,q,G,H,J))}on(q,G,H){return this.event(q,G,H)}once(q,G,H){return a(this.event)(q,G,H)}}function O(X){return new F(X)}s.chain=O;function V(X,q,G=H=>H){const H=(...ie)=>ee.fire(G(...ie)),J=()=>X.on(q,H),x=()=>X.removeListener(q,H),ee=new v({onFirstListenerAdd:J,onLastListenerRemove:x});return ee.event}s.fromNodeEventEmitter=V;function K(X,q,G=H=>H){const H=(...ie)=>ee.fire(G(...ie)),J=()=>X.addEventListener(q,H),x=()=>X.removeEventListener(q,H),ee=new v({onFirstListenerAdd:J,onLastListenerRemove:x});return ee.event}s.fromDOMEventEmitter=K;function $(X){return new Promise(q=>a(X)(q))}s.toPromise=$;function z(X,q){return q(void 0),X(G=>q(G))}s.runAndSubscribe=z;function n(X,q){let G=null;function H(x){G==null||G.dispose(),G=new e.DisposableStore,q(x,G)}H(void 0);const J=X(x=>H(x));return(0,e.toDisposable)(()=>{J.dispose(),G==null||G.dispose()})}s.runAndSubscribeWithStore=n})(c=r.Event||(r.Event={}));class m{constructor(i){this._listenerCount=0,this._invocationCount=0,this._elapsedOverall=0,this._name=`${i}_${m._idPool++}`}start(i){this._stopWatch=new o.StopWatch(!0),this._listenerCount=i}stop(){if(this._stopWatch){const i=this._stopWatch.elapsed();this._elapsedOverall+=i,this._invocationCount+=1,console.info(`did FIRE ${this._name}: elapsed_ms: ${i.toFixed(5)}, listener: ${this._listenerCount} (elapsed_overall: ${this._elapsedOverall.toFixed(2)}, invocations: ${this._invocationCount})`),this._stopWatch=void 0}}}m._idPool=0;let S=-1;class t{constructor(i,a=Math.random().toString(18).slice(2,5)){this.customThreshold=i,this.name=a,this._warnCountdown=0}dispose(){this._stacks&&this._stacks.clear()}check(i,a){let l=S;if(typeof this.customThreshold=="number"&&(l=this.customThreshold),l<=0||a{const u=this._stacks.get(i.value)||0;this._stacks.set(i.value,u-1)}}}class d{constructor(i){this.value=i}static create(){var i;return new d((i=new Error().stack)!==null&&i!==void 0?i:"")}print(){console.warn(this.value.split(`
    +`).slice(2).join(`
    +`))}}class h{constructor(i,a,l){this.callback=i,this.callbackThis=a,this.stack=l,this.subscription=new e.SafeDisposable}invoke(i){this.callback.call(this.callbackThis,i)}}class v{constructor(i){var a;this._disposed=!1,this._options=i,this._leakageMon=S>0?new t(this._options&&this._options.leakWarningThreshold):void 0,this._perfMon=((a=this._options)===null||a===void 0?void 0:a._profName)?new m(this._options._profName):void 0}dispose(){var i,a,l,f;if(!this._disposed){if(this._disposed=!0,this._listeners){if(w){const u=Array.from(this._listeners);queueMicrotask(()=>{var _;for(const b of u)b.subscription.isset()&&(b.subscription.unset(),(_=b.stack)===null||_===void 0||_.print())})}this._listeners.clear()}(i=this._deliveryQueue)===null||i===void 0||i.clear(),(l=(a=this._options)===null||a===void 0?void 0:a.onLastListenerRemove)===null||l===void 0||l.call(a),(f=this._leakageMon)===null||f===void 0||f.dispose()}}get event(){return this._event||(this._event=(i,a,l)=>{var f,u,_;this._listeners||(this._listeners=new N.LinkedList);const b=this._listeners.isEmpty();b&&((f=this._options)===null||f===void 0?void 0:f.onFirstListenerAdd)&&this._options.onFirstListenerAdd(this);let A,P;this._leakageMon&&this._listeners.size>=30&&(P=d.create(),A=this._leakageMon.check(P,this._listeners.size+1)),w&&(P=P??d.create());const D=new h(i,a,P),k=this._listeners.push(D);b&&((u=this._options)===null||u===void 0?void 0:u.onFirstListenerDidAdd)&&this._options.onFirstListenerDidAdd(this),((_=this._options)===null||_===void 0?void 0:_.onListenerDidAdd)&&this._options.onListenerDidAdd(this,i,a);const R=D.subscription.set(()=>{A&&A(),this._disposed||(k(),this._options&&this._options.onLastListenerRemove&&(this._listeners&&!this._listeners.isEmpty()||this._options.onLastListenerRemove(this)))});return l instanceof e.DisposableStore?l.add(R):Array.isArray(l)&&l.push(R),R}),this._event}fire(i){var a,l;if(this._listeners){this._deliveryQueue||(this._deliveryQueue=new N.LinkedList);for(let f of this._listeners)this._deliveryQueue.push([f,i]);for((a=this._perfMon)===null||a===void 0||a.start(this._deliveryQueue.size);this._deliveryQueue.size>0;){const[f,u]=this._deliveryQueue.shift();try{f.invoke(u)}catch(_){(0,E.onUnexpectedError)(_)}}(l=this._perfMon)===null||l===void 0||l.stop()}}}r.Emitter=v;class L extends v{constructor(i){super(i);this._isPaused=0,this._eventQueue=new N.LinkedList,this._mergeFn=i==null?void 0:i.merge}pause(){this._isPaused++}resume(){if(this._isPaused!==0&&--this._isPaused==0)if(this._mergeFn){const i=Array.from(this._eventQueue);this._eventQueue.clear(),super.fire(this._mergeFn(i))}else for(;!this._isPaused&&this._eventQueue.size!==0;)super.fire(this._eventQueue.shift())}fire(i){this._listeners&&(this._isPaused!==0?this._eventQueue.push(i):super.fire(i))}}r.PauseableEmitter=L;class C extends L{constructor(i){var a;super(i);this._delay=(a=i.delay)!==null&&a!==void 0?a:100}fire(i){this._handle||(this.pause(),this._handle=setTimeout(()=>{this._handle=void 0,this.resume()},this._delay)),super.fire(i)}}r.DebounceEmitter=C;class y{constructor(){this.buffers=[]}wrapEvent(i){return(a,l,f)=>i(u=>{const _=this.buffers[this.buffers.length-1];_?_.push(()=>a.call(l,u)):a.call(l,u)},void 0,f)}bufferEvents(i){const a=[];this.buffers.push(a);const l=i();return this.buffers.pop(),a.forEach(f=>f()),l}}r.EventBufferer=y;class p{constructor(){this.listening=!1,this.inputEvent=c.None,this.inputEventListener=e.Disposable.None,this.emitter=new v({onFirstListenerDidAdd:()=>{this.listening=!0,this.inputEventListener=this.inputEvent(this.emitter.fire,this.emitter)},onLastListenerRemove:()=>{this.listening=!1,this.inputEventListener.dispose()}}),this.event=this.emitter.event}set input(i){this.inputEvent=i,this.listening&&(this.inputEventListener.dispose(),this.inputEventListener=i(this.emitter.fire,this.emitter))}dispose(){this.inputEventListener.dispose(),this.emitter.dispose()}}r.Relay=p}),Y(Q[27],Z([0,1,6]),function(U,r,E){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.CancellationTokenSource=r.CancellationToken=void 0;const e=Object.freeze(function(g,c){const m=setTimeout(g.bind(c),0);return{dispose(){clearTimeout(m)}}});var N;(function(g){function c(m){return m===g.None||m===g.Cancelled||m instanceof o?!0:!m||typeof m!="object"?!1:typeof m.isCancellationRequested=="boolean"&&typeof m.onCancellationRequested=="function"}g.isCancellationToken=c,g.None=Object.freeze({isCancellationRequested:!1,onCancellationRequested:E.Event.None}),g.Cancelled=Object.freeze({isCancellationRequested:!0,onCancellationRequested:e})})(N=r.CancellationToken||(r.CancellationToken={}));class o{constructor(){this._isCancelled=!1,this._emitter=null}cancel(){this._isCancelled||(this._isCancelled=!0,this._emitter&&(this._emitter.fire(void 0),this.dispose()))}get isCancellationRequested(){return this._isCancelled}get onCancellationRequested(){return this._isCancelled?e:(this._emitter||(this._emitter=new E.Emitter),this._emitter.event)}dispose(){this._emitter&&(this._emitter.dispose(),this._emitter=null)}}class w{constructor(c){this._token=void 0,this._parentListener=void 0,this._parentListener=c&&c.onCancellationRequested(this.cancel,this)}get token(){return this._token||(this._token=new o),this._token}cancel(){this._token?this._token instanceof o&&this._token.cancel():this._token=N.Cancelled}dispose(c=!1){c&&this.cancel(),this._parentListener&&this._parentListener.dispose(),this._token?this._token instanceof o&&this._token.dispose():this._token=N.None}}r.CancellationTokenSource=w}),Y(Q[2],Z([0,1,17,23]),function(U,r,E,e){"use strict";var N;Object.defineProperty(r,"__esModule",{value:!0}),r.InvisibleCharacters=r.AmbiguousCharacters=r.noBreakWhitespace=r.getLeftDeleteOffset=r.singleLetterHash=r.containsUppercaseCharacter=r.startsWithUTF8BOM=r.UTF8_BOM_CHARACTER=r.isEmojiImprecise=r.isFullWidthCharacter=r.containsUnusualLineTerminators=r.UNUSUAL_LINE_TERMINATORS=r.isBasicASCII=r.containsRTL=r.getCharContainingOffset=r.prevCharLength=r.nextCharLength=r.GraphemeIterator=r.CodePointIterator=r.getNextCodePoint=r.computeCodePoint=r.isLowSurrogate=r.isHighSurrogate=r.commonSuffixLength=r.commonPrefixLength=r.startsWithIgnoreCase=r.equalsIgnoreCase=r.isUpperAsciiLetter=r.isLowerAsciiLetter=r.compareSubstringIgnoreCase=r.compareIgnoreCase=r.compareSubstring=r.compare=r.lastNonWhitespaceIndex=r.getLeadingWhitespace=r.firstNonWhitespaceIndex=r.splitLines=r.regExpFlags=r.regExpLeadsToEndlessLoop=r.createRegExp=r.stripWildcards=r.convertSimple2RegExpPattern=r.rtrim=r.ltrim=r.trim=r.escapeRegExpCharacters=r.escape=r.format=r.isFalsyOrWhitespace=void 0;function o(M){return!M||typeof M!="string"?!0:M.trim().length===0}r.isFalsyOrWhitespace=o;const w=/{(\d+)}/g;function g(M,...T){return T.length===0?M:M.replace(w,function(B,W){const j=parseInt(W,10);return isNaN(j)||j<0||j>=T.length?B:T[j]})}r.format=g;function c(M){return M.replace(/[<>&]/g,function(T){switch(T){case"<":return"<";case">":return">";case"&":return"&";default:return T}})}r.escape=c;function m(M){return M.replace(/[\\\{\}\*\+\?\|\^\$\.\[\]\(\)]/g,"\\$&")}r.escapeRegExpCharacters=m;function S(M,T=" "){const B=t(M,T);return d(B,T)}r.trim=S;function t(M,T){if(!M||!T)return M;const B=T.length;if(B===0||M.length===0)return M;let W=0;for(;M.indexOf(T,W)===W;)W=W+B;return M.substring(W)}r.ltrim=t;function d(M,T){if(!M||!T)return M;const B=T.length,W=M.length;if(B===0||W===0)return M;let j=W,te=-1;for(;te=M.lastIndexOf(T,j-1),!(te===-1||te+B!==j);){if(te===0)return"";j=te}return M.substring(0,j)}r.rtrim=d;function h(M){return M.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g,"\\$&").replace(/[\*]/g,".*")}r.convertSimple2RegExpPattern=h;function v(M){return M.replace(/\*/g,"")}r.stripWildcards=v;function L(M,T,B={}){if(!M)throw new Error("Cannot create regex from empty string");T||(M=m(M)),B.wholeWord&&(/\B/.test(M.charAt(0))||(M="\\b"+M),/\B/.test(M.charAt(M.length-1))||(M=M+"\\b"));let W="";return B.global&&(W+="g"),B.matchCase||(W+="i"),B.multiline&&(W+="m"),B.unicode&&(W+="u"),new RegExp(M,W)}r.createRegExp=L;function C(M){return M.source==="^"||M.source==="^$"||M.source==="$"||M.source==="^\\s*$"?!1:!!(M.exec("")&&M.lastIndex===0)}r.regExpLeadsToEndlessLoop=C;function y(M){return(M.global?"g":"")+(M.ignoreCase?"i":"")+(M.multiline?"m":"")+(M.unicode?"u":"")}r.regExpFlags=y;function p(M){return M.split(/\r\n|\r|\n/)}r.splitLines=p;function s(M){for(let T=0,B=M.length;T=0;B--){const W=M.charCodeAt(B);if(W!==32&&W!==9)return B}return-1}r.lastNonWhitespaceIndex=a;function l(M,T){return MT?1:0}r.compare=l;function f(M,T,B=0,W=M.length,j=0,te=T.length){for(;Bne)return 1}const se=W-B,de=te-j;return sede?1:0}r.compareSubstring=f;function u(M,T){return _(M,T,0,M.length,0,T.length)}r.compareIgnoreCase=u;function _(M,T,B=0,W=M.length,j=0,te=T.length){for(;B=128||ne>=128)return f(M.toLowerCase(),T.toLowerCase(),B,W,j,te);b(ce)&&(ce-=32),b(ne)&&(ne-=32);const ae=ce-ne;if(ae!==0)return ae}const se=W-B,de=te-j;return sede?1:0}r.compareSubstringIgnoreCase=_;function b(M){return M>=97&&M<=122}r.isLowerAsciiLetter=b;function A(M){return M>=65&&M<=90}r.isUpperAsciiLetter=A;function P(M,T){return M.length===T.length&&_(M,T)===0}r.equalsIgnoreCase=P;function D(M,T){const B=T.length;return T.length>M.length?!1:_(M,T,0,B)===0}r.startsWithIgnoreCase=D;function k(M,T){let B,W=Math.min(M.length,T.length);for(B=0;B1){const W=M.charCodeAt(T-2);if(I(W))return O(W,B)}return B}class ${constructor(T,B=0){this._str=T,this._len=T.length,this._offset=B}get offset(){return this._offset}setOffset(T){this._offset=T}prevCodePoint(){const T=K(this._str,this._offset);return this._offset-=T>=65536?2:1,T}nextCodePoint(){const T=V(this._str,this._len,this._offset);return this._offset+=T>=65536?2:1,T}eol(){return this._offset>=this._len}}r.CodePointIterator=$;class z{constructor(T,B=0){this._iterator=new $(T,B)}get offset(){return this._iterator.offset}nextGraphemeLength(){const T=le.getInstance(),B=this._iterator,W=B.offset;let j=T.getGraphemeBreakType(B.nextCodePoint());for(;!B.eol();){const te=B.offset,se=T.getGraphemeBreakType(B.nextCodePoint());if(Ce(j,se)){B.setOffset(te);break}j=se}return B.offset-W}prevGraphemeLength(){const T=le.getInstance(),B=this._iterator,W=B.offset;let j=T.getGraphemeBreakType(B.prevCodePoint());for(;B.offset>0;){const te=B.offset,se=T.getGraphemeBreakType(B.prevCodePoint());if(Ce(se,j)){B.setOffset(te);break}j=se}return W-B.offset}eol(){return this._iterator.eol()}}r.GraphemeIterator=z;function n(M,T){return new z(M,T).nextGraphemeLength()}r.nextCharLength=n;function X(M,T){return new z(M,T).prevGraphemeLength()}r.prevCharLength=X;function q(M,T){T>0&&F(M.charCodeAt(T))&&T--;const B=T+n(M,T);return[B-X(M,B),B]}r.getCharContainingOffset=q;const G=/(?:[\u05BE\u05C0\u05C3\u05C6\u05D0-\u05F4\u0608\u060B\u060D\u061B-\u064A\u066D-\u066F\u0671-\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u0710\u0712-\u072F\u074D-\u07A5\u07B1-\u07EA\u07F4\u07F5\u07FA\u07FE-\u0815\u081A\u0824\u0828\u0830-\u0858\u085E-\u088E\u08A0-\u08C9\u200F\uFB1D\uFB1F-\uFB28\uFB2A-\uFD3D\uFD50-\uFDC7\uFDF0-\uFDFC\uFE70-\uFEFC]|\uD802[\uDC00-\uDD1B\uDD20-\uDE00\uDE10-\uDE35\uDE40-\uDEE4\uDEEB-\uDF35\uDF40-\uDFFF]|\uD803[\uDC00-\uDD23\uDE80-\uDEA9\uDEAD-\uDF45\uDF51-\uDF81\uDF86-\uDFF6]|\uD83A[\uDC00-\uDCCF\uDD00-\uDD43\uDD4B-\uDFFF]|\uD83B[\uDC00-\uDEBB])/;function H(M){return G.test(M)}r.containsRTL=H;const J=/^[\t\n\r\x20-\x7E]*$/;function x(M){return J.test(M)}r.isBasicASCII=x,r.UNUSUAL_LINE_TERMINATORS=/[\u2028\u2029]/;function ee(M){return r.UNUSUAL_LINE_TERMINATORS.test(M)}r.containsUnusualLineTerminators=ee;function ie(M){return M>=11904&&M<=55215||M>=63744&&M<=64255||M>=65281&&M<=65374}r.isFullWidthCharacter=ie;function he(M){return M>=127462&&M<=127487||M===8986||M===8987||M===9200||M===9203||M>=9728&&M<=10175||M===11088||M===11093||M>=127744&&M<=128591||M>=128640&&M<=128764||M>=128992&&M<=129008||M>=129280&&M<=129535||M>=129648&&M<=129782}r.isEmojiImprecise=he,r.UTF8_BOM_CHARACTER=String.fromCharCode(65279);function _e(M){return!!(M&&M.length>0&&M.charCodeAt(0)===65279)}r.startsWithUTF8BOM=_e;function be(M,T=!1){return M?(T&&(M=M.replace(/\\./g,"")),M.toLowerCase()!==M):!1}r.containsUppercaseCharacter=be;function me(M){const T=90-65+1;return M=M%(2*T),MB[3*j+1])j=2*j+1;else return B[3*j+2];return 0}}le._INSTANCE=null;function ye(){return JSON.parse("[0,0,0,51229,51255,12,44061,44087,12,127462,127487,6,7083,7085,5,47645,47671,12,54813,54839,12,128678,128678,14,3270,3270,5,9919,9923,14,45853,45879,12,49437,49463,12,53021,53047,12,71216,71218,7,128398,128399,14,129360,129374,14,2519,2519,5,4448,4519,9,9742,9742,14,12336,12336,14,44957,44983,12,46749,46775,12,48541,48567,12,50333,50359,12,52125,52151,12,53917,53943,12,69888,69890,5,73018,73018,5,127990,127990,14,128558,128559,14,128759,128760,14,129653,129655,14,2027,2035,5,2891,2892,7,3761,3761,5,6683,6683,5,8293,8293,4,9825,9826,14,9999,9999,14,43452,43453,5,44509,44535,12,45405,45431,12,46301,46327,12,47197,47223,12,48093,48119,12,48989,49015,12,49885,49911,12,50781,50807,12,51677,51703,12,52573,52599,12,53469,53495,12,54365,54391,12,65279,65279,4,70471,70472,7,72145,72147,7,119173,119179,5,127799,127818,14,128240,128244,14,128512,128512,14,128652,128652,14,128721,128722,14,129292,129292,14,129445,129450,14,129734,129743,14,1476,1477,5,2366,2368,7,2750,2752,7,3076,3076,5,3415,3415,5,4141,4144,5,6109,6109,5,6964,6964,5,7394,7400,5,9197,9198,14,9770,9770,14,9877,9877,14,9968,9969,14,10084,10084,14,43052,43052,5,43713,43713,5,44285,44311,12,44733,44759,12,45181,45207,12,45629,45655,12,46077,46103,12,46525,46551,12,46973,46999,12,47421,47447,12,47869,47895,12,48317,48343,12,48765,48791,12,49213,49239,12,49661,49687,12,50109,50135,12,50557,50583,12,51005,51031,12,51453,51479,12,51901,51927,12,52349,52375,12,52797,52823,12,53245,53271,12,53693,53719,12,54141,54167,12,54589,54615,12,55037,55063,12,69506,69509,5,70191,70193,5,70841,70841,7,71463,71467,5,72330,72342,5,94031,94031,5,123628,123631,5,127763,127765,14,127941,127941,14,128043,128062,14,128302,128317,14,128465,128467,14,128539,128539,14,128640,128640,14,128662,128662,14,128703,128703,14,128745,128745,14,129004,129007,14,129329,129330,14,129402,129402,14,129483,129483,14,129686,129704,14,130048,131069,14,173,173,4,1757,1757,1,2200,2207,5,2434,2435,7,2631,2632,5,2817,2817,5,3008,3008,5,3201,3201,5,3387,3388,5,3542,3542,5,3902,3903,7,4190,4192,5,6002,6003,5,6439,6440,5,6765,6770,7,7019,7027,5,7154,7155,7,8205,8205,13,8505,8505,14,9654,9654,14,9757,9757,14,9792,9792,14,9852,9853,14,9890,9894,14,9937,9937,14,9981,9981,14,10035,10036,14,11035,11036,14,42654,42655,5,43346,43347,7,43587,43587,5,44006,44007,7,44173,44199,12,44397,44423,12,44621,44647,12,44845,44871,12,45069,45095,12,45293,45319,12,45517,45543,12,45741,45767,12,45965,45991,12,46189,46215,12,46413,46439,12,46637,46663,12,46861,46887,12,47085,47111,12,47309,47335,12,47533,47559,12,47757,47783,12,47981,48007,12,48205,48231,12,48429,48455,12,48653,48679,12,48877,48903,12,49101,49127,12,49325,49351,12,49549,49575,12,49773,49799,12,49997,50023,12,50221,50247,12,50445,50471,12,50669,50695,12,50893,50919,12,51117,51143,12,51341,51367,12,51565,51591,12,51789,51815,12,52013,52039,12,52237,52263,12,52461,52487,12,52685,52711,12,52909,52935,12,53133,53159,12,53357,53383,12,53581,53607,12,53805,53831,12,54029,54055,12,54253,54279,12,54477,54503,12,54701,54727,12,54925,54951,12,55149,55175,12,68101,68102,5,69762,69762,7,70067,70069,7,70371,70378,5,70720,70721,7,71087,71087,5,71341,71341,5,71995,71996,5,72249,72249,7,72850,72871,5,73109,73109,5,118576,118598,5,121505,121519,5,127245,127247,14,127568,127569,14,127777,127777,14,127872,127891,14,127956,127967,14,128015,128016,14,128110,128172,14,128259,128259,14,128367,128368,14,128424,128424,14,128488,128488,14,128530,128532,14,128550,128551,14,128566,128566,14,128647,128647,14,128656,128656,14,128667,128673,14,128691,128693,14,128715,128715,14,128728,128732,14,128752,128752,14,128765,128767,14,129096,129103,14,129311,129311,14,129344,129349,14,129394,129394,14,129413,129425,14,129466,129471,14,129511,129535,14,129664,129666,14,129719,129722,14,129760,129767,14,917536,917631,5,13,13,2,1160,1161,5,1564,1564,4,1807,1807,1,2085,2087,5,2307,2307,7,2382,2383,7,2497,2500,5,2563,2563,7,2677,2677,5,2763,2764,7,2879,2879,5,2914,2915,5,3021,3021,5,3142,3144,5,3263,3263,5,3285,3286,5,3398,3400,7,3530,3530,5,3633,3633,5,3864,3865,5,3974,3975,5,4155,4156,7,4229,4230,5,5909,5909,7,6078,6085,7,6277,6278,5,6451,6456,7,6744,6750,5,6846,6846,5,6972,6972,5,7074,7077,5,7146,7148,7,7222,7223,5,7416,7417,5,8234,8238,4,8417,8417,5,9000,9000,14,9203,9203,14,9730,9731,14,9748,9749,14,9762,9763,14,9776,9783,14,9800,9811,14,9831,9831,14,9872,9873,14,9882,9882,14,9900,9903,14,9929,9933,14,9941,9960,14,9974,9974,14,9989,9989,14,10006,10006,14,10062,10062,14,10160,10160,14,11647,11647,5,12953,12953,14,43019,43019,5,43232,43249,5,43443,43443,5,43567,43568,7,43696,43696,5,43765,43765,7,44013,44013,5,44117,44143,12,44229,44255,12,44341,44367,12,44453,44479,12,44565,44591,12,44677,44703,12,44789,44815,12,44901,44927,12,45013,45039,12,45125,45151,12,45237,45263,12,45349,45375,12,45461,45487,12,45573,45599,12,45685,45711,12,45797,45823,12,45909,45935,12,46021,46047,12,46133,46159,12,46245,46271,12,46357,46383,12,46469,46495,12,46581,46607,12,46693,46719,12,46805,46831,12,46917,46943,12,47029,47055,12,47141,47167,12,47253,47279,12,47365,47391,12,47477,47503,12,47589,47615,12,47701,47727,12,47813,47839,12,47925,47951,12,48037,48063,12,48149,48175,12,48261,48287,12,48373,48399,12,48485,48511,12,48597,48623,12,48709,48735,12,48821,48847,12,48933,48959,12,49045,49071,12,49157,49183,12,49269,49295,12,49381,49407,12,49493,49519,12,49605,49631,12,49717,49743,12,49829,49855,12,49941,49967,12,50053,50079,12,50165,50191,12,50277,50303,12,50389,50415,12,50501,50527,12,50613,50639,12,50725,50751,12,50837,50863,12,50949,50975,12,51061,51087,12,51173,51199,12,51285,51311,12,51397,51423,12,51509,51535,12,51621,51647,12,51733,51759,12,51845,51871,12,51957,51983,12,52069,52095,12,52181,52207,12,52293,52319,12,52405,52431,12,52517,52543,12,52629,52655,12,52741,52767,12,52853,52879,12,52965,52991,12,53077,53103,12,53189,53215,12,53301,53327,12,53413,53439,12,53525,53551,12,53637,53663,12,53749,53775,12,53861,53887,12,53973,53999,12,54085,54111,12,54197,54223,12,54309,54335,12,54421,54447,12,54533,54559,12,54645,54671,12,54757,54783,12,54869,54895,12,54981,55007,12,55093,55119,12,55243,55291,10,66045,66045,5,68325,68326,5,69688,69702,5,69817,69818,5,69957,69958,7,70089,70092,5,70198,70199,5,70462,70462,5,70502,70508,5,70750,70750,5,70846,70846,7,71100,71101,5,71230,71230,7,71351,71351,5,71737,71738,5,72000,72000,7,72160,72160,5,72273,72278,5,72752,72758,5,72882,72883,5,73031,73031,5,73461,73462,7,94192,94193,7,119149,119149,7,121403,121452,5,122915,122916,5,126980,126980,14,127358,127359,14,127535,127535,14,127759,127759,14,127771,127771,14,127792,127793,14,127825,127867,14,127897,127899,14,127945,127945,14,127985,127986,14,128000,128007,14,128021,128021,14,128066,128100,14,128184,128235,14,128249,128252,14,128266,128276,14,128335,128335,14,128379,128390,14,128407,128419,14,128444,128444,14,128481,128481,14,128499,128499,14,128526,128526,14,128536,128536,14,128543,128543,14,128556,128556,14,128564,128564,14,128577,128580,14,128643,128645,14,128649,128649,14,128654,128654,14,128660,128660,14,128664,128664,14,128675,128675,14,128686,128689,14,128695,128696,14,128705,128709,14,128717,128719,14,128725,128725,14,128736,128741,14,128747,128748,14,128755,128755,14,128762,128762,14,128981,128991,14,129009,129023,14,129160,129167,14,129296,129304,14,129320,129327,14,129340,129342,14,129356,129356,14,129388,129392,14,129399,129400,14,129404,129407,14,129432,129442,14,129454,129455,14,129473,129474,14,129485,129487,14,129648,129651,14,129659,129660,14,129671,129679,14,129709,129711,14,129728,129730,14,129751,129753,14,129776,129782,14,917505,917505,4,917760,917999,5,10,10,3,127,159,4,768,879,5,1471,1471,5,1536,1541,1,1648,1648,5,1767,1768,5,1840,1866,5,2070,2073,5,2137,2139,5,2274,2274,1,2363,2363,7,2377,2380,7,2402,2403,5,2494,2494,5,2507,2508,7,2558,2558,5,2622,2624,7,2641,2641,5,2691,2691,7,2759,2760,5,2786,2787,5,2876,2876,5,2881,2884,5,2901,2902,5,3006,3006,5,3014,3016,7,3072,3072,5,3134,3136,5,3157,3158,5,3260,3260,5,3266,3266,5,3274,3275,7,3328,3329,5,3391,3392,7,3405,3405,5,3457,3457,5,3536,3537,7,3551,3551,5,3636,3642,5,3764,3772,5,3895,3895,5,3967,3967,7,3993,4028,5,4146,4151,5,4182,4183,7,4226,4226,5,4253,4253,5,4957,4959,5,5940,5940,7,6070,6070,7,6087,6088,7,6158,6158,4,6432,6434,5,6448,6449,7,6679,6680,5,6742,6742,5,6754,6754,5,6783,6783,5,6912,6915,5,6966,6970,5,6978,6978,5,7042,7042,7,7080,7081,5,7143,7143,7,7150,7150,7,7212,7219,5,7380,7392,5,7412,7412,5,8203,8203,4,8232,8232,4,8265,8265,14,8400,8412,5,8421,8432,5,8617,8618,14,9167,9167,14,9200,9200,14,9410,9410,14,9723,9726,14,9733,9733,14,9745,9745,14,9752,9752,14,9760,9760,14,9766,9766,14,9774,9774,14,9786,9786,14,9794,9794,14,9823,9823,14,9828,9828,14,9833,9850,14,9855,9855,14,9875,9875,14,9880,9880,14,9885,9887,14,9896,9897,14,9906,9916,14,9926,9927,14,9935,9935,14,9939,9939,14,9962,9962,14,9972,9972,14,9978,9978,14,9986,9986,14,9997,9997,14,10002,10002,14,10017,10017,14,10055,10055,14,10071,10071,14,10133,10135,14,10548,10549,14,11093,11093,14,12330,12333,5,12441,12442,5,42608,42610,5,43010,43010,5,43045,43046,5,43188,43203,7,43302,43309,5,43392,43394,5,43446,43449,5,43493,43493,5,43571,43572,7,43597,43597,7,43703,43704,5,43756,43757,5,44003,44004,7,44009,44010,7,44033,44059,12,44089,44115,12,44145,44171,12,44201,44227,12,44257,44283,12,44313,44339,12,44369,44395,12,44425,44451,12,44481,44507,12,44537,44563,12,44593,44619,12,44649,44675,12,44705,44731,12,44761,44787,12,44817,44843,12,44873,44899,12,44929,44955,12,44985,45011,12,45041,45067,12,45097,45123,12,45153,45179,12,45209,45235,12,45265,45291,12,45321,45347,12,45377,45403,12,45433,45459,12,45489,45515,12,45545,45571,12,45601,45627,12,45657,45683,12,45713,45739,12,45769,45795,12,45825,45851,12,45881,45907,12,45937,45963,12,45993,46019,12,46049,46075,12,46105,46131,12,46161,46187,12,46217,46243,12,46273,46299,12,46329,46355,12,46385,46411,12,46441,46467,12,46497,46523,12,46553,46579,12,46609,46635,12,46665,46691,12,46721,46747,12,46777,46803,12,46833,46859,12,46889,46915,12,46945,46971,12,47001,47027,12,47057,47083,12,47113,47139,12,47169,47195,12,47225,47251,12,47281,47307,12,47337,47363,12,47393,47419,12,47449,47475,12,47505,47531,12,47561,47587,12,47617,47643,12,47673,47699,12,47729,47755,12,47785,47811,12,47841,47867,12,47897,47923,12,47953,47979,12,48009,48035,12,48065,48091,12,48121,48147,12,48177,48203,12,48233,48259,12,48289,48315,12,48345,48371,12,48401,48427,12,48457,48483,12,48513,48539,12,48569,48595,12,48625,48651,12,48681,48707,12,48737,48763,12,48793,48819,12,48849,48875,12,48905,48931,12,48961,48987,12,49017,49043,12,49073,49099,12,49129,49155,12,49185,49211,12,49241,49267,12,49297,49323,12,49353,49379,12,49409,49435,12,49465,49491,12,49521,49547,12,49577,49603,12,49633,49659,12,49689,49715,12,49745,49771,12,49801,49827,12,49857,49883,12,49913,49939,12,49969,49995,12,50025,50051,12,50081,50107,12,50137,50163,12,50193,50219,12,50249,50275,12,50305,50331,12,50361,50387,12,50417,50443,12,50473,50499,12,50529,50555,12,50585,50611,12,50641,50667,12,50697,50723,12,50753,50779,12,50809,50835,12,50865,50891,12,50921,50947,12,50977,51003,12,51033,51059,12,51089,51115,12,51145,51171,12,51201,51227,12,51257,51283,12,51313,51339,12,51369,51395,12,51425,51451,12,51481,51507,12,51537,51563,12,51593,51619,12,51649,51675,12,51705,51731,12,51761,51787,12,51817,51843,12,51873,51899,12,51929,51955,12,51985,52011,12,52041,52067,12,52097,52123,12,52153,52179,12,52209,52235,12,52265,52291,12,52321,52347,12,52377,52403,12,52433,52459,12,52489,52515,12,52545,52571,12,52601,52627,12,52657,52683,12,52713,52739,12,52769,52795,12,52825,52851,12,52881,52907,12,52937,52963,12,52993,53019,12,53049,53075,12,53105,53131,12,53161,53187,12,53217,53243,12,53273,53299,12,53329,53355,12,53385,53411,12,53441,53467,12,53497,53523,12,53553,53579,12,53609,53635,12,53665,53691,12,53721,53747,12,53777,53803,12,53833,53859,12,53889,53915,12,53945,53971,12,54001,54027,12,54057,54083,12,54113,54139,12,54169,54195,12,54225,54251,12,54281,54307,12,54337,54363,12,54393,54419,12,54449,54475,12,54505,54531,12,54561,54587,12,54617,54643,12,54673,54699,12,54729,54755,12,54785,54811,12,54841,54867,12,54897,54923,12,54953,54979,12,55009,55035,12,55065,55091,12,55121,55147,12,55177,55203,12,65024,65039,5,65520,65528,4,66422,66426,5,68152,68154,5,69291,69292,5,69633,69633,5,69747,69748,5,69811,69814,5,69826,69826,5,69932,69932,7,70016,70017,5,70079,70080,7,70095,70095,5,70196,70196,5,70367,70367,5,70402,70403,7,70464,70464,5,70487,70487,5,70709,70711,7,70725,70725,7,70833,70834,7,70843,70844,7,70849,70849,7,71090,71093,5,71103,71104,5,71227,71228,7,71339,71339,5,71344,71349,5,71458,71461,5,71727,71735,5,71985,71989,7,71998,71998,5,72002,72002,7,72154,72155,5,72193,72202,5,72251,72254,5,72281,72283,5,72344,72345,5,72766,72766,7,72874,72880,5,72885,72886,5,73023,73029,5,73104,73105,5,73111,73111,5,92912,92916,5,94095,94098,5,113824,113827,4,119142,119142,7,119155,119162,4,119362,119364,5,121476,121476,5,122888,122904,5,123184,123190,5,125252,125258,5,127183,127183,14,127340,127343,14,127377,127386,14,127491,127503,14,127548,127551,14,127744,127756,14,127761,127761,14,127769,127769,14,127773,127774,14,127780,127788,14,127796,127797,14,127820,127823,14,127869,127869,14,127894,127895,14,127902,127903,14,127943,127943,14,127947,127950,14,127972,127972,14,127988,127988,14,127992,127994,14,128009,128011,14,128019,128019,14,128023,128041,14,128064,128064,14,128102,128107,14,128174,128181,14,128238,128238,14,128246,128247,14,128254,128254,14,128264,128264,14,128278,128299,14,128329,128330,14,128348,128359,14,128371,128377,14,128392,128393,14,128401,128404,14,128421,128421,14,128433,128434,14,128450,128452,14,128476,128478,14,128483,128483,14,128495,128495,14,128506,128506,14,128519,128520,14,128528,128528,14,128534,128534,14,128538,128538,14,128540,128542,14,128544,128549,14,128552,128555,14,128557,128557,14,128560,128563,14,128565,128565,14,128567,128576,14,128581,128591,14,128641,128642,14,128646,128646,14,128648,128648,14,128650,128651,14,128653,128653,14,128655,128655,14,128657,128659,14,128661,128661,14,128663,128663,14,128665,128666,14,128674,128674,14,128676,128677,14,128679,128685,14,128690,128690,14,128694,128694,14,128697,128702,14,128704,128704,14,128710,128714,14,128716,128716,14,128720,128720,14,128723,128724,14,128726,128727,14,128733,128735,14,128742,128744,14,128746,128746,14,128749,128751,14,128753,128754,14,128756,128758,14,128761,128761,14,128763,128764,14,128884,128895,14,128992,129003,14,129008,129008,14,129036,129039,14,129114,129119,14,129198,129279,14,129293,129295,14,129305,129310,14,129312,129319,14,129328,129328,14,129331,129338,14,129343,129343,14,129351,129355,14,129357,129359,14,129375,129387,14,129393,129393,14,129395,129398,14,129401,129401,14,129403,129403,14,129408,129412,14,129426,129431,14,129443,129444,14,129451,129453,14,129456,129465,14,129472,129472,14,129475,129482,14,129484,129484,14,129488,129510,14,129536,129647,14,129652,129652,14,129656,129658,14,129661,129663,14,129667,129670,14,129680,129685,14,129705,129708,14,129712,129718,14,129723,129727,14,129731,129733,14,129744,129750,14,129754,129759,14,129768,129775,14,129783,129791,14,917504,917504,4,917506,917535,4,917632,917759,4,918000,921599,4,0,9,4,11,12,4,14,31,4,169,169,14,174,174,14,1155,1159,5,1425,1469,5,1473,1474,5,1479,1479,5,1552,1562,5,1611,1631,5,1750,1756,5,1759,1764,5,1770,1773,5,1809,1809,5,1958,1968,5,2045,2045,5,2075,2083,5,2089,2093,5,2192,2193,1,2250,2273,5,2275,2306,5,2362,2362,5,2364,2364,5,2369,2376,5,2381,2381,5,2385,2391,5,2433,2433,5,2492,2492,5,2495,2496,7,2503,2504,7,2509,2509,5,2530,2531,5,2561,2562,5,2620,2620,5,2625,2626,5,2635,2637,5,2672,2673,5,2689,2690,5,2748,2748,5,2753,2757,5,2761,2761,7,2765,2765,5,2810,2815,5,2818,2819,7,2878,2878,5,2880,2880,7,2887,2888,7,2893,2893,5,2903,2903,5,2946,2946,5,3007,3007,7,3009,3010,7,3018,3020,7,3031,3031,5,3073,3075,7,3132,3132,5,3137,3140,7,3146,3149,5,3170,3171,5,3202,3203,7,3262,3262,7,3264,3265,7,3267,3268,7,3271,3272,7,3276,3277,5,3298,3299,5,3330,3331,7,3390,3390,5,3393,3396,5,3402,3404,7,3406,3406,1,3426,3427,5,3458,3459,7,3535,3535,5,3538,3540,5,3544,3550,7,3570,3571,7,3635,3635,7,3655,3662,5,3763,3763,7,3784,3789,5,3893,3893,5,3897,3897,5,3953,3966,5,3968,3972,5,3981,3991,5,4038,4038,5,4145,4145,7,4153,4154,5,4157,4158,5,4184,4185,5,4209,4212,5,4228,4228,7,4237,4237,5,4352,4447,8,4520,4607,10,5906,5908,5,5938,5939,5,5970,5971,5,6068,6069,5,6071,6077,5,6086,6086,5,6089,6099,5,6155,6157,5,6159,6159,5,6313,6313,5,6435,6438,7,6441,6443,7,6450,6450,5,6457,6459,5,6681,6682,7,6741,6741,7,6743,6743,7,6752,6752,5,6757,6764,5,6771,6780,5,6832,6845,5,6847,6862,5,6916,6916,7,6965,6965,5,6971,6971,7,6973,6977,7,6979,6980,7,7040,7041,5,7073,7073,7,7078,7079,7,7082,7082,7,7142,7142,5,7144,7145,5,7149,7149,5,7151,7153,5,7204,7211,7,7220,7221,7,7376,7378,5,7393,7393,7,7405,7405,5,7415,7415,7,7616,7679,5,8204,8204,5,8206,8207,4,8233,8233,4,8252,8252,14,8288,8292,4,8294,8303,4,8413,8416,5,8418,8420,5,8482,8482,14,8596,8601,14,8986,8987,14,9096,9096,14,9193,9196,14,9199,9199,14,9201,9202,14,9208,9210,14,9642,9643,14,9664,9664,14,9728,9729,14,9732,9732,14,9735,9741,14,9743,9744,14,9746,9746,14,9750,9751,14,9753,9756,14,9758,9759,14,9761,9761,14,9764,9765,14,9767,9769,14,9771,9773,14,9775,9775,14,9784,9785,14,9787,9791,14,9793,9793,14,9795,9799,14,9812,9822,14,9824,9824,14,9827,9827,14,9829,9830,14,9832,9832,14,9851,9851,14,9854,9854,14,9856,9861,14,9874,9874,14,9876,9876,14,9878,9879,14,9881,9881,14,9883,9884,14,9888,9889,14,9895,9895,14,9898,9899,14,9904,9905,14,9917,9918,14,9924,9925,14,9928,9928,14,9934,9934,14,9936,9936,14,9938,9938,14,9940,9940,14,9961,9961,14,9963,9967,14,9970,9971,14,9973,9973,14,9975,9977,14,9979,9980,14,9982,9985,14,9987,9988,14,9992,9996,14,9998,9998,14,10000,10001,14,10004,10004,14,10013,10013,14,10024,10024,14,10052,10052,14,10060,10060,14,10067,10069,14,10083,10083,14,10085,10087,14,10145,10145,14,10175,10175,14,11013,11015,14,11088,11088,14,11503,11505,5,11744,11775,5,12334,12335,5,12349,12349,14,12951,12951,14,42607,42607,5,42612,42621,5,42736,42737,5,43014,43014,5,43043,43044,7,43047,43047,7,43136,43137,7,43204,43205,5,43263,43263,5,43335,43345,5,43360,43388,8,43395,43395,7,43444,43445,7,43450,43451,7,43454,43456,7,43561,43566,5,43569,43570,5,43573,43574,5,43596,43596,5,43644,43644,5,43698,43700,5,43710,43711,5,43755,43755,7,43758,43759,7,43766,43766,5,44005,44005,5,44008,44008,5,44012,44012,7,44032,44032,11,44060,44060,11,44088,44088,11,44116,44116,11,44144,44144,11,44172,44172,11,44200,44200,11,44228,44228,11,44256,44256,11,44284,44284,11,44312,44312,11,44340,44340,11,44368,44368,11,44396,44396,11,44424,44424,11,44452,44452,11,44480,44480,11,44508,44508,11,44536,44536,11,44564,44564,11,44592,44592,11,44620,44620,11,44648,44648,11,44676,44676,11,44704,44704,11,44732,44732,11,44760,44760,11,44788,44788,11,44816,44816,11,44844,44844,11,44872,44872,11,44900,44900,11,44928,44928,11,44956,44956,11,44984,44984,11,45012,45012,11,45040,45040,11,45068,45068,11,45096,45096,11,45124,45124,11,45152,45152,11,45180,45180,11,45208,45208,11,45236,45236,11,45264,45264,11,45292,45292,11,45320,45320,11,45348,45348,11,45376,45376,11,45404,45404,11,45432,45432,11,45460,45460,11,45488,45488,11,45516,45516,11,45544,45544,11,45572,45572,11,45600,45600,11,45628,45628,11,45656,45656,11,45684,45684,11,45712,45712,11,45740,45740,11,45768,45768,11,45796,45796,11,45824,45824,11,45852,45852,11,45880,45880,11,45908,45908,11,45936,45936,11,45964,45964,11,45992,45992,11,46020,46020,11,46048,46048,11,46076,46076,11,46104,46104,11,46132,46132,11,46160,46160,11,46188,46188,11,46216,46216,11,46244,46244,11,46272,46272,11,46300,46300,11,46328,46328,11,46356,46356,11,46384,46384,11,46412,46412,11,46440,46440,11,46468,46468,11,46496,46496,11,46524,46524,11,46552,46552,11,46580,46580,11,46608,46608,11,46636,46636,11,46664,46664,11,46692,46692,11,46720,46720,11,46748,46748,11,46776,46776,11,46804,46804,11,46832,46832,11,46860,46860,11,46888,46888,11,46916,46916,11,46944,46944,11,46972,46972,11,47000,47000,11,47028,47028,11,47056,47056,11,47084,47084,11,47112,47112,11,47140,47140,11,47168,47168,11,47196,47196,11,47224,47224,11,47252,47252,11,47280,47280,11,47308,47308,11,47336,47336,11,47364,47364,11,47392,47392,11,47420,47420,11,47448,47448,11,47476,47476,11,47504,47504,11,47532,47532,11,47560,47560,11,47588,47588,11,47616,47616,11,47644,47644,11,47672,47672,11,47700,47700,11,47728,47728,11,47756,47756,11,47784,47784,11,47812,47812,11,47840,47840,11,47868,47868,11,47896,47896,11,47924,47924,11,47952,47952,11,47980,47980,11,48008,48008,11,48036,48036,11,48064,48064,11,48092,48092,11,48120,48120,11,48148,48148,11,48176,48176,11,48204,48204,11,48232,48232,11,48260,48260,11,48288,48288,11,48316,48316,11,48344,48344,11,48372,48372,11,48400,48400,11,48428,48428,11,48456,48456,11,48484,48484,11,48512,48512,11,48540,48540,11,48568,48568,11,48596,48596,11,48624,48624,11,48652,48652,11,48680,48680,11,48708,48708,11,48736,48736,11,48764,48764,11,48792,48792,11,48820,48820,11,48848,48848,11,48876,48876,11,48904,48904,11,48932,48932,11,48960,48960,11,48988,48988,11,49016,49016,11,49044,49044,11,49072,49072,11,49100,49100,11,49128,49128,11,49156,49156,11,49184,49184,11,49212,49212,11,49240,49240,11,49268,49268,11,49296,49296,11,49324,49324,11,49352,49352,11,49380,49380,11,49408,49408,11,49436,49436,11,49464,49464,11,49492,49492,11,49520,49520,11,49548,49548,11,49576,49576,11,49604,49604,11,49632,49632,11,49660,49660,11,49688,49688,11,49716,49716,11,49744,49744,11,49772,49772,11,49800,49800,11,49828,49828,11,49856,49856,11,49884,49884,11,49912,49912,11,49940,49940,11,49968,49968,11,49996,49996,11,50024,50024,11,50052,50052,11,50080,50080,11,50108,50108,11,50136,50136,11,50164,50164,11,50192,50192,11,50220,50220,11,50248,50248,11,50276,50276,11,50304,50304,11,50332,50332,11,50360,50360,11,50388,50388,11,50416,50416,11,50444,50444,11,50472,50472,11,50500,50500,11,50528,50528,11,50556,50556,11,50584,50584,11,50612,50612,11,50640,50640,11,50668,50668,11,50696,50696,11,50724,50724,11,50752,50752,11,50780,50780,11,50808,50808,11,50836,50836,11,50864,50864,11,50892,50892,11,50920,50920,11,50948,50948,11,50976,50976,11,51004,51004,11,51032,51032,11,51060,51060,11,51088,51088,11,51116,51116,11,51144,51144,11,51172,51172,11,51200,51200,11,51228,51228,11,51256,51256,11,51284,51284,11,51312,51312,11,51340,51340,11,51368,51368,11,51396,51396,11,51424,51424,11,51452,51452,11,51480,51480,11,51508,51508,11,51536,51536,11,51564,51564,11,51592,51592,11,51620,51620,11,51648,51648,11,51676,51676,11,51704,51704,11,51732,51732,11,51760,51760,11,51788,51788,11,51816,51816,11,51844,51844,11,51872,51872,11,51900,51900,11,51928,51928,11,51956,51956,11,51984,51984,11,52012,52012,11,52040,52040,11,52068,52068,11,52096,52096,11,52124,52124,11,52152,52152,11,52180,52180,11,52208,52208,11,52236,52236,11,52264,52264,11,52292,52292,11,52320,52320,11,52348,52348,11,52376,52376,11,52404,52404,11,52432,52432,11,52460,52460,11,52488,52488,11,52516,52516,11,52544,52544,11,52572,52572,11,52600,52600,11,52628,52628,11,52656,52656,11,52684,52684,11,52712,52712,11,52740,52740,11,52768,52768,11,52796,52796,11,52824,52824,11,52852,52852,11,52880,52880,11,52908,52908,11,52936,52936,11,52964,52964,11,52992,52992,11,53020,53020,11,53048,53048,11,53076,53076,11,53104,53104,11,53132,53132,11,53160,53160,11,53188,53188,11,53216,53216,11,53244,53244,11,53272,53272,11,53300,53300,11,53328,53328,11,53356,53356,11,53384,53384,11,53412,53412,11,53440,53440,11,53468,53468,11,53496,53496,11,53524,53524,11,53552,53552,11,53580,53580,11,53608,53608,11,53636,53636,11,53664,53664,11,53692,53692,11,53720,53720,11,53748,53748,11,53776,53776,11,53804,53804,11,53832,53832,11,53860,53860,11,53888,53888,11,53916,53916,11,53944,53944,11,53972,53972,11,54000,54000,11,54028,54028,11,54056,54056,11,54084,54084,11,54112,54112,11,54140,54140,11,54168,54168,11,54196,54196,11,54224,54224,11,54252,54252,11,54280,54280,11,54308,54308,11,54336,54336,11,54364,54364,11,54392,54392,11,54420,54420,11,54448,54448,11,54476,54476,11,54504,54504,11,54532,54532,11,54560,54560,11,54588,54588,11,54616,54616,11,54644,54644,11,54672,54672,11,54700,54700,11,54728,54728,11,54756,54756,11,54784,54784,11,54812,54812,11,54840,54840,11,54868,54868,11,54896,54896,11,54924,54924,11,54952,54952,11,54980,54980,11,55008,55008,11,55036,55036,11,55064,55064,11,55092,55092,11,55120,55120,11,55148,55148,11,55176,55176,11,55216,55238,9,64286,64286,5,65056,65071,5,65438,65439,5,65529,65531,4,66272,66272,5,68097,68099,5,68108,68111,5,68159,68159,5,68900,68903,5,69446,69456,5,69632,69632,7,69634,69634,7,69744,69744,5,69759,69761,5,69808,69810,7,69815,69816,7,69821,69821,1,69837,69837,1,69927,69931,5,69933,69940,5,70003,70003,5,70018,70018,7,70070,70078,5,70082,70083,1,70094,70094,7,70188,70190,7,70194,70195,7,70197,70197,7,70206,70206,5,70368,70370,7,70400,70401,5,70459,70460,5,70463,70463,7,70465,70468,7,70475,70477,7,70498,70499,7,70512,70516,5,70712,70719,5,70722,70724,5,70726,70726,5,70832,70832,5,70835,70840,5,70842,70842,5,70845,70845,5,70847,70848,5,70850,70851,5,71088,71089,7,71096,71099,7,71102,71102,7,71132,71133,5,71219,71226,5,71229,71229,5,71231,71232,5,71340,71340,7,71342,71343,7,71350,71350,7,71453,71455,5,71462,71462,7,71724,71726,7,71736,71736,7,71984,71984,5,71991,71992,7,71997,71997,7,71999,71999,1,72001,72001,1,72003,72003,5,72148,72151,5,72156,72159,7,72164,72164,7,72243,72248,5,72250,72250,1,72263,72263,5,72279,72280,7,72324,72329,1,72343,72343,7,72751,72751,7,72760,72765,5,72767,72767,5,72873,72873,7,72881,72881,7,72884,72884,7,73009,73014,5,73020,73021,5,73030,73030,1,73098,73102,7,73107,73108,7,73110,73110,7,73459,73460,5,78896,78904,4,92976,92982,5,94033,94087,7,94180,94180,5,113821,113822,5,118528,118573,5,119141,119141,5,119143,119145,5,119150,119154,5,119163,119170,5,119210,119213,5,121344,121398,5,121461,121461,5,121499,121503,5,122880,122886,5,122907,122913,5,122918,122922,5,123566,123566,5,125136,125142,5,126976,126979,14,126981,127182,14,127184,127231,14,127279,127279,14,127344,127345,14,127374,127374,14,127405,127461,14,127489,127490,14,127514,127514,14,127538,127546,14,127561,127567,14,127570,127743,14,127757,127758,14,127760,127760,14,127762,127762,14,127766,127768,14,127770,127770,14,127772,127772,14,127775,127776,14,127778,127779,14,127789,127791,14,127794,127795,14,127798,127798,14,127819,127819,14,127824,127824,14,127868,127868,14,127870,127871,14,127892,127893,14,127896,127896,14,127900,127901,14,127904,127940,14,127942,127942,14,127944,127944,14,127946,127946,14,127951,127955,14,127968,127971,14,127973,127984,14,127987,127987,14,127989,127989,14,127991,127991,14,127995,127999,5,128008,128008,14,128012,128014,14,128017,128018,14,128020,128020,14,128022,128022,14,128042,128042,14,128063,128063,14,128065,128065,14,128101,128101,14,128108,128109,14,128173,128173,14,128182,128183,14,128236,128237,14,128239,128239,14,128245,128245,14,128248,128248,14,128253,128253,14,128255,128258,14,128260,128263,14,128265,128265,14,128277,128277,14,128300,128301,14,128326,128328,14,128331,128334,14,128336,128347,14,128360,128366,14,128369,128370,14,128378,128378,14,128391,128391,14,128394,128397,14,128400,128400,14,128405,128406,14,128420,128420,14,128422,128423,14,128425,128432,14,128435,128443,14,128445,128449,14,128453,128464,14,128468,128475,14,128479,128480,14,128482,128482,14,128484,128487,14,128489,128494,14,128496,128498,14,128500,128505,14,128507,128511,14,128513,128518,14,128521,128525,14,128527,128527,14,128529,128529,14,128533,128533,14,128535,128535,14,128537,128537,14]")}function Le(M,T){if(M===0)return 0;const B=Ne(M,T);if(B!==void 0)return B;const W=new $(T,M);return W.prevCodePoint(),W.offset}r.getLeftDeleteOffset=Le;function Ne(M,T){const B=new $(T,M);let W=B.prevCodePoint();for(;Ae(W)||W===65039||W===8419;){if(B.offset===0)return;W=B.prevCodePoint()}if(!he(W))return;let j=B.offset;return j>0&&B.prevCodePoint()===8205&&(j=B.offset),j}function Ae(M){return 127995<=M&&M<=127999}r.noBreakWhitespace="\xA0";class ue{constructor(T){this.confusableDictionary=T}static getInstance(T){return ue.cache.get(Array.from(T))}static getLocales(){return ue._locales.getValue()}isAmbiguous(T){return this.confusableDictionary.has(T)}getPrimaryConfusable(T){return this.confusableDictionary.get(T)}getConfusableCodePoints(){return new Set(this.confusableDictionary.keys())}}r.AmbiguousCharacters=ue,N=ue,ue.ambiguousCharacterData=new e.Lazy(()=>JSON.parse('{"_common":[8232,32,8233,32,5760,32,8192,32,8193,32,8194,32,8195,32,8196,32,8197,32,8198,32,8200,32,8201,32,8202,32,8287,32,8199,32,8239,32,2042,95,65101,95,65102,95,65103,95,8208,45,8209,45,8210,45,65112,45,1748,45,8259,45,727,45,8722,45,10134,45,11450,45,1549,44,1643,44,8218,44,184,44,42233,44,894,59,2307,58,2691,58,1417,58,1795,58,1796,58,5868,58,65072,58,6147,58,6153,58,8282,58,1475,58,760,58,42889,58,8758,58,720,58,42237,58,451,33,11601,33,660,63,577,63,2429,63,5038,63,42731,63,119149,46,8228,46,1793,46,1794,46,42510,46,68176,46,1632,46,1776,46,42232,46,1373,96,65287,96,8219,96,8242,96,1370,96,1523,96,8175,96,65344,96,900,96,8189,96,8125,96,8127,96,8190,96,697,96,884,96,712,96,714,96,715,96,756,96,699,96,701,96,700,96,702,96,42892,96,1497,96,2036,96,2037,96,5194,96,5836,96,94033,96,94034,96,65339,91,10088,40,10098,40,12308,40,64830,40,65341,93,10089,41,10099,41,12309,41,64831,41,10100,123,119060,123,10101,125,65342,94,8270,42,1645,42,8727,42,66335,42,5941,47,8257,47,8725,47,8260,47,9585,47,10187,47,10744,47,119354,47,12755,47,12339,47,11462,47,20031,47,12035,47,65340,92,65128,92,8726,92,10189,92,10741,92,10745,92,119311,92,119355,92,12756,92,20022,92,12034,92,42872,38,708,94,710,94,5869,43,10133,43,66203,43,8249,60,10094,60,706,60,119350,60,5176,60,5810,60,5120,61,11840,61,12448,61,42239,61,8250,62,10095,62,707,62,119351,62,5171,62,94015,62,8275,126,732,126,8128,126,8764,126,65372,124,65293,45,120784,50,120794,50,120804,50,120814,50,120824,50,130034,50,42842,50,423,50,1000,50,42564,50,5311,50,42735,50,119302,51,120785,51,120795,51,120805,51,120815,51,120825,51,130035,51,42923,51,540,51,439,51,42858,51,11468,51,1248,51,94011,51,71882,51,120786,52,120796,52,120806,52,120816,52,120826,52,130036,52,5070,52,71855,52,120787,53,120797,53,120807,53,120817,53,120827,53,130037,53,444,53,71867,53,120788,54,120798,54,120808,54,120818,54,120828,54,130038,54,11474,54,5102,54,71893,54,119314,55,120789,55,120799,55,120809,55,120819,55,120829,55,130039,55,66770,55,71878,55,2819,56,2538,56,2666,56,125131,56,120790,56,120800,56,120810,56,120820,56,120830,56,130040,56,547,56,546,56,66330,56,2663,57,2920,57,2541,57,3437,57,120791,57,120801,57,120811,57,120821,57,120831,57,130041,57,42862,57,11466,57,71884,57,71852,57,71894,57,9082,97,65345,97,119834,97,119886,97,119938,97,119990,97,120042,97,120094,97,120146,97,120198,97,120250,97,120302,97,120354,97,120406,97,120458,97,593,97,945,97,120514,97,120572,97,120630,97,120688,97,120746,97,65313,65,119808,65,119860,65,119912,65,119964,65,120016,65,120068,65,120120,65,120172,65,120224,65,120276,65,120328,65,120380,65,120432,65,913,65,120488,65,120546,65,120604,65,120662,65,120720,65,5034,65,5573,65,42222,65,94016,65,66208,65,119835,98,119887,98,119939,98,119991,98,120043,98,120095,98,120147,98,120199,98,120251,98,120303,98,120355,98,120407,98,120459,98,388,98,5071,98,5234,98,5551,98,65314,66,8492,66,119809,66,119861,66,119913,66,120017,66,120069,66,120121,66,120173,66,120225,66,120277,66,120329,66,120381,66,120433,66,42932,66,914,66,120489,66,120547,66,120605,66,120663,66,120721,66,5108,66,5623,66,42192,66,66178,66,66209,66,66305,66,65347,99,8573,99,119836,99,119888,99,119940,99,119992,99,120044,99,120096,99,120148,99,120200,99,120252,99,120304,99,120356,99,120408,99,120460,99,7428,99,1010,99,11429,99,43951,99,66621,99,128844,67,71922,67,71913,67,65315,67,8557,67,8450,67,8493,67,119810,67,119862,67,119914,67,119966,67,120018,67,120174,67,120226,67,120278,67,120330,67,120382,67,120434,67,1017,67,11428,67,5087,67,42202,67,66210,67,66306,67,66581,67,66844,67,8574,100,8518,100,119837,100,119889,100,119941,100,119993,100,120045,100,120097,100,120149,100,120201,100,120253,100,120305,100,120357,100,120409,100,120461,100,1281,100,5095,100,5231,100,42194,100,8558,68,8517,68,119811,68,119863,68,119915,68,119967,68,120019,68,120071,68,120123,68,120175,68,120227,68,120279,68,120331,68,120383,68,120435,68,5024,68,5598,68,5610,68,42195,68,8494,101,65349,101,8495,101,8519,101,119838,101,119890,101,119942,101,120046,101,120098,101,120150,101,120202,101,120254,101,120306,101,120358,101,120410,101,120462,101,43826,101,1213,101,8959,69,65317,69,8496,69,119812,69,119864,69,119916,69,120020,69,120072,69,120124,69,120176,69,120228,69,120280,69,120332,69,120384,69,120436,69,917,69,120492,69,120550,69,120608,69,120666,69,120724,69,11577,69,5036,69,42224,69,71846,69,71854,69,66182,69,119839,102,119891,102,119943,102,119995,102,120047,102,120099,102,120151,102,120203,102,120255,102,120307,102,120359,102,120411,102,120463,102,43829,102,42905,102,383,102,7837,102,1412,102,119315,70,8497,70,119813,70,119865,70,119917,70,120021,70,120073,70,120125,70,120177,70,120229,70,120281,70,120333,70,120385,70,120437,70,42904,70,988,70,120778,70,5556,70,42205,70,71874,70,71842,70,66183,70,66213,70,66853,70,65351,103,8458,103,119840,103,119892,103,119944,103,120048,103,120100,103,120152,103,120204,103,120256,103,120308,103,120360,103,120412,103,120464,103,609,103,7555,103,397,103,1409,103,119814,71,119866,71,119918,71,119970,71,120022,71,120074,71,120126,71,120178,71,120230,71,120282,71,120334,71,120386,71,120438,71,1292,71,5056,71,5107,71,42198,71,65352,104,8462,104,119841,104,119945,104,119997,104,120049,104,120101,104,120153,104,120205,104,120257,104,120309,104,120361,104,120413,104,120465,104,1211,104,1392,104,5058,104,65320,72,8459,72,8460,72,8461,72,119815,72,119867,72,119919,72,120023,72,120179,72,120231,72,120283,72,120335,72,120387,72,120439,72,919,72,120494,72,120552,72,120610,72,120668,72,120726,72,11406,72,5051,72,5500,72,42215,72,66255,72,731,105,9075,105,65353,105,8560,105,8505,105,8520,105,119842,105,119894,105,119946,105,119998,105,120050,105,120102,105,120154,105,120206,105,120258,105,120310,105,120362,105,120414,105,120466,105,120484,105,618,105,617,105,953,105,8126,105,890,105,120522,105,120580,105,120638,105,120696,105,120754,105,1110,105,42567,105,1231,105,43893,105,5029,105,71875,105,65354,106,8521,106,119843,106,119895,106,119947,106,119999,106,120051,106,120103,106,120155,106,120207,106,120259,106,120311,106,120363,106,120415,106,120467,106,1011,106,1112,106,65322,74,119817,74,119869,74,119921,74,119973,74,120025,74,120077,74,120129,74,120181,74,120233,74,120285,74,120337,74,120389,74,120441,74,42930,74,895,74,1032,74,5035,74,5261,74,42201,74,119844,107,119896,107,119948,107,120000,107,120052,107,120104,107,120156,107,120208,107,120260,107,120312,107,120364,107,120416,107,120468,107,8490,75,65323,75,119818,75,119870,75,119922,75,119974,75,120026,75,120078,75,120130,75,120182,75,120234,75,120286,75,120338,75,120390,75,120442,75,922,75,120497,75,120555,75,120613,75,120671,75,120729,75,11412,75,5094,75,5845,75,42199,75,66840,75,1472,108,8739,73,9213,73,65512,73,1633,108,1777,73,66336,108,125127,108,120783,73,120793,73,120803,73,120813,73,120823,73,130033,73,65321,73,8544,73,8464,73,8465,73,119816,73,119868,73,119920,73,120024,73,120128,73,120180,73,120232,73,120284,73,120336,73,120388,73,120440,73,65356,108,8572,73,8467,108,119845,108,119897,108,119949,108,120001,108,120053,108,120105,73,120157,73,120209,73,120261,73,120313,73,120365,73,120417,73,120469,73,448,73,120496,73,120554,73,120612,73,120670,73,120728,73,11410,73,1030,73,1216,73,1493,108,1503,108,1575,108,126464,108,126592,108,65166,108,65165,108,1994,108,11599,73,5825,73,42226,73,93992,73,66186,124,66313,124,119338,76,8556,76,8466,76,119819,76,119871,76,119923,76,120027,76,120079,76,120131,76,120183,76,120235,76,120287,76,120339,76,120391,76,120443,76,11472,76,5086,76,5290,76,42209,76,93974,76,71843,76,71858,76,66587,76,66854,76,65325,77,8559,77,8499,77,119820,77,119872,77,119924,77,120028,77,120080,77,120132,77,120184,77,120236,77,120288,77,120340,77,120392,77,120444,77,924,77,120499,77,120557,77,120615,77,120673,77,120731,77,1018,77,11416,77,5047,77,5616,77,5846,77,42207,77,66224,77,66321,77,119847,110,119899,110,119951,110,120003,110,120055,110,120107,110,120159,110,120211,110,120263,110,120315,110,120367,110,120419,110,120471,110,1400,110,1404,110,65326,78,8469,78,119821,78,119873,78,119925,78,119977,78,120029,78,120081,78,120185,78,120237,78,120289,78,120341,78,120393,78,120445,78,925,78,120500,78,120558,78,120616,78,120674,78,120732,78,11418,78,42208,78,66835,78,3074,111,3202,111,3330,111,3458,111,2406,111,2662,111,2790,111,3046,111,3174,111,3302,111,3430,111,3664,111,3792,111,4160,111,1637,111,1781,111,65359,111,8500,111,119848,111,119900,111,119952,111,120056,111,120108,111,120160,111,120212,111,120264,111,120316,111,120368,111,120420,111,120472,111,7439,111,7441,111,43837,111,959,111,120528,111,120586,111,120644,111,120702,111,120760,111,963,111,120532,111,120590,111,120648,111,120706,111,120764,111,11423,111,4351,111,1413,111,1505,111,1607,111,126500,111,126564,111,126596,111,65259,111,65260,111,65258,111,65257,111,1726,111,64428,111,64429,111,64427,111,64426,111,1729,111,64424,111,64425,111,64423,111,64422,111,1749,111,3360,111,4125,111,66794,111,71880,111,71895,111,66604,111,1984,79,2534,79,2918,79,12295,79,70864,79,71904,79,120782,79,120792,79,120802,79,120812,79,120822,79,130032,79,65327,79,119822,79,119874,79,119926,79,119978,79,120030,79,120082,79,120134,79,120186,79,120238,79,120290,79,120342,79,120394,79,120446,79,927,79,120502,79,120560,79,120618,79,120676,79,120734,79,11422,79,1365,79,11604,79,4816,79,2848,79,66754,79,42227,79,71861,79,66194,79,66219,79,66564,79,66838,79,9076,112,65360,112,119849,112,119901,112,119953,112,120005,112,120057,112,120109,112,120161,112,120213,112,120265,112,120317,112,120369,112,120421,112,120473,112,961,112,120530,112,120544,112,120588,112,120602,112,120646,112,120660,112,120704,112,120718,112,120762,112,120776,112,11427,112,65328,80,8473,80,119823,80,119875,80,119927,80,119979,80,120031,80,120083,80,120187,80,120239,80,120291,80,120343,80,120395,80,120447,80,929,80,120504,80,120562,80,120620,80,120678,80,120736,80,11426,80,5090,80,5229,80,42193,80,66197,80,119850,113,119902,113,119954,113,120006,113,120058,113,120110,113,120162,113,120214,113,120266,113,120318,113,120370,113,120422,113,120474,113,1307,113,1379,113,1382,113,8474,81,119824,81,119876,81,119928,81,119980,81,120032,81,120084,81,120188,81,120240,81,120292,81,120344,81,120396,81,120448,81,11605,81,119851,114,119903,114,119955,114,120007,114,120059,114,120111,114,120163,114,120215,114,120267,114,120319,114,120371,114,120423,114,120475,114,43847,114,43848,114,7462,114,11397,114,43905,114,119318,82,8475,82,8476,82,8477,82,119825,82,119877,82,119929,82,120033,82,120189,82,120241,82,120293,82,120345,82,120397,82,120449,82,422,82,5025,82,5074,82,66740,82,5511,82,42211,82,94005,82,65363,115,119852,115,119904,115,119956,115,120008,115,120060,115,120112,115,120164,115,120216,115,120268,115,120320,115,120372,115,120424,115,120476,115,42801,115,445,115,1109,115,43946,115,71873,115,66632,115,65331,83,119826,83,119878,83,119930,83,119982,83,120034,83,120086,83,120138,83,120190,83,120242,83,120294,83,120346,83,120398,83,120450,83,1029,83,1359,83,5077,83,5082,83,42210,83,94010,83,66198,83,66592,83,119853,116,119905,116,119957,116,120009,116,120061,116,120113,116,120165,116,120217,116,120269,116,120321,116,120373,116,120425,116,120477,116,8868,84,10201,84,128872,84,65332,84,119827,84,119879,84,119931,84,119983,84,120035,84,120087,84,120139,84,120191,84,120243,84,120295,84,120347,84,120399,84,120451,84,932,84,120507,84,120565,84,120623,84,120681,84,120739,84,11430,84,5026,84,42196,84,93962,84,71868,84,66199,84,66225,84,66325,84,119854,117,119906,117,119958,117,120010,117,120062,117,120114,117,120166,117,120218,117,120270,117,120322,117,120374,117,120426,117,120478,117,42911,117,7452,117,43854,117,43858,117,651,117,965,117,120534,117,120592,117,120650,117,120708,117,120766,117,1405,117,66806,117,71896,117,8746,85,8899,85,119828,85,119880,85,119932,85,119984,85,120036,85,120088,85,120140,85,120192,85,120244,85,120296,85,120348,85,120400,85,120452,85,1357,85,4608,85,66766,85,5196,85,42228,85,94018,85,71864,85,8744,118,8897,118,65366,118,8564,118,119855,118,119907,118,119959,118,120011,118,120063,118,120115,118,120167,118,120219,118,120271,118,120323,118,120375,118,120427,118,120479,118,7456,118,957,118,120526,118,120584,118,120642,118,120700,118,120758,118,1141,118,1496,118,71430,118,43945,118,71872,118,119309,86,1639,86,1783,86,8548,86,119829,86,119881,86,119933,86,119985,86,120037,86,120089,86,120141,86,120193,86,120245,86,120297,86,120349,86,120401,86,120453,86,1140,86,11576,86,5081,86,5167,86,42719,86,42214,86,93960,86,71840,86,66845,86,623,119,119856,119,119908,119,119960,119,120012,119,120064,119,120116,119,120168,119,120220,119,120272,119,120324,119,120376,119,120428,119,120480,119,7457,119,1121,119,1309,119,1377,119,71434,119,71438,119,71439,119,43907,119,71919,87,71910,87,119830,87,119882,87,119934,87,119986,87,120038,87,120090,87,120142,87,120194,87,120246,87,120298,87,120350,87,120402,87,120454,87,1308,87,5043,87,5076,87,42218,87,5742,120,10539,120,10540,120,10799,120,65368,120,8569,120,119857,120,119909,120,119961,120,120013,120,120065,120,120117,120,120169,120,120221,120,120273,120,120325,120,120377,120,120429,120,120481,120,5441,120,5501,120,5741,88,9587,88,66338,88,71916,88,65336,88,8553,88,119831,88,119883,88,119935,88,119987,88,120039,88,120091,88,120143,88,120195,88,120247,88,120299,88,120351,88,120403,88,120455,88,42931,88,935,88,120510,88,120568,88,120626,88,120684,88,120742,88,11436,88,11613,88,5815,88,42219,88,66192,88,66228,88,66327,88,66855,88,611,121,7564,121,65369,121,119858,121,119910,121,119962,121,120014,121,120066,121,120118,121,120170,121,120222,121,120274,121,120326,121,120378,121,120430,121,120482,121,655,121,7935,121,43866,121,947,121,8509,121,120516,121,120574,121,120632,121,120690,121,120748,121,1199,121,4327,121,71900,121,65337,89,119832,89,119884,89,119936,89,119988,89,120040,89,120092,89,120144,89,120196,89,120248,89,120300,89,120352,89,120404,89,120456,89,933,89,978,89,120508,89,120566,89,120624,89,120682,89,120740,89,11432,89,1198,89,5033,89,5053,89,42220,89,94019,89,71844,89,66226,89,119859,122,119911,122,119963,122,120015,122,120067,122,120119,122,120171,122,120223,122,120275,122,120327,122,120379,122,120431,122,120483,122,7458,122,43923,122,71876,122,66293,90,71909,90,65338,90,8484,90,8488,90,119833,90,119885,90,119937,90,119989,90,120041,90,120197,90,120249,90,120301,90,120353,90,120405,90,120457,90,918,90,120493,90,120551,90,120609,90,120667,90,120725,90,5059,90,42204,90,71849,90,65282,34,65284,36,65285,37,65286,38,65290,42,65291,43,65294,46,65295,47,65296,48,65297,49,65298,50,65299,51,65300,52,65301,53,65302,54,65303,55,65304,56,65305,57,65308,60,65309,61,65310,62,65312,64,65316,68,65318,70,65319,71,65324,76,65329,81,65330,82,65333,85,65334,86,65335,87,65343,95,65346,98,65348,100,65350,102,65355,107,65357,109,65358,110,65361,113,65362,114,65364,116,65365,117,65367,119,65370,122,65371,123,65373,125],"_default":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],"cs":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],"de":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],"es":[8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],"fr":[65374,126,65306,58,65281,33,8216,96,8245,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],"it":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],"ja":[8211,45,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65292,44,65307,59],"ko":[8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],"pl":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],"pt-BR":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],"qps-ploc":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],"ru":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,305,105,921,73,1009,112,215,120,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],"tr":[160,32,8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],"zh-hans":[65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65288,40,65289,41],"zh-hant":[8211,45,65374,126,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65307,59]}')),ue.cache=new E.LRUCachedComputed(M=>{function T(ne){const ae=new Map;for(let fe=0;fe!ne.startsWith("_")&&ne in j);te.length===0&&(te=["_default"]);let se;for(const ne of te){const ae=T(j[ne]);se=W(se,ae)}const de=T(j._common),ce=B(de,se);return new ue(ce)}),ue._locales=new e.Lazy(()=>Object.keys(ue.ambiguousCharacterData.getValue()).filter(M=>!M.startsWith("_")));class ge{static getRawData(){return JSON.parse("[9,10,11,12,13,32,127,160,173,847,1564,4447,4448,6068,6069,6155,6156,6157,6158,7355,7356,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8203,8204,8205,8206,8207,8234,8235,8236,8237,8238,8239,8287,8288,8289,8290,8291,8292,8293,8294,8295,8296,8297,8298,8299,8300,8301,8302,8303,10240,12288,12644,65024,65025,65026,65027,65028,65029,65030,65031,65032,65033,65034,65035,65036,65037,65038,65039,65279,65440,65520,65521,65522,65523,65524,65525,65526,65527,65528,65532,78844,119155,119156,119157,119158,119159,119160,119161,119162,917504,917505,917506,917507,917508,917509,917510,917511,917512,917513,917514,917515,917516,917517,917518,917519,917520,917521,917522,917523,917524,917525,917526,917527,917528,917529,917530,917531,917532,917533,917534,917535,917536,917537,917538,917539,917540,917541,917542,917543,917544,917545,917546,917547,917548,917549,917550,917551,917552,917553,917554,917555,917556,917557,917558,917559,917560,917561,917562,917563,917564,917565,917566,917567,917568,917569,917570,917571,917572,917573,917574,917575,917576,917577,917578,917579,917580,917581,917582,917583,917584,917585,917586,917587,917588,917589,917590,917591,917592,917593,917594,917595,917596,917597,917598,917599,917600,917601,917602,917603,917604,917605,917606,917607,917608,917609,917610,917611,917612,917613,917614,917615,917616,917617,917618,917619,917620,917621,917622,917623,917624,917625,917626,917627,917628,917629,917630,917631,917760,917761,917762,917763,917764,917765,917766,917767,917768,917769,917770,917771,917772,917773,917774,917775,917776,917777,917778,917779,917780,917781,917782,917783,917784,917785,917786,917787,917788,917789,917790,917791,917792,917793,917794,917795,917796,917797,917798,917799,917800,917801,917802,917803,917804,917805,917806,917807,917808,917809,917810,917811,917812,917813,917814,917815,917816,917817,917818,917819,917820,917821,917822,917823,917824,917825,917826,917827,917828,917829,917830,917831,917832,917833,917834,917835,917836,917837,917838,917839,917840,917841,917842,917843,917844,917845,917846,917847,917848,917849,917850,917851,917852,917853,917854,917855,917856,917857,917858,917859,917860,917861,917862,917863,917864,917865,917866,917867,917868,917869,917870,917871,917872,917873,917874,917875,917876,917877,917878,917879,917880,917881,917882,917883,917884,917885,917886,917887,917888,917889,917890,917891,917892,917893,917894,917895,917896,917897,917898,917899,917900,917901,917902,917903,917904,917905,917906,917907,917908,917909,917910,917911,917912,917913,917914,917915,917916,917917,917918,917919,917920,917921,917922,917923,917924,917925,917926,917927,917928,917929,917930,917931,917932,917933,917934,917935,917936,917937,917938,917939,917940,917941,917942,917943,917944,917945,917946,917947,917948,917949,917950,917951,917952,917953,917954,917955,917956,917957,917958,917959,917960,917961,917962,917963,917964,917965,917966,917967,917968,917969,917970,917971,917972,917973,917974,917975,917976,917977,917978,917979,917980,917981,917982,917983,917984,917985,917986,917987,917988,917989,917990,917991,917992,917993,917994,917995,917996,917997,917998,917999]")}static getData(){return this._data||(this._data=new Set(ge.getRawData())),this._data}static isInvisibleCharacter(T){return ge.getData().has(T)}static get codePoints(){return ge.getData()}}r.InvisibleCharacters=ge,ge._data=void 0}),Y(Q[28],Z([0,1,2]),function(U,r,E){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.StringSHA1=r.toHexString=r.stringHash=r.numberHash=r.doHash=r.hash=void 0;function e(L){return N(L,0)}r.hash=e;function N(L,C){switch(typeof L){case"object":return L===null?o(349,C):Array.isArray(L)?c(L,C):m(L,C);case"string":return g(L,C);case"boolean":return w(L,C);case"number":return o(L,C);case"undefined":return o(937,C);default:return o(617,C)}}r.doHash=N;function o(L,C){return(C<<5)-C+L|0}r.numberHash=o;function w(L,C){return o(L?433:863,C)}function g(L,C){C=o(149417,C);for(let y=0,p=L.length;yN(p,y),C)}function m(L,C){return C=o(181387,C),Object.keys(L).sort().reduce((y,p)=>(y=g(p,y),N(L[p],y)),C)}function S(L,C,y=32){const p=y-C,s=~((1<>>p)>>>0}function t(L,C=0,y=L.byteLength,p=0){for(let s=0;sy.toString(16).padStart(2,"0")).join(""):d((L>>>0).toString(16),C/4)}r.toHexString=h;class v{constructor(){this._h0=1732584193,this._h1=4023233417,this._h2=2562383102,this._h3=271733878,this._h4=3285377520,this._buff=new Uint8Array(64+3),this._buffDV=new DataView(this._buff.buffer),this._buffLen=0,this._totalLen=0,this._leftoverHighSurrogate=0,this._finished=!1}update(C){const y=C.length;if(y===0)return;const p=this._buff;let s=this._buffLen,i=this._leftoverHighSurrogate,a,l;for(i!==0?(a=i,l=-1,i=0):(a=C.charCodeAt(0),l=0);;){let f=a;if(E.isHighSurrogate(a))if(l+1>>6,C[y++]=128|(p&63)>>>0):p<65536?(C[y++]=224|(p&61440)>>>12,C[y++]=128|(p&4032)>>>6,C[y++]=128|(p&63)>>>0):(C[y++]=240|(p&1835008)>>>18,C[y++]=128|(p&258048)>>>12,C[y++]=128|(p&4032)>>>6,C[y++]=128|(p&63)>>>0),y>=64&&(this._step(),y-=64,this._totalLen+=64,C[0]=C[64+0],C[1]=C[64+1],C[2]=C[64+2]),y}digest(){return this._finished||(this._finished=!0,this._leftoverHighSurrogate&&(this._leftoverHighSurrogate=0,this._buffLen=this._push(this._buff,this._buffLen,65533)),this._totalLen+=this._buffLen,this._wrapUp()),h(this._h0)+h(this._h1)+h(this._h2)+h(this._h3)+h(this._h4)}_wrapUp(){this._buff[this._buffLen++]=128,t(this._buff,this._buffLen),this._buffLen>56&&(this._step(),t(this._buff));const C=8*this._totalLen;this._buffDV.setUint32(56,Math.floor(C/4294967296),!1),this._buffDV.setUint32(60,C%4294967296,!1),this._step()}_step(){const C=v._bigBlock32,y=this._buffDV;for(let b=0;b<64;b+=4)C.setUint32(b,y.getUint32(b,!1),!1);for(let b=64;b<320;b+=4)C.setUint32(b,S(C.getUint32(b-12,!1)^C.getUint32(b-32,!1)^C.getUint32(b-56,!1)^C.getUint32(b-64,!1),1),!1);let p=this._h0,s=this._h1,i=this._h2,a=this._h3,l=this._h4,f,u,_;for(let b=0;b<80;b++)b<20?(f=s&i|~s&a,u=1518500249):b<40?(f=s^i^a,u=1859775393):b<60?(f=s&i|s&a|i&a,u=2400959708):(f=s^i^a,u=3395469782),_=S(p,5)+f+l+u+C.getUint32(b*4,!1)&4294967295,l=a,a=i,i=S(s,30),s=p,p=_;this._h0=this._h0+p&4294967295,this._h1=this._h1+s&4294967295,this._h2=this._h2+i&4294967295,this._h3=this._h3+a&4294967295,this._h4=this._h4+l&4294967295}}r.StringSHA1=v,v._bigBlock32=new DataView(new ArrayBuffer(320))}),Y(Q[12],Z([0,1,19,28]),function(U,r,E,e){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.LcsDiff=r.MyArray=r.Debug=r.stringDiff=r.StringDiffSequence=void 0;class N{constructor(t){this.source=t}getElements(){const t=this.source,d=new Int32Array(t.length);for(let h=0,v=t.length;h0||this.m_modifiedCount>0)&&this.m_changes.push(new E.DiffChange(this.m_originalStart,this.m_originalCount,this.m_modifiedStart,this.m_modifiedCount)),this.m_originalCount=0,this.m_modifiedCount=0,this.m_originalStart=1073741824,this.m_modifiedStart=1073741824}AddOriginalElement(t,d){this.m_originalStart=Math.min(this.m_originalStart,t),this.m_modifiedStart=Math.min(this.m_modifiedStart,d),this.m_originalCount++}AddModifiedElement(t,d){this.m_originalStart=Math.min(this.m_originalStart,t),this.m_modifiedStart=Math.min(this.m_modifiedStart,d),this.m_modifiedCount++}getChanges(){return(this.m_originalCount>0||this.m_modifiedCount>0)&&this.MarkNextChange(),this.m_changes}getReverseChanges(){return(this.m_originalCount>0||this.m_modifiedCount>0)&&this.MarkNextChange(),this.m_changes.reverse(),this.m_changes}}class m{constructor(t,d,h=null){this.ContinueProcessingPredicate=h,this._originalSequence=t,this._modifiedSequence=d;const[v,L,C]=m._getElements(t),[y,p,s]=m._getElements(d);this._hasStrings=C&&s,this._originalStringElements=v,this._originalElementsOrHash=L,this._modifiedStringElements=y,this._modifiedElementsOrHash=p,this.m_forwardHistory=[],this.m_reverseHistory=[]}static _isStringArray(t){return t.length>0&&typeof t[0]=="string"}static _getElements(t){const d=t.getElements();if(m._isStringArray(d)){const h=new Int32Array(d.length);for(let v=0,L=d.length;v=t&&v>=h&&this.ElementsAreEqual(d,v);)d--,v--;if(t>d||h>v){let a;return h<=v?(w.Assert(t===d+1,"originalStart should only be one more than originalEnd"),a=[new E.DiffChange(t,0,h,v-h+1)]):t<=d?(w.Assert(h===v+1,"modifiedStart should only be one more than modifiedEnd"),a=[new E.DiffChange(t,d-t+1,h,0)]):(w.Assert(t===d+1,"originalStart should only be one more than originalEnd"),w.Assert(h===v+1,"modifiedStart should only be one more than modifiedEnd"),a=[]),a}const C=[0],y=[0],p=this.ComputeRecursionPoint(t,d,h,v,C,y,L),s=C[0],i=y[0];if(p!==null)return p;if(!L[0]){const a=this.ComputeDiffRecursive(t,s,h,i,L);let l=[];return L[0]?l=[new E.DiffChange(s+1,d-(s+1)+1,i+1,v-(i+1)+1)]:l=this.ComputeDiffRecursive(s+1,d,i+1,v,L),this.ConcatenateChanges(a,l)}return[new E.DiffChange(t,d-t+1,h,v-h+1)]}WALKTRACE(t,d,h,v,L,C,y,p,s,i,a,l,f,u,_,b,A,P){let D=null,k=null,R=new c,I=d,F=h,O=f[0]-b[0]-v,V=-1073741824,K=this.m_forwardHistory.length-1;do{const $=O+t;$===I||$=0&&(s=this.m_forwardHistory[K],t=s[0],I=1,F=s.length-1)}while(--K>=-1);if(D=R.getReverseChanges(),P[0]){let $=f[0]+1,z=b[0]+1;if(D!==null&&D.length>0){const n=D[D.length-1];$=Math.max($,n.getOriginalEnd()),z=Math.max(z,n.getModifiedEnd())}k=[new E.DiffChange($,l-$+1,z,_-z+1)]}else{R=new c,I=C,F=y,O=f[0]-b[0]-p,V=1073741824,K=A?this.m_reverseHistory.length-1:this.m_reverseHistory.length-2;do{const $=O+L;$===I||$=i[$+1]?(a=i[$+1]-1,u=a-O-p,a>V&&R.MarkNextChange(),V=a+1,R.AddOriginalElement(a+1,u+1),O=$+1-L):(a=i[$-1],u=a-O-p,a>V&&R.MarkNextChange(),V=a,R.AddModifiedElement(a+1,u+1),O=$-1-L),K>=0&&(i=this.m_reverseHistory[K],L=i[0],I=1,F=i.length-1)}while(--K>=-1);k=R.getChanges()}return this.ConcatenateChanges(D,k)}ComputeRecursionPoint(t,d,h,v,L,C,y){let p=0,s=0,i=0,a=0,l=0,f=0;t--,h--,L[0]=0,C[0]=0,this.m_forwardHistory=[],this.m_reverseHistory=[];const u=d-t+(v-h),_=u+1,b=new Int32Array(_),A=new Int32Array(_),P=v-h,D=d-t,k=t-h,R=d-v,F=(D-P)%2==0;b[P]=t,A[D]=d,y[0]=!1;for(let O=1;O<=u/2+1;O++){let V=0,K=0;i=this.ClipDiagonalBound(P-O,O,P,_),a=this.ClipDiagonalBound(P+O,O,P,_);for(let z=i;z<=a;z+=2){z===i||zV+K&&(V=p,K=s),!F&&Math.abs(z-D)<=O-1&&p>=A[z])return L[0]=p,C[0]=s,n<=A[z]&&1447>0&&O<=1447+1?this.WALKTRACE(P,i,a,k,D,l,f,R,b,A,p,d,L,s,v,C,F,y):null}const $=(V-t+(K-h)-O)/2;if(this.ContinueProcessingPredicate!==null&&!this.ContinueProcessingPredicate(V,$))return y[0]=!0,L[0]=V,C[0]=K,$>0&&1447>0&&O<=1447+1?this.WALKTRACE(P,i,a,k,D,l,f,R,b,A,p,d,L,s,v,C,F,y):(t++,h++,[new E.DiffChange(t,d-t+1,h,v-h+1)]);l=this.ClipDiagonalBound(D-O,O,D,_),f=this.ClipDiagonalBound(D+O,O,D,_);for(let z=l;z<=f;z+=2){z===l||z=A[z+1]?p=A[z+1]-1:p=A[z-1],s=p-(z-D)-R;const n=p;for(;p>t&&s>h&&this.ElementsAreEqual(p,s);)p--,s--;if(A[z]=p,F&&Math.abs(z-P)<=O&&p<=b[z])return L[0]=p,C[0]=s,n>=b[z]&&1447>0&&O<=1447+1?this.WALKTRACE(P,i,a,k,D,l,f,R,b,A,p,d,L,s,v,C,F,y):null}if(O<=1447){let z=new Int32Array(a-i+2);z[0]=P-i+1,g.Copy2(b,i,z,1,a-i+1),this.m_forwardHistory.push(z),z=new Int32Array(f-l+2),z[0]=D-l+1,g.Copy2(A,l,z,1,f-l+1),this.m_reverseHistory.push(z)}}return this.WALKTRACE(P,i,a,k,D,l,f,R,b,A,p,d,L,s,v,C,F,y)}PrettifyChanges(t){for(let d=0;d0,y=h.modifiedLength>0;for(;h.originalStart+h.originalLength=0;d--){const h=t[d];let v=0,L=0;if(d>0){const a=t[d-1];v=a.originalStart+a.originalLength,L=a.modifiedStart+a.modifiedLength}const C=h.originalLength>0,y=h.modifiedLength>0;let p=0,s=this._boundaryScore(h.originalStart,h.originalLength,h.modifiedStart,h.modifiedLength);for(let a=1;;a++){const l=h.originalStart-a,f=h.modifiedStart-a;if(ls&&(s=_,p=a)}h.originalStart-=p,h.modifiedStart-=p;const i=[null];if(d>0&&this.ChangesOverlap(t[d-1],t[d],i)){t[d-1]=i[0],t.splice(d,1),d++;continue}}if(this._hasStrings)for(let d=1,h=t.length;d0&&f>p&&(p=f,s=a,i=l)}return p>0?[s,i]:null}_contiguousSequenceScore(t,d,h){let v=0;for(let L=0;L=this._originalElementsOrHash.length-1?!0:this._hasStrings&&/^\s*$/.test(this._originalStringElements[t])}_OriginalRegionIsBoundary(t,d){if(this._OriginalIsBoundary(t)||this._OriginalIsBoundary(t-1))return!0;if(d>0){const h=t+d;if(this._OriginalIsBoundary(h-1)||this._OriginalIsBoundary(h))return!0}return!1}_ModifiedIsBoundary(t){return t<=0||t>=this._modifiedElementsOrHash.length-1?!0:this._hasStrings&&/^\s*$/.test(this._modifiedStringElements[t])}_ModifiedRegionIsBoundary(t,d){if(this._ModifiedIsBoundary(t)||this._ModifiedIsBoundary(t-1))return!0;if(d>0){const h=t+d;if(this._ModifiedIsBoundary(h-1)||this._ModifiedIsBoundary(h))return!0}return!1}_boundaryScore(t,d,h,v){const L=this._OriginalRegionIsBoundary(t,d)?1:0,C=this._ModifiedRegionIsBoundary(h,v)?1:0;return L+C}ConcatenateChanges(t,d){let h=[];if(t.length===0||d.length===0)return d.length>0?d:t;if(this.ChangesOverlap(t[t.length-1],d[0],h)){const v=new Array(t.length+d.length-1);return g.Copy(t,0,v,0,t.length-1),v[t.length-1]=h[0],g.Copy(d,1,v,t.length,d.length-1),v}else{const v=new Array(t.length+d.length);return g.Copy(t,0,v,0,t.length),g.Copy(d,0,v,t.length,d.length),v}}ChangesOverlap(t,d,h){if(w.Assert(t.originalStart<=d.originalStart,"Left change is not less than or equal to right change"),w.Assert(t.modifiedStart<=d.modifiedStart,"Left change is not less than or equal to right change"),t.originalStart+t.originalLength>=d.originalStart||t.modifiedStart+t.modifiedLength>=d.modifiedStart){const v=t.originalStart;let L=t.originalLength;const C=t.modifiedStart;let y=t.modifiedLength;return t.originalStart+t.originalLength>=d.originalStart&&(L=d.originalStart+d.originalLength-t.originalStart),t.modifiedStart+t.modifiedLength>=d.modifiedStart&&(y=d.modifiedStart+d.modifiedLength-t.modifiedStart),h[0]=new E.DiffChange(v,L,C,y),!0}else return h[0]=null,!1}ClipDiagonalBound(t,d,h,v){if(t>=0&&tfunction(){const b=Array.prototype.slice.call(arguments,0);return l(_,b)};let u={};for(const _ of a)u[_]=f(_);return u}r.createProxyObject=p;function s(a){return a===null?void 0:a}r.withNullAsUndefined=s;function i(a,l="Unreachable"){throw new Error(l)}r.assertNever=i}),Y(Q[29],Z([0,1,7]),function(U,r,E){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.getOrDefault=r.equals=r.mixin=r.cloneAndChange=r.deepFreeze=r.deepClone=void 0;function e(t){if(!t||typeof t!="object"||t instanceof RegExp)return t;const d=Array.isArray(t)?[]:{};return Object.keys(t).forEach(h=>{t[h]&&typeof t[h]=="object"?d[h]=e(t[h]):d[h]=t[h]}),d}r.deepClone=e;function N(t){if(!t||typeof t!="object")return t;const d=[t];for(;d.length>0;){const h=d.shift();Object.freeze(h);for(const v in h)if(o.call(h,v)){const L=h[v];typeof L=="object"&&!Object.isFrozen(L)&&d.push(L)}}return t}r.deepFreeze=N;const o=Object.prototype.hasOwnProperty;function w(t,d){return g(t,d,new Set)}r.cloneAndChange=w;function g(t,d,h){if((0,E.isUndefinedOrNull)(t))return t;const v=d(t);if(typeof v!="undefined")return v;if((0,E.isArray)(t)){const L=[];for(const C of t)L.push(g(C,d,h));return L}if((0,E.isObject)(t)){if(h.has(t))throw new Error("Cannot clone recursive data-structure");h.add(t);const L={};for(let C in t)o.call(t,C)&&(L[C]=g(t[C],d,h));return h.delete(t),L}return t}function c(t,d,h=!0){return(0,E.isObject)(t)?((0,E.isObject)(d)&&Object.keys(d).forEach(v=>{v in t?h&&((0,E.isObject)(t[v])&&(0,E.isObject)(d[v])?c(t[v],d[v],h):t[v]=d[v]):t[v]=d[v]}),t):d}r.mixin=c;function m(t,d){if(t===d)return!0;if(t==null||d===null||d===void 0||typeof t!=typeof d||typeof t!="object"||Array.isArray(t)!==Array.isArray(d))return!1;let h,v;if(Array.isArray(t)){if(t.length!==d.length)return!1;for(h=0;h255?255:N|0}r.toUint8=E;function e(N){return N<0?0:N>4294967295?4294967295:N|0}r.toUint32=e}),Y(Q[9],Z([0,1,26,5]),function(U,r,E,e){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.uriToFsPath=r.URI=void 0;const N=/^\w[\w\d+.-]*$/,o=/^\//,w=/^\/\//;function g(u,_){if(!u.scheme&&_)throw new Error(`[UriError]: Scheme is missing: {scheme: "", authority: "${u.authority}", path: "${u.path}", query: "${u.query}", fragment: "${u.fragment}"}`);if(u.scheme&&!N.test(u.scheme))throw new Error("[UriError]: Scheme contains illegal characters.");if(u.path){if(u.authority){if(!o.test(u.path))throw new Error('[UriError]: If a URI contains an authority component, then the path component must either be empty or begin with a slash ("/") character')}else if(w.test(u.path))throw new Error('[UriError]: If a URI does not contain an authority component, then the path cannot begin with two slash characters ("//")')}}function c(u,_){return!u&&!_?"file":u}function m(u,_){switch(u){case"https":case"http":case"file":_?_[0]!==t&&(_=t+_):_=t;break}return _}const S="",t="/",d=/^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;class h{constructor(_,b,A,P,D,k=!1){typeof _=="object"?(this.scheme=_.scheme||S,this.authority=_.authority||S,this.path=_.path||S,this.query=_.query||S,this.fragment=_.fragment||S):(this.scheme=c(_,k),this.authority=b||S,this.path=m(this.scheme,A||S),this.query=P||S,this.fragment=D||S,g(this,k))}static isUri(_){return _ instanceof h?!0:_?typeof _.authority=="string"&&typeof _.fragment=="string"&&typeof _.path=="string"&&typeof _.query=="string"&&typeof _.scheme=="string"&&typeof _.fsPath=="string"&&typeof _.with=="function"&&typeof _.toString=="function":!1}get fsPath(){return s(this,!1)}with(_){if(!_)return this;let{scheme:b,authority:A,path:P,query:D,fragment:k}=_;return b===void 0?b=this.scheme:b===null&&(b=S),A===void 0?A=this.authority:A===null&&(A=S),P===void 0?P=this.path:P===null&&(P=S),D===void 0?D=this.query:D===null&&(D=S),k===void 0?k=this.fragment:k===null&&(k=S),b===this.scheme&&A===this.authority&&P===this.path&&D===this.query&&k===this.fragment?this:new L(b,A,P,D,k)}static parse(_,b=!1){const A=d.exec(_);return A?new L(A[2]||S,f(A[4]||S),f(A[5]||S),f(A[7]||S),f(A[9]||S),b):new L(S,S,S,S,S)}static file(_){let b=S;if(e.isWindows&&(_=_.replace(/\\/g,t)),_[0]===t&&_[1]===t){const A=_.indexOf(t,2);A===-1?(b=_.substring(2),_=t):(b=_.substring(2,A),_=_.substring(A)||t)}return new L("file",b,_,S,S)}static from(_){const b=new L(_.scheme,_.authority,_.path,_.query,_.fragment);return g(b,!0),b}static joinPath(_,...b){if(!_.path)throw new Error("[UriError]: cannot call joinPath on URI without path");let A;return e.isWindows&&_.scheme==="file"?A=h.file(E.win32.join(s(_,!0),...b)).path:A=E.posix.join(_.path,...b),_.with({path:A})}toString(_=!1){return i(this,_)}toJSON(){return this}static revive(_){if(_){if(_ instanceof h)return _;{const b=new L(_);return b._formatted=_.external,b._fsPath=_._sep===v?_.fsPath:null,b}}else return _}}r.URI=h;const v=e.isWindows?1:void 0;class L extends h{constructor(){super(...arguments);this._formatted=null,this._fsPath=null}get fsPath(){return this._fsPath||(this._fsPath=s(this,!1)),this._fsPath}toString(_=!1){return _?i(this,!0):(this._formatted||(this._formatted=i(this,!1)),this._formatted)}toJSON(){const _={$mid:1};return this._fsPath&&(_.fsPath=this._fsPath,_._sep=v),this._formatted&&(_.external=this._formatted),this.path&&(_.path=this.path),this.scheme&&(_.scheme=this.scheme),this.authority&&(_.authority=this.authority),this.query&&(_.query=this.query),this.fragment&&(_.fragment=this.fragment),_}}const C={[58]:"%3A",[47]:"%2F",[63]:"%3F",[35]:"%23",[91]:"%5B",[93]:"%5D",[64]:"%40",[33]:"%21",[36]:"%24",[38]:"%26",[39]:"%27",[40]:"%28",[41]:"%29",[42]:"%2A",[43]:"%2B",[44]:"%2C",[59]:"%3B",[61]:"%3D",[32]:"%20"};function y(u,_){let b,A=-1;for(let P=0;P=97&&D<=122||D>=65&&D<=90||D>=48&&D<=57||D===45||D===46||D===95||D===126||_&&D===47)A!==-1&&(b+=encodeURIComponent(u.substring(A,P)),A=-1),b!==void 0&&(b+=u.charAt(P));else{b===void 0&&(b=u.substr(0,P));const k=C[D];k!==void 0?(A!==-1&&(b+=encodeURIComponent(u.substring(A,P)),A=-1),b+=k):A===-1&&(A=P)}}return A!==-1&&(b+=encodeURIComponent(u.substring(A))),b!==void 0?b:u}function p(u){let _;for(let b=0;b1&&u.scheme==="file"?b=`//${u.authority}${u.path}`:u.path.charCodeAt(0)===47&&(u.path.charCodeAt(1)>=65&&u.path.charCodeAt(1)<=90||u.path.charCodeAt(1)>=97&&u.path.charCodeAt(1)<=122)&&u.path.charCodeAt(2)===58?_?b=u.path.substr(1):b=u.path[1].toLowerCase()+u.path.substr(2):b=u.path,e.isWindows&&(b=b.replace(/\//g,"\\")),b}r.uriToFsPath=s;function i(u,_){const b=_?p:y;let A="",{scheme:P,authority:D,path:k,query:R,fragment:I}=u;if(P&&(A+=P,A+=":"),(D||P==="file")&&(A+=t,A+=t),D){let F=D.indexOf("@");if(F!==-1){const O=D.substr(0,F);D=D.substr(F+1),F=O.indexOf(":"),F===-1?A+=b(O,!1):(A+=b(O.substr(0,F),!1),A+=":",A+=b(O.substr(F+1),!1)),A+="@"}D=D.toLowerCase(),F=D.indexOf(":"),F===-1?A+=b(D,!1):(A+=b(D.substr(0,F),!1),A+=D.substr(F))}if(k){if(k.length>=3&&k.charCodeAt(0)===47&&k.charCodeAt(2)===58){const F=k.charCodeAt(1);F>=65&&F<=90&&(k=`/${String.fromCharCode(F+32)}:${k.substr(3)}`)}else if(k.length>=2&&k.charCodeAt(1)===58){const F=k.charCodeAt(0);F>=65&&F<=90&&(k=`${String.fromCharCode(F+32)}:${k.substr(2)}`)}A+=b(k,!0)}return R&&(A+="?",A+=b(R,!1)),I&&(A+="#",A+=_?I:y(I,!1)),A}function a(u){try{return decodeURIComponent(u)}catch{return u.length>3?u.substr(0,3)+a(u.substr(3)):u}}const l=/(%[0-9A-Za-z][0-9A-Za-z])+/g;function f(u){return u.match(l)?u.replace(l,_=>a(_)):u}}),Y(Q[44],Z([0,1,10,6,8,5,7,2]),function(U,r,E,e,N,o,w,g){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.create=r.SimpleWorkerServer=r.SimpleWorkerClient=r.logOnceWebWorkerWarning=void 0;const c="$initialize";let m=!1;function S(f){!o.isWeb||(m||(m=!0,console.warn("Could not create web worker(s). Falling back to loading web worker code in main thread, which might cause UI freezes. Please see https://github.com/microsoft/monaco-editor#faq")),console.warn(f.message))}r.logOnceWebWorkerWarning=S;class t{constructor(u,_,b,A){this.vsWorker=u,this.req=_,this.method=b,this.args=A,this.type=0}}class d{constructor(u,_,b,A){this.vsWorker=u,this.seq=_,this.res=b,this.err=A,this.type=1}}class h{constructor(u,_,b,A){this.vsWorker=u,this.req=_,this.eventName=b,this.arg=A,this.type=2}}class v{constructor(u,_,b){this.vsWorker=u,this.req=_,this.event=b,this.type=3}}class L{constructor(u,_){this.vsWorker=u,this.req=_,this.type=4}}class C{constructor(u){this._workerId=-1,this._handler=u,this._lastSentReq=0,this._pendingReplies=Object.create(null),this._pendingEmitters=new Map,this._pendingEvents=new Map}setWorkerId(u){this._workerId=u}sendMessage(u,_){const b=String(++this._lastSentReq);return new Promise((A,P)=>{this._pendingReplies[b]={resolve:A,reject:P},this._send(new t(this._workerId,b,u,_))})}listen(u,_){let b=null;const A=new e.Emitter({onFirstListenerAdd:()=>{b=String(++this._lastSentReq),this._pendingEmitters.set(b,A),this._send(new h(this._workerId,b,u,_))},onLastListenerRemove:()=>{this._pendingEmitters.delete(b),this._send(new L(this._workerId,b)),b=null}});return A.event}handleMessage(u){!u||!u.vsWorker||this._workerId!==-1&&u.vsWorker!==this._workerId||this._handleMessage(u)}_handleMessage(u){switch(u.type){case 1:return this._handleReplyMessage(u);case 0:return this._handleRequestMessage(u);case 2:return this._handleSubscribeEventMessage(u);case 3:return this._handleEventMessage(u);case 4:return this._handleUnsubscribeEventMessage(u)}}_handleReplyMessage(u){if(!this._pendingReplies[u.seq]){console.warn("Got reply to unknown seq");return}let _=this._pendingReplies[u.seq];if(delete this._pendingReplies[u.seq],u.err){let b=u.err;u.err.$isError&&(b=new Error,b.name=u.err.name,b.message=u.err.message,b.stack=u.err.stack),_.reject(b);return}_.resolve(u.res)}_handleRequestMessage(u){let _=u.req;this._handler.handleMessage(u.method,u.args).then(A=>{this._send(new d(this._workerId,_,A,void 0))},A=>{A.detail instanceof Error&&(A.detail=(0,E.transformErrorForSerialization)(A.detail)),this._send(new d(this._workerId,_,void 0,(0,E.transformErrorForSerialization)(A)))})}_handleSubscribeEventMessage(u){const _=u.req,b=this._handler.handleEvent(u.eventName,u.arg)(A=>{this._send(new v(this._workerId,_,A))});this._pendingEvents.set(_,b)}_handleEventMessage(u){if(!this._pendingEmitters.has(u.req)){console.warn("Got event for unknown req");return}this._pendingEmitters.get(u.req).fire(u.event)}_handleUnsubscribeEventMessage(u){if(!this._pendingEvents.has(u.req)){console.warn("Got unsubscribe for unknown req");return}this._pendingEvents.get(u.req).dispose(),this._pendingEvents.delete(u.req)}_send(u){let _=[];if(u.type===0)for(let b=0;b{this._protocol.handleMessage(I)},I=>{A&&A(I)})),this._protocol=new C({sendMessage:(I,F)=>{this._worker.postMessage(I,F)},handleMessage:(I,F)=>{if(typeof b[I]!="function")return Promise.reject(new Error("Missing method "+I+" on main thread host."));try{return Promise.resolve(b[I].apply(b,F))}catch(O){return Promise.reject(O)}},handleEvent:(I,F)=>{if(s(I)){const O=b[I].call(b,F);if(typeof O!="function")throw new Error(`Missing dynamic event ${I} on main thread host.`);return O}if(p(I)){const O=b[I];if(typeof O!="function")throw new Error(`Missing event ${I} on main thread host.`);return O}throw new Error(`Malformed event name ${I}`)}}),this._protocol.setWorkerId(this._worker.getId());let P=null;typeof o.globals.require!="undefined"&&typeof o.globals.require.getConfig=="function"?P=o.globals.require.getConfig():typeof o.globals.requirejs!="undefined"&&(P=o.globals.requirejs.s.contexts._.config);const D=w.getAllMethodNames(b);this._onModuleLoaded=this._protocol.sendMessage(c,[this._worker.getId(),JSON.parse(JSON.stringify(P)),_,D]);const k=(I,F)=>this._request(I,F),R=(I,F)=>this._protocol.listen(I,F);this._lazyProxy=new Promise((I,F)=>{A=F,this._onModuleLoaded.then(O=>{I(i(O,k,R))},O=>{F(O),this._onError("Worker failed to load "+_,O)})})}getProxyObject(){return this._lazyProxy}_request(u,_){return new Promise((b,A)=>{this._onModuleLoaded.then(()=>{this._protocol.sendMessage(u,_).then(b,A)},A)})}_onError(u,_){console.error(u),console.info(_)}}r.SimpleWorkerClient=y;function p(f){return f[0]==="o"&&f[1]==="n"&&g.isUpperAsciiLetter(f.charCodeAt(2))}function s(f){return/^onDynamic/.test(f)&&g.isUpperAsciiLetter(f.charCodeAt(9))}function i(f,u,_){const b=D=>function(){const k=Array.prototype.slice.call(arguments,0);return u(D,k)},A=D=>function(k){return _(D,k)};let P={};for(const D of f){if(s(D)){P[D]=A(D);continue}if(p(D)){P[D]=_(D,void 0);continue}P[D]=b(D)}return P}class a{constructor(u,_){this._requestHandlerFactory=_,this._requestHandler=null,this._protocol=new C({sendMessage:(b,A)=>{u(b,A)},handleMessage:(b,A)=>this._handleMessage(b,A),handleEvent:(b,A)=>this._handleEvent(b,A)})}onmessage(u){this._protocol.handleMessage(u)}_handleMessage(u,_){if(u===c)return this.initialize(_[0],_[1],_[2],_[3]);if(!this._requestHandler||typeof this._requestHandler[u]!="function")return Promise.reject(new Error("Missing requestHandler or method: "+u));try{return Promise.resolve(this._requestHandler[u].apply(this._requestHandler,_))}catch(b){return Promise.reject(b)}}_handleEvent(u,_){if(!this._requestHandler)throw new Error("Missing requestHandler");if(s(u)){const b=this._requestHandler[u].call(this._requestHandler,_);if(typeof b!="function")throw new Error(`Missing dynamic event ${u} on request handler.`);return b}if(p(u)){const b=this._requestHandler[u];if(typeof b!="function")throw new Error(`Missing event ${u} on request handler.`);return b}throw new Error(`Malformed event name ${u}`)}initialize(u,_,b,A){this._protocol.setWorkerId(u);const k=i(A,(R,I)=>this._protocol.sendMessage(R,I),(R,I)=>this._protocol.listen(R,I));return this._requestHandlerFactory?(this._requestHandler=this._requestHandlerFactory(k),Promise.resolve(w.getAllMethodNames(this._requestHandler))):(_&&(typeof _.baseUrl!="undefined"&&delete _.baseUrl,typeof _.paths!="undefined"&&typeof _.paths.vs!="undefined"&&delete _.paths.vs,typeof _.trustedTypesPolicy!==void 0&&delete _.trustedTypesPolicy,_.catchError=!0,o.globals.require.config(_)),new Promise((R,I)=>{(o.globals.require||U)([b],O=>{if(this._requestHandler=O.create(k),!this._requestHandler){I(new Error("No RequestHandler!"));return}R(w.getAllMethodNames(this._requestHandler))},I)}))}}r.SimpleWorkerServer=a;function l(f){return new a(f,null)}r.create=l}),Y(Q[14],Z([0,1,13]),function(U,r,E){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.CharacterSet=r.CharacterClassifier=void 0;class e{constructor(w){const g=(0,E.toUint8)(w);this._defaultValue=g,this._asciiMap=e._createAsciiMap(g),this._map=new Map}static _createAsciiMap(w){const g=new Uint8Array(256);for(let c=0;c<256;c++)g[c]=w;return g}set(w,g){const c=(0,E.toUint8)(g);w>=0&&w<256?this._asciiMap[w]=c:this._map.set(w,c)}get(w){return w>=0&&w<256?this._asciiMap[w]:this._map.get(w)||this._defaultValue}}r.CharacterClassifier=e;class N{constructor(){this._actual=new e(0)}add(w){this._actual.set(w,1)}has(w){return this._actual.get(w)===1}}r.CharacterSet=N}),Y(Q[3],Z([0,1]),function(U,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.Position=void 0;class E{constructor(N,o){this.lineNumber=N,this.column=o}with(N=this.lineNumber,o=this.column){return N===this.lineNumber&&o===this.column?this:new E(N,o)}delta(N=0,o=0){return this.with(this.lineNumber+N,this.column+o)}equals(N){return E.equals(this,N)}static equals(N,o){return!N&&!o?!0:!!N&&!!o&&N.lineNumber===o.lineNumber&&N.column===o.column}isBefore(N){return E.isBefore(this,N)}static isBefore(N,o){return N.lineNumberg||o===g&&w>c?(this.startLineNumber=g,this.startColumn=c,this.endLineNumber=o,this.endColumn=w):(this.startLineNumber=o,this.startColumn=w,this.endLineNumber=g,this.endColumn=c)}isEmpty(){return e.isEmpty(this)}static isEmpty(o){return o.startLineNumber===o.endLineNumber&&o.startColumn===o.endColumn}containsPosition(o){return e.containsPosition(this,o)}static containsPosition(o,w){return!(w.lineNumbero.endLineNumber||w.lineNumber===o.startLineNumber&&w.columno.endColumn)}static strictContainsPosition(o,w){return!(w.lineNumbero.endLineNumber||w.lineNumber===o.startLineNumber&&w.column<=o.startColumn||w.lineNumber===o.endLineNumber&&w.column>=o.endColumn)}containsRange(o){return e.containsRange(this,o)}static containsRange(o,w){return!(w.startLineNumbero.endLineNumber||w.endLineNumber>o.endLineNumber||w.startLineNumber===o.startLineNumber&&w.startColumno.endColumn)}strictContainsRange(o){return e.strictContainsRange(this,o)}static strictContainsRange(o,w){return!(w.startLineNumbero.endLineNumber||w.endLineNumber>o.endLineNumber||w.startLineNumber===o.startLineNumber&&w.startColumn<=o.startColumn||w.endLineNumber===o.endLineNumber&&w.endColumn>=o.endColumn)}plusRange(o){return e.plusRange(this,o)}static plusRange(o,w){let g,c,m,S;return w.startLineNumbero.endLineNumber?(m=w.endLineNumber,S=w.endColumn):w.endLineNumber===o.endLineNumber?(m=w.endLineNumber,S=Math.max(w.endColumn,o.endColumn)):(m=o.endLineNumber,S=o.endColumn),new e(g,c,m,S)}intersectRanges(o){return e.intersectRanges(this,o)}static intersectRanges(o,w){let g=o.startLineNumber,c=o.startColumn,m=o.endLineNumber,S=o.endColumn,t=w.startLineNumber,d=w.startColumn,h=w.endLineNumber,v=w.endColumn;return gh?(m=h,S=v):m===h&&(S=Math.min(S,v)),g>m||g===m&&c>S?null:new e(g,c,m,S)}equalsRange(o){return e.equalsRange(this,o)}static equalsRange(o,w){return!!o&&!!w&&o.startLineNumber===w.startLineNumber&&o.startColumn===w.startColumn&&o.endLineNumber===w.endLineNumber&&o.endColumn===w.endColumn}getEndPosition(){return e.getEndPosition(this)}static getEndPosition(o){return new E.Position(o.endLineNumber,o.endColumn)}getStartPosition(){return e.getStartPosition(this)}static getStartPosition(o){return new E.Position(o.startLineNumber,o.startColumn)}toString(){return"["+this.startLineNumber+","+this.startColumn+" -> "+this.endLineNumber+","+this.endColumn+"]"}setEndPosition(o,w){return new e(this.startLineNumber,this.startColumn,o,w)}setStartPosition(o,w){return new e(o,w,this.endLineNumber,this.endColumn)}collapseToStart(){return e.collapseToStart(this)}static collapseToStart(o){return new e(o.startLineNumber,o.startColumn,o.startLineNumber,o.startColumn)}static fromPositions(o,w=o){return new e(o.lineNumber,o.column,w.lineNumber,w.column)}static lift(o){return o?new e(o.startLineNumber,o.startColumn,o.endLineNumber,o.endColumn):null}static isIRange(o){return o&&typeof o.startLineNumber=="number"&&typeof o.startColumn=="number"&&typeof o.endLineNumber=="number"&&typeof o.endColumn=="number"}static areIntersectingOrTouching(o,w){return!(o.endLineNumbero.startLineNumber}toJSON(){return this}}r.Range=e}),Y(Q[30],Z([0,1,3,4]),function(U,r,E,e){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.Selection=void 0;class N extends e.Range{constructor(w,g,c,m){super(w,g,c,m);this.selectionStartLineNumber=w,this.selectionStartColumn=g,this.positionLineNumber=c,this.positionColumn=m}toString(){return"["+this.selectionStartLineNumber+","+this.selectionStartColumn+" -> "+this.positionLineNumber+","+this.positionColumn+"]"}equalsSelection(w){return N.selectionsEqual(this,w)}static selectionsEqual(w,g){return w.selectionStartLineNumber===g.selectionStartLineNumber&&w.selectionStartColumn===g.selectionStartColumn&&w.positionLineNumber===g.positionLineNumber&&w.positionColumn===g.positionColumn}getDirection(){return this.selectionStartLineNumber===this.startLineNumber&&this.selectionStartColumn===this.startColumn?0:1}setEndPosition(w,g){return this.getDirection()===0?new N(this.startLineNumber,this.startColumn,w,g):new N(w,g,this.startLineNumber,this.startColumn)}getPosition(){return new E.Position(this.positionLineNumber,this.positionColumn)}getSelectionStart(){return new E.Position(this.selectionStartLineNumber,this.selectionStartColumn)}setStartPosition(w,g){return this.getDirection()===0?new N(w,g,this.endLineNumber,this.endColumn):new N(this.endLineNumber,this.endColumn,w,g)}static fromPositions(w,g=w){return new N(w.lineNumber,w.column,g.lineNumber,g.column)}static fromRange(w,g){return g===0?new N(w.startLineNumber,w.startColumn,w.endLineNumber,w.endColumn):new N(w.endLineNumber,w.endColumn,w.startLineNumber,w.startColumn)}static liftSelection(w){return new N(w.selectionStartLineNumber,w.selectionStartColumn,w.positionLineNumber,w.positionColumn)}static selectionsArrEqual(w,g){if(w&&!g||!w&&g)return!1;if(!w&&!g)return!0;if(w.length!==g.length)return!1;for(let c=0,m=w.length;c(w.hasOwnProperty(g)||(w[g]=o(g)),w[g])}r.getMapForWordSeparators=N(o=>new e(o))}),Y(Q[15],Z([0,1]),function(U,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.getWordAtText=r.ensureValidWordDefinition=r.DEFAULT_WORD_REGEXP=r.USUAL_WORD_SEPARATORS=void 0,r.USUAL_WORD_SEPARATORS="`~!@#$%^&*()-=+[{]}\\|;:'\",.<>/?";function E(g=""){let c="(-?\\d*\\.\\d\\w*)|([^";for(const m of r.USUAL_WORD_SEPARATORS)g.indexOf(m)>=0||(c+="\\"+m);return c+="\\s]+)",new RegExp(c,"g")}r.DEFAULT_WORD_REGEXP=E();function e(g){let c=r.DEFAULT_WORD_REGEXP;if(g&&g instanceof RegExp)if(g.global)c=g;else{let m="g";g.ignoreCase&&(m+="i"),g.multiline&&(m+="m"),g.unicode&&(m+="u"),c=new RegExp(g.source,m)}return c.lastIndex=0,c}r.ensureValidWordDefinition=e;const N={maxLen:1e3,windowSize:15,timeBudget:150};function o(g,c,m,S,t=N){if(m.length>t.maxLen){let C=g-t.maxLen/2;return C<0?C=0:S+=C,m=m.substring(C,g+t.maxLen/2),o(g,c,m,S,t)}const d=Date.now(),h=g-1-S;let v=-1,L=null;for(let C=1;!(Date.now()-d>=t.timeBudget);C++){const y=h-t.windowSize*C;c.lastIndex=Math.max(0,y);const p=w(c,m,h,v);if(!p&&L||(L=p,y<=0))break;v=y}if(L){const C={word:L[0],startColumn:S+1+L.index,endColumn:S+1+L.index+L[0].length};return c.lastIndex=0,C}return null}r.getWordAtText=o;function w(g,c,m,S){let t;for(;t=g.exec(c);){const d=t.index||0;if(d<=m&&g.lastIndex>=m)return t;if(S>0&&d>S)return null}return null}}),Y(Q[32],Z([0,1,12,2]),function(U,r,E,e){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.DiffComputer=void 0;const N=3;function o(L,C,y,p){return new E.LcsDiff(L,C,y).ComputeDiff(p)}class w{constructor(C){const y=[],p=[];for(let s=0,i=C.length;s0&&y.originalLength<20&&y.modifiedLength>0&&y.modifiedLength<20&&i()){const P=p.createCharSequence(C,y.originalStart,y.originalStart+y.originalLength-1),D=s.createCharSequence(C,y.modifiedStart,y.modifiedStart+y.modifiedLength-1);let k=o(P,D,i,!0).changes;l&&(k=m(k)),A=[];for(let R=0,I=k.length;R1&&k>1;){const R=A.charCodeAt(D-2),I=P.charCodeAt(k-2);if(R!==I)break;D--,k--}(D>1||k>1)&&this._pushTrimWhitespaceCharChange(s,i+1,1,D,a+1,1,k)}{let D=h(A,1),k=h(P,1);const R=A.length+1,I=P.length+1;for(;D!0;const C=Date.now();return()=>Date.now()-Ch&&(h=s),p>v&&(v=p),i>v&&(v=i)}h++,v++;const L=new e(v,h,0);for(let C=0,y=d.length;C=this._maxCharCode?0:this._states.get(d,h)}}r.StateMachine=N;let o=null;function w(){return o===null&&(o=new N([[1,104,2],[1,72,2],[1,102,6],[1,70,6],[2,116,3],[2,84,3],[3,116,4],[3,84,4],[4,112,5],[4,80,5],[5,115,9],[5,83,9],[5,58,10],[6,105,7],[6,73,7],[7,108,8],[7,76,8],[8,101,9],[8,69,9],[9,58,10],[10,47,11],[11,47,12]])),o}let g=null;function c(){if(g===null){g=new E.CharacterClassifier(0);const t=` 	<>'"\u3001\u3002\uFF61\uFF64\uFF0C\uFF0E\uFF1A\uFF1B\u2018\u3008\u300C\u300E\u3014\uFF08\uFF3B\uFF5B\uFF62\uFF63\uFF5D\uFF3D\uFF09\u3015\u300F\u300D\u3009\u2019\uFF40\uFF5E\u2026`;for(let h=0;hL);if(L>0){const p=h.charCodeAt(L-1),s=h.charCodeAt(y);(p===40&&s===41||p===91&&s===93||p===123&&s===125)&&y--}return{range:{startLineNumber:v,startColumn:L+1,endLineNumber:v,endColumn:y+2},url:h.substring(L,y+1)}}static computeLinks(d,h=w()){const v=c(),L=[];for(let C=1,y=d.getLineCount();C<=y;C++){const p=d.getLineContent(C),s=p.length;let i=0,a=0,l=0,f=1,u=!1,_=!1,b=!1,A=!1;for(;i=0?(g+=w?1:-1,g<0?g=N.length-1:g%=N.length,N[g]):null}}r.BasicInplaceReplace=E,E.INSTANCE=new E}),Y(Q[35],Z([0,1,29]),function(U,r,E){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.shouldSynchronizeModel=r.ApplyEditsResult=r.SearchData=r.ValidAnnotatedEditOperation=r.FindMatch=r.TextModelResolvedOptions=r.InjectedTextCursorStops=r.MinimapPosition=r.OverviewRulerLane=void 0;var e;(function(d){d[d.Left=1]="Left",d[d.Center=2]="Center",d[d.Right=4]="Right",d[d.Full=7]="Full"})(e=r.OverviewRulerLane||(r.OverviewRulerLane={}));var N;(function(d){d[d.Inline=1]="Inline",d[d.Gutter=2]="Gutter"})(N=r.MinimapPosition||(r.MinimapPosition={}));var o;(function(d){d[d.Both=0]="Both",d[d.Right=1]="Right",d[d.Left=2]="Left",d[d.None=3]="None"})(o=r.InjectedTextCursorStops||(r.InjectedTextCursorStops={}));class w{constructor(h){this._textModelResolvedOptionsBrand=void 0,this.tabSize=Math.max(1,h.tabSize|0),this.indentSize=h.tabSize|0,this.insertSpaces=Boolean(h.insertSpaces),this.defaultEOL=h.defaultEOL|0,this.trimAutoWhitespace=Boolean(h.trimAutoWhitespace),this.bracketPairColorizationOptions=h.bracketPairColorizationOptions}equals(h){return this.tabSize===h.tabSize&&this.indentSize===h.indentSize&&this.insertSpaces===h.insertSpaces&&this.defaultEOL===h.defaultEOL&&this.trimAutoWhitespace===h.trimAutoWhitespace&&(0,E.equals)(this.bracketPairColorizationOptions,h.bracketPairColorizationOptions)}createChangeEvent(h){return{tabSize:this.tabSize!==h.tabSize,indentSize:this.indentSize!==h.indentSize,insertSpaces:this.insertSpaces!==h.insertSpaces,trimAutoWhitespace:this.trimAutoWhitespace!==h.trimAutoWhitespace}}}r.TextModelResolvedOptions=w;class g{constructor(h,v){this._findMatchBrand=void 0,this.range=h,this.matches=v}}r.FindMatch=g;class c{constructor(h,v,L,C,y,p){this.identifier=h,this.range=v,this.text=L,this.forceMoveMarkers=C,this.isAutoWhitespaceEdit=y,this._isTracked=p}}r.ValidAnnotatedEditOperation=c;class m{constructor(h,v,L){this.regex=h,this.wordSeparators=v,this.simpleSearch=L}}r.SearchData=m;class S{constructor(h,v,L){this.reverseEdits=h,this.changes=v,this.trimAutoWhitespaceLineNumbers=L}}r.ApplyEditsResult=S;function t(d){return!d.isTooLargeForSyncing()&&!d.isForSimpleWidget}r.shouldSynchronizeModel=t}),Y(Q[36],Z([0,1,16,13]),function(U,r,E,e){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.PrefixSumIndexOfResult=r.ConstantTimePrefixSumComputer=r.PrefixSumComputer=void 0;class N{constructor(c){this.values=c,this.prefixSum=new Uint32Array(c.length),this.prefixSumValidIndex=new Int32Array(1),this.prefixSumValidIndex[0]=-1}insertValues(c,m){c=(0,e.toUint32)(c);const S=this.values,t=this.prefixSum,d=m.length;return d===0?!1:(this.values=new Uint32Array(S.length+d),this.values.set(S.subarray(0,c),0),this.values.set(S.subarray(c),c+d),this.values.set(m,c),c-1=0&&this.prefixSum.set(t.subarray(0,this.prefixSumValidIndex[0]+1)),!0)}setValue(c,m){return c=(0,e.toUint32)(c),m=(0,e.toUint32)(m),this.values[c]===m?!1:(this.values[c]=m,c-1=S.length)return!1;const d=S.length-c;return m>=d&&(m=d),m===0?!1:(this.values=new Uint32Array(S.length-m),this.values.set(S.subarray(0,c),0),this.values.set(S.subarray(c+m),c),this.prefixSum=new Uint32Array(this.values.length),c-1=0&&this.prefixSum.set(t.subarray(0,this.prefixSumValidIndex[0]+1)),!0)}getTotalSum(){return this.values.length===0?0:this._getPrefixSum(this.values.length-1)}getPrefixSum(c){return c<0?0:(c=(0,e.toUint32)(c),this._getPrefixSum(c))}_getPrefixSum(c){if(c<=this.prefixSumValidIndex[0])return this.prefixSum[c];let m=this.prefixSumValidIndex[0]+1;m===0&&(this.prefixSum[0]=this.values[0],m++),c>=this.values.length&&(c=this.values.length-1);for(let S=m;S<=c;S++)this.prefixSum[S]=this.prefixSum[S-1]+this.values[S];return this.prefixSumValidIndex[0]=Math.max(this.prefixSumValidIndex[0],c),this.prefixSum[c]}getIndexOf(c){c=Math.floor(c),this.getTotalSum();let m=0,S=this.values.length-1,t=0,d=0,h=0;for(;m<=S;)if(t=m+(S-m)/2|0,d=this.prefixSum[t],h=d-this.values[t],c=d)m=t+1;else break;return new w(t,c-h)}}r.PrefixSumComputer=N;class o{constructor(c){this._values=c,this._isValid=!1,this._validEndIndex=-1,this._prefixSum=[],this._indexBySum=[]}getTotalSum(){return this._ensureValid(),this._indexBySum.length}getPrefixSum(c){return this._ensureValid(),c===0?0:this._prefixSum[c-1]}getIndexOf(c){this._ensureValid();const m=this._indexBySum[c],S=m>0?this._prefixSum[m-1]:0;return new w(m,c-S)}removeValues(c,m){this._values.splice(c,m),this._invalidate(c)}insertValues(c,m){this._values=(0,E.arrayInsert)(this._values,c,m),this._invalidate(c)}_invalidate(c){this._isValid=!1,this._validEndIndex=Math.min(this._validEndIndex,c-1)}_ensureValid(){if(!this._isValid){for(let c=this._validEndIndex+1,m=this._values.length;c0?this._prefixSum[c-1]:0;this._prefixSum[c]=t+S;for(let d=0;d=0;let s=null;try{s=E.createRegExp(this.searchString,this.isRegex,{matchCase:this.matchCase,wholeWord:!1,multiline:p,global:!0,unicode:!0})}catch{return null}if(!s)return null;let i=!this.isRegex&&!p;return i&&this.searchString.toLowerCase()!==this.searchString.toUpperCase()&&(i=this.matchCase),new w.SearchData(s,this.wordSeparators?(0,e.getMapForWordSeparators)(this.wordSeparators):null,i?this.searchString:null)}}r.SearchParams=c;function m(y){if(!y||y.length===0)return!1;for(let p=0,s=y.length;p=s)break;const a=y.charCodeAt(p);if(a===110||a===114||a===87)return!0}return!1}r.isMultilineRegexSource=m;function S(y,p,s){if(!s)return new w.FindMatch(y,null);const i=[];for(let a=0,l=p.length;a>0);s[l]>=p?a=l-1:s[l+1]>=p?(i=l,a=l):i=l+1}return i+1}}class d{static findMatches(p,s,i,a,l){const f=s.parseSearchRequest();return f?f.regex.multiline?this._doFindMatchesMultiline(p,i,new C(f.wordSeparators,f.regex),a,l):this._doFindMatchesLineByLine(p,i,f,a,l):[]}static _getMultilineMatchRange(p,s,i,a,l,f){let u,_=0;a?(_=a.findLineFeedCountBeforeOffset(l),u=s+l+_):u=s+l;let b;if(a){const k=a.findLineFeedCountBeforeOffset(l+f.length)-_;b=u+f.length+k}else b=u+f.length;const A=p.getPositionAt(u),P=p.getPositionAt(b);return new o.Range(A.lineNumber,A.column,P.lineNumber,P.column)}static _doFindMatchesMultiline(p,s,i,a,l){const f=p.getOffsetAt(s.getStartPosition()),u=p.getValueInRange(s,1),_=p.getEOL()===`\r
    +`?new t(u):null,b=[];let A=0,P;for(i.reset(0);P=i.next(u);)if(b[A++]=S(this._getMultilineMatchRange(p,f,u,_,P.index,P[0]),P,a),A>=l)return b;return b}static _doFindMatchesLineByLine(p,s,i,a,l){const f=[];let u=0;if(s.startLineNumber===s.endLineNumber){const b=p.getLineContent(s.startLineNumber).substring(s.startColumn-1,s.endColumn-1);return u=this._findMatchesInLine(i,b,s.startLineNumber,s.startColumn-1,u,f,a,l),f}const _=p.getLineContent(s.startLineNumber).substring(s.startColumn-1);u=this._findMatchesInLine(i,_,s.startLineNumber,s.startColumn-1,u,f,a,l);for(let b=s.startLineNumber+1;b=_))return l;return l}const A=new C(p.wordSeparators,p.regex);let P;A.reset(0);do if(P=A.next(s),P&&(f[l++]=S(new o.Range(i,P.index+1+a,i,P.index+1+P[0].length+a),P,u),l>=_))return l;while(P);return l}static findNextMatch(p,s,i,a){const l=s.parseSearchRequest();if(!l)return null;const f=new C(l.wordSeparators,l.regex);return l.regex.multiline?this._doFindNextMatchMultiline(p,i,f,a):this._doFindNextMatchLineByLine(p,i,f,a)}static _doFindNextMatchMultiline(p,s,i,a){const l=new N.Position(s.lineNumber,1),f=p.getOffsetAt(l),u=p.getLineCount(),_=p.getValueInRange(new o.Range(l.lineNumber,l.column,u,p.getLineMaxColumn(u)),1),b=p.getEOL()===`\r
    +`?new t(_):null;i.reset(s.column-1);let A=i.next(_);return A?S(this._getMultilineMatchRange(p,f,_,b,A.index,A[0]),A,a):s.lineNumber!==1||s.column!==1?this._doFindNextMatchMultiline(p,new N.Position(1,1),i,a):null}static _doFindNextMatchLineByLine(p,s,i,a){const l=p.getLineCount(),f=s.lineNumber,u=p.getLineContent(f),_=this._findFirstMatchInLine(i,u,f,s.column,a);if(_)return _;for(let b=1;b<=l;b++){const A=(f+b-1)%l,P=p.getLineContent(A+1),D=this._findFirstMatchInLine(i,P,A+1,1,a);if(D)return D}return null}static _findFirstMatchInLine(p,s,i,a,l){p.reset(a-1);const f=p.next(s);return f?S(new o.Range(i,f.index+1,i,f.index+1+f[0].length),f,l):null}static findPreviousMatch(p,s,i,a){const l=s.parseSearchRequest();if(!l)return null;const f=new C(l.wordSeparators,l.regex);return l.regex.multiline?this._doFindPreviousMatchMultiline(p,i,f,a):this._doFindPreviousMatchLineByLine(p,i,f,a)}static _doFindPreviousMatchMultiline(p,s,i,a){const l=this._doFindMatchesMultiline(p,new o.Range(1,1,s.lineNumber,s.column),i,a,10*g);if(l.length>0)return l[l.length-1];const f=p.getLineCount();return s.lineNumber!==f||s.column!==p.getLineMaxColumn(f)?this._doFindPreviousMatchMultiline(p,new N.Position(f,p.getLineMaxColumn(f)),i,a):null}static _doFindPreviousMatchLineByLine(p,s,i,a){const l=p.getLineCount(),f=s.lineNumber,u=p.getLineContent(f).substring(0,s.column-1),_=this._findLastMatchInLine(i,u,f,a);if(_)return _;for(let b=1;b<=l;b++){const A=(l+f-b-1)%l,P=p.getLineContent(A+1),D=this._findLastMatchInLine(i,P,A+1,a);if(D)return D}return null}static _findLastMatchInLine(p,s,i,a){let l=null,f;for(p.reset(0);f=p.next(s);)l=S(new o.Range(i,f.index+1,i,f.index+1+f[0].length),f,a);return l}}r.TextModelSearch=d;function h(y,p,s,i,a){if(i===0)return!0;const l=p.charCodeAt(i-1);if(y.get(l)!==0||l===13||l===10)return!0;if(a>0){const f=p.charCodeAt(i);if(y.get(f)!==0)return!0}return!1}function v(y,p,s,i,a){if(i+a===s)return!0;const l=p.charCodeAt(i+a);if(y.get(l)!==0||l===13||l===10)return!0;if(a>0){const f=p.charCodeAt(i+a-1);if(y.get(f)!==0)return!0}return!1}function L(y,p,s,i,a){return h(y,p,s,i,a)&&v(y,p,s,i,a)}r.isValidMatch=L;class C{constructor(p,s){this._wordSeparators=p,this._searchRegex=s,this._prevMatchStartIndex=-1,this._prevMatchLength=0}reset(p){this._searchRegex.lastIndex=p,this._prevMatchStartIndex=-1,this._prevMatchLength=0}next(p){const s=p.length;let i;do{if(this._prevMatchStartIndex+this._prevMatchLength===s||(i=this._searchRegex.exec(p),!i))return null;const a=i.index,l=i[0].length;if(a===this._prevMatchStartIndex&&l===this._prevMatchLength){if(l===0){E.getNextCodePoint(p,s,this._searchRegex.lastIndex)>65535?this._searchRegex.lastIndex+=2:this._searchRegex.lastIndex+=1;continue}return null}if(this._prevMatchStartIndex=a,this._prevMatchLength=l,!this._wordSeparators||L(this._wordSeparators,p,s,a,l))return i}while(i);return null}}r.Searcher=C}),Y(Q[39],Z([0,1,4,38,2,7,15]),function(U,r,E,e,N,o,w){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.UnicodeTextModelHighlighter=void 0;class g{static computeUnicodeHighlights(d,h,v){const L=v?v.startLineNumber:1,C=v?v.endLineNumber:d.getLineCount(),y=new m(h),p=y.getCandidateCodePoints();let s;p==="allNonBasicAscii"?s=new RegExp("[^\\t\\n\\r\\x20-\\x7E]","g"):s=new RegExp(`${c(Array.from(p))}`,"g");const i=new e.Searcher(null,s),a=[];let l=!1,f,u=0,_=0,b=0;e:for(let A=L,P=C;A<=P;A++){const D=d.getLineContent(A),k=D.length;i.reset(0);do if(f=i.next(D),f){let R=f.index,I=f.index+f[0].length;if(R>0){const K=D.charCodeAt(R-1);N.isHighSurrogate(K)&&R--}if(I+1=K){l=!0;break e}a.push(new E.Range(A,R+1,A,I+1))}}while(f)}return{ranges:a,hasMore:l,ambiguousCharacterCount:u,invisibleCharacterCount:_,nonBasicAsciiCharacterCount:b}}static computeUnicodeHighlightReason(d,h){const v=new m(h);switch(v.shouldHighlightNonBasicASCII(d,null)){case 0:return null;case 2:return{kind:1};case 3:{const C=d.codePointAt(0),y=v.ambiguousCharacters.getPrimaryConfusable(C),p=N.AmbiguousCharacters.getLocales().filter(s=>!N.AmbiguousCharacters.getInstance(new Set([...h.allowedLocales,s])).isAmbiguous(C));return{kind:0,confusableWith:String.fromCodePoint(y),notAmbiguousInLocales:p}}case 1:return{kind:2}}}}r.UnicodeTextModelHighlighter=g;function c(t,d){return`[${N.escapeRegExpCharacters(t.map(v=>String.fromCodePoint(v)).join(""))}]`}class m{constructor(d){this.options=d,this.allowedCodePoints=new Set(d.allowedCodePoints),this.ambiguousCharacters=N.AmbiguousCharacters.getInstance(new Set(d.allowedLocales))}getCandidateCodePoints(){if(this.options.nonBasicASCII)return"allNonBasicAscii";const d=new Set;if(this.options.invisibleCharacters)for(const h of N.InvisibleCharacters.codePoints)S(String.fromCodePoint(h))||d.add(h);if(this.options.ambiguousCharacters)for(const h of this.ambiguousCharacters.getConfusableCodePoints())d.add(h);for(const h of this.allowedCodePoints)d.delete(h);return d}shouldHighlightNonBasicASCII(d,h){const v=d.codePointAt(0);if(this.allowedCodePoints.has(v))return 0;if(this.options.nonBasicASCII)return 1;let L=!1,C=!1;if(h)for(let y of h){const p=y.codePointAt(0),s=N.isBasicASCII(y);L=L||s,!s&&!this.ambiguousCharacters.isAmbiguous(p)&&!N.InvisibleCharacters.isInvisibleCharacter(p)&&(C=!0)}return!L&&C?0:this.options.invisibleCharacters&&!S(d)&&N.InvisibleCharacters.isInvisibleCharacter(v)?2:this.options.ambiguousCharacters&&this.ambiguousCharacters.isAmbiguous(v)?3:0}}function S(t){return t===" "||t===`
    +`||t==="	"}}),Y(Q[40],Z([0,1]),function(U,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.WrappingIndent=r.TrackedRangeStickiness=r.TextEditorCursorStyle=r.TextEditorCursorBlinkingStyle=r.SymbolTag=r.SymbolKind=r.SignatureHelpTriggerKind=r.SelectionDirection=r.ScrollbarVisibility=r.ScrollType=r.RenderMinimap=r.RenderLineNumbersType=r.PositionAffinity=r.OverviewRulerLane=r.OverlayWidgetPositionPreference=r.MouseTargetType=r.MinimapPosition=r.MarkerTag=r.MarkerSeverity=r.KeyCode=r.InlineCompletionTriggerKind=r.InlayHintKind=r.InjectedTextCursorStops=r.IndentAction=r.EndOfLineSequence=r.EndOfLinePreference=r.EditorOption=r.EditorAutoIndentStrategy=r.DocumentHighlightKind=r.DefaultEndOfLine=r.CursorChangeReason=r.ContentWidgetPositionPreference=r.CompletionTriggerKind=r.CompletionItemTag=r.CompletionItemKind=r.CompletionItemInsertTextRule=r.AccessibilitySupport=void 0;var E;(function(n){n[n.Unknown=0]="Unknown",n[n.Disabled=1]="Disabled",n[n.Enabled=2]="Enabled"})(E=r.AccessibilitySupport||(r.AccessibilitySupport={}));var e;(function(n){n[n.KeepWhitespace=1]="KeepWhitespace",n[n.InsertAsSnippet=4]="InsertAsSnippet"})(e=r.CompletionItemInsertTextRule||(r.CompletionItemInsertTextRule={}));var N;(function(n){n[n.Method=0]="Method",n[n.Function=1]="Function",n[n.Constructor=2]="Constructor",n[n.Field=3]="Field",n[n.Variable=4]="Variable",n[n.Class=5]="Class",n[n.Struct=6]="Struct",n[n.Interface=7]="Interface",n[n.Module=8]="Module",n[n.Property=9]="Property",n[n.Event=10]="Event",n[n.Operator=11]="Operator",n[n.Unit=12]="Unit",n[n.Value=13]="Value",n[n.Constant=14]="Constant",n[n.Enum=15]="Enum",n[n.EnumMember=16]="EnumMember",n[n.Keyword=17]="Keyword",n[n.Text=18]="Text",n[n.Color=19]="Color",n[n.File=20]="File",n[n.Reference=21]="Reference",n[n.Customcolor=22]="Customcolor",n[n.Folder=23]="Folder",n[n.TypeParameter=24]="TypeParameter",n[n.User=25]="User",n[n.Issue=26]="Issue",n[n.Snippet=27]="Snippet"})(N=r.CompletionItemKind||(r.CompletionItemKind={}));var o;(function(n){n[n.Deprecated=1]="Deprecated"})(o=r.CompletionItemTag||(r.CompletionItemTag={}));var w;(function(n){n[n.Invoke=0]="Invoke",n[n.TriggerCharacter=1]="TriggerCharacter",n[n.TriggerForIncompleteCompletions=2]="TriggerForIncompleteCompletions"})(w=r.CompletionTriggerKind||(r.CompletionTriggerKind={}));var g;(function(n){n[n.EXACT=0]="EXACT",n[n.ABOVE=1]="ABOVE",n[n.BELOW=2]="BELOW"})(g=r.ContentWidgetPositionPreference||(r.ContentWidgetPositionPreference={}));var c;(function(n){n[n.NotSet=0]="NotSet",n[n.ContentFlush=1]="ContentFlush",n[n.RecoverFromMarkers=2]="RecoverFromMarkers",n[n.Explicit=3]="Explicit",n[n.Paste=4]="Paste",n[n.Undo=5]="Undo",n[n.Redo=6]="Redo"})(c=r.CursorChangeReason||(r.CursorChangeReason={}));var m;(function(n){n[n.LF=1]="LF",n[n.CRLF=2]="CRLF"})(m=r.DefaultEndOfLine||(r.DefaultEndOfLine={}));var S;(function(n){n[n.Text=0]="Text",n[n.Read=1]="Read",n[n.Write=2]="Write"})(S=r.DocumentHighlightKind||(r.DocumentHighlightKind={}));var t;(function(n){n[n.None=0]="None",n[n.Keep=1]="Keep",n[n.Brackets=2]="Brackets",n[n.Advanced=3]="Advanced",n[n.Full=4]="Full"})(t=r.EditorAutoIndentStrategy||(r.EditorAutoIndentStrategy={}));var d;(function(n){n[n.acceptSuggestionOnCommitCharacter=0]="acceptSuggestionOnCommitCharacter",n[n.acceptSuggestionOnEnter=1]="acceptSuggestionOnEnter",n[n.accessibilitySupport=2]="accessibilitySupport",n[n.accessibilityPageSize=3]="accessibilityPageSize",n[n.ariaLabel=4]="ariaLabel",n[n.autoClosingBrackets=5]="autoClosingBrackets",n[n.autoClosingDelete=6]="autoClosingDelete",n[n.autoClosingOvertype=7]="autoClosingOvertype",n[n.autoClosingQuotes=8]="autoClosingQuotes",n[n.autoIndent=9]="autoIndent",n[n.automaticLayout=10]="automaticLayout",n[n.autoSurround=11]="autoSurround",n[n.bracketPairColorization=12]="bracketPairColorization",n[n.guides=13]="guides",n[n.codeLens=14]="codeLens",n[n.codeLensFontFamily=15]="codeLensFontFamily",n[n.codeLensFontSize=16]="codeLensFontSize",n[n.colorDecorators=17]="colorDecorators",n[n.columnSelection=18]="columnSelection",n[n.comments=19]="comments",n[n.contextmenu=20]="contextmenu",n[n.copyWithSyntaxHighlighting=21]="copyWithSyntaxHighlighting",n[n.cursorBlinking=22]="cursorBlinking",n[n.cursorSmoothCaretAnimation=23]="cursorSmoothCaretAnimation",n[n.cursorStyle=24]="cursorStyle",n[n.cursorSurroundingLines=25]="cursorSurroundingLines",n[n.cursorSurroundingLinesStyle=26]="cursorSurroundingLinesStyle",n[n.cursorWidth=27]="cursorWidth",n[n.disableLayerHinting=28]="disableLayerHinting",n[n.disableMonospaceOptimizations=29]="disableMonospaceOptimizations",n[n.domReadOnly=30]="domReadOnly",n[n.dragAndDrop=31]="dragAndDrop",n[n.emptySelectionClipboard=32]="emptySelectionClipboard",n[n.extraEditorClassName=33]="extraEditorClassName",n[n.fastScrollSensitivity=34]="fastScrollSensitivity",n[n.find=35]="find",n[n.fixedOverflowWidgets=36]="fixedOverflowWidgets",n[n.folding=37]="folding",n[n.foldingStrategy=38]="foldingStrategy",n[n.foldingHighlight=39]="foldingHighlight",n[n.foldingImportsByDefault=40]="foldingImportsByDefault",n[n.foldingMaximumRegions=41]="foldingMaximumRegions",n[n.unfoldOnClickAfterEndOfLine=42]="unfoldOnClickAfterEndOfLine",n[n.fontFamily=43]="fontFamily",n[n.fontInfo=44]="fontInfo",n[n.fontLigatures=45]="fontLigatures",n[n.fontSize=46]="fontSize",n[n.fontWeight=47]="fontWeight",n[n.formatOnPaste=48]="formatOnPaste",n[n.formatOnType=49]="formatOnType",n[n.glyphMargin=50]="glyphMargin",n[n.gotoLocation=51]="gotoLocation",n[n.hideCursorInOverviewRuler=52]="hideCursorInOverviewRuler",n[n.hover=53]="hover",n[n.inDiffEditor=54]="inDiffEditor",n[n.inlineSuggest=55]="inlineSuggest",n[n.letterSpacing=56]="letterSpacing",n[n.lightbulb=57]="lightbulb",n[n.lineDecorationsWidth=58]="lineDecorationsWidth",n[n.lineHeight=59]="lineHeight",n[n.lineNumbers=60]="lineNumbers",n[n.lineNumbersMinChars=61]="lineNumbersMinChars",n[n.linkedEditing=62]="linkedEditing",n[n.links=63]="links",n[n.matchBrackets=64]="matchBrackets",n[n.minimap=65]="minimap",n[n.mouseStyle=66]="mouseStyle",n[n.mouseWheelScrollSensitivity=67]="mouseWheelScrollSensitivity",n[n.mouseWheelZoom=68]="mouseWheelZoom",n[n.multiCursorMergeOverlapping=69]="multiCursorMergeOverlapping",n[n.multiCursorModifier=70]="multiCursorModifier",n[n.multiCursorPaste=71]="multiCursorPaste",n[n.occurrencesHighlight=72]="occurrencesHighlight",n[n.overviewRulerBorder=73]="overviewRulerBorder",n[n.overviewRulerLanes=74]="overviewRulerLanes",n[n.padding=75]="padding",n[n.parameterHints=76]="parameterHints",n[n.peekWidgetDefaultFocus=77]="peekWidgetDefaultFocus",n[n.definitionLinkOpensInPeek=78]="definitionLinkOpensInPeek",n[n.quickSuggestions=79]="quickSuggestions",n[n.quickSuggestionsDelay=80]="quickSuggestionsDelay",n[n.readOnly=81]="readOnly",n[n.renameOnType=82]="renameOnType",n[n.renderControlCharacters=83]="renderControlCharacters",n[n.renderFinalNewline=84]="renderFinalNewline",n[n.renderLineHighlight=85]="renderLineHighlight",n[n.renderLineHighlightOnlyWhenFocus=86]="renderLineHighlightOnlyWhenFocus",n[n.renderValidationDecorations=87]="renderValidationDecorations",n[n.renderWhitespace=88]="renderWhitespace",n[n.revealHorizontalRightPadding=89]="revealHorizontalRightPadding",n[n.roundedSelection=90]="roundedSelection",n[n.rulers=91]="rulers",n[n.scrollbar=92]="scrollbar",n[n.scrollBeyondLastColumn=93]="scrollBeyondLastColumn",n[n.scrollBeyondLastLine=94]="scrollBeyondLastLine",n[n.scrollPredominantAxis=95]="scrollPredominantAxis",n[n.selectionClipboard=96]="selectionClipboard",n[n.selectionHighlight=97]="selectionHighlight",n[n.selectOnLineNumbers=98]="selectOnLineNumbers",n[n.showFoldingControls=99]="showFoldingControls",n[n.showUnused=100]="showUnused",n[n.snippetSuggestions=101]="snippetSuggestions",n[n.smartSelect=102]="smartSelect",n[n.smoothScrolling=103]="smoothScrolling",n[n.stickyTabStops=104]="stickyTabStops",n[n.stopRenderingLineAfter=105]="stopRenderingLineAfter",n[n.suggest=106]="suggest",n[n.suggestFontSize=107]="suggestFontSize",n[n.suggestLineHeight=108]="suggestLineHeight",n[n.suggestOnTriggerCharacters=109]="suggestOnTriggerCharacters",n[n.suggestSelection=110]="suggestSelection",n[n.tabCompletion=111]="tabCompletion",n[n.tabIndex=112]="tabIndex",n[n.unicodeHighlighting=113]="unicodeHighlighting",n[n.unusualLineTerminators=114]="unusualLineTerminators",n[n.useShadowDOM=115]="useShadowDOM",n[n.useTabStops=116]="useTabStops",n[n.wordSeparators=117]="wordSeparators",n[n.wordWrap=118]="wordWrap",n[n.wordWrapBreakAfterCharacters=119]="wordWrapBreakAfterCharacters",n[n.wordWrapBreakBeforeCharacters=120]="wordWrapBreakBeforeCharacters",n[n.wordWrapColumn=121]="wordWrapColumn",n[n.wordWrapOverride1=122]="wordWrapOverride1",n[n.wordWrapOverride2=123]="wordWrapOverride2",n[n.wrappingIndent=124]="wrappingIndent",n[n.wrappingStrategy=125]="wrappingStrategy",n[n.showDeprecated=126]="showDeprecated",n[n.inlayHints=127]="inlayHints",n[n.editorClassName=128]="editorClassName",n[n.pixelRatio=129]="pixelRatio",n[n.tabFocusMode=130]="tabFocusMode",n[n.layoutInfo=131]="layoutInfo",n[n.wrappingInfo=132]="wrappingInfo"})(d=r.EditorOption||(r.EditorOption={}));var h;(function(n){n[n.TextDefined=0]="TextDefined",n[n.LF=1]="LF",n[n.CRLF=2]="CRLF"})(h=r.EndOfLinePreference||(r.EndOfLinePreference={}));var v;(function(n){n[n.LF=0]="LF",n[n.CRLF=1]="CRLF"})(v=r.EndOfLineSequence||(r.EndOfLineSequence={}));var L;(function(n){n[n.None=0]="None",n[n.Indent=1]="Indent",n[n.IndentOutdent=2]="IndentOutdent",n[n.Outdent=3]="Outdent"})(L=r.IndentAction||(r.IndentAction={}));var C;(function(n){n[n.Both=0]="Both",n[n.Right=1]="Right",n[n.Left=2]="Left",n[n.None=3]="None"})(C=r.InjectedTextCursorStops||(r.InjectedTextCursorStops={}));var y;(function(n){n[n.Type=1]="Type",n[n.Parameter=2]="Parameter"})(y=r.InlayHintKind||(r.InlayHintKind={}));var p;(function(n){n[n.Automatic=0]="Automatic",n[n.Explicit=1]="Explicit"})(p=r.InlineCompletionTriggerKind||(r.InlineCompletionTriggerKind={}));var s;(function(n){n[n.DependsOnKbLayout=-1]="DependsOnKbLayout",n[n.Unknown=0]="Unknown",n[n.Backspace=1]="Backspace",n[n.Tab=2]="Tab",n[n.Enter=3]="Enter",n[n.Shift=4]="Shift",n[n.Ctrl=5]="Ctrl",n[n.Alt=6]="Alt",n[n.PauseBreak=7]="PauseBreak",n[n.CapsLock=8]="CapsLock",n[n.Escape=9]="Escape",n[n.Space=10]="Space",n[n.PageUp=11]="PageUp",n[n.PageDown=12]="PageDown",n[n.End=13]="End",n[n.Home=14]="Home",n[n.LeftArrow=15]="LeftArrow",n[n.UpArrow=16]="UpArrow",n[n.RightArrow=17]="RightArrow",n[n.DownArrow=18]="DownArrow",n[n.Insert=19]="Insert",n[n.Delete=20]="Delete",n[n.Digit0=21]="Digit0",n[n.Digit1=22]="Digit1",n[n.Digit2=23]="Digit2",n[n.Digit3=24]="Digit3",n[n.Digit4=25]="Digit4",n[n.Digit5=26]="Digit5",n[n.Digit6=27]="Digit6",n[n.Digit7=28]="Digit7",n[n.Digit8=29]="Digit8",n[n.Digit9=30]="Digit9",n[n.KeyA=31]="KeyA",n[n.KeyB=32]="KeyB",n[n.KeyC=33]="KeyC",n[n.KeyD=34]="KeyD",n[n.KeyE=35]="KeyE",n[n.KeyF=36]="KeyF",n[n.KeyG=37]="KeyG",n[n.KeyH=38]="KeyH",n[n.KeyI=39]="KeyI",n[n.KeyJ=40]="KeyJ",n[n.KeyK=41]="KeyK",n[n.KeyL=42]="KeyL",n[n.KeyM=43]="KeyM",n[n.KeyN=44]="KeyN",n[n.KeyO=45]="KeyO",n[n.KeyP=46]="KeyP",n[n.KeyQ=47]="KeyQ",n[n.KeyR=48]="KeyR",n[n.KeyS=49]="KeyS",n[n.KeyT=50]="KeyT",n[n.KeyU=51]="KeyU",n[n.KeyV=52]="KeyV",n[n.KeyW=53]="KeyW",n[n.KeyX=54]="KeyX",n[n.KeyY=55]="KeyY",n[n.KeyZ=56]="KeyZ",n[n.Meta=57]="Meta",n[n.ContextMenu=58]="ContextMenu",n[n.F1=59]="F1",n[n.F2=60]="F2",n[n.F3=61]="F3",n[n.F4=62]="F4",n[n.F5=63]="F5",n[n.F6=64]="F6",n[n.F7=65]="F7",n[n.F8=66]="F8",n[n.F9=67]="F9",n[n.F10=68]="F10",n[n.F11=69]="F11",n[n.F12=70]="F12",n[n.F13=71]="F13",n[n.F14=72]="F14",n[n.F15=73]="F15",n[n.F16=74]="F16",n[n.F17=75]="F17",n[n.F18=76]="F18",n[n.F19=77]="F19",n[n.NumLock=78]="NumLock",n[n.ScrollLock=79]="ScrollLock",n[n.Semicolon=80]="Semicolon",n[n.Equal=81]="Equal",n[n.Comma=82]="Comma",n[n.Minus=83]="Minus",n[n.Period=84]="Period",n[n.Slash=85]="Slash",n[n.Backquote=86]="Backquote",n[n.BracketLeft=87]="BracketLeft",n[n.Backslash=88]="Backslash",n[n.BracketRight=89]="BracketRight",n[n.Quote=90]="Quote",n[n.OEM_8=91]="OEM_8",n[n.IntlBackslash=92]="IntlBackslash",n[n.Numpad0=93]="Numpad0",n[n.Numpad1=94]="Numpad1",n[n.Numpad2=95]="Numpad2",n[n.Numpad3=96]="Numpad3",n[n.Numpad4=97]="Numpad4",n[n.Numpad5=98]="Numpad5",n[n.Numpad6=99]="Numpad6",n[n.Numpad7=100]="Numpad7",n[n.Numpad8=101]="Numpad8",n[n.Numpad9=102]="Numpad9",n[n.NumpadMultiply=103]="NumpadMultiply",n[n.NumpadAdd=104]="NumpadAdd",n[n.NUMPAD_SEPARATOR=105]="NUMPAD_SEPARATOR",n[n.NumpadSubtract=106]="NumpadSubtract",n[n.NumpadDecimal=107]="NumpadDecimal",n[n.NumpadDivide=108]="NumpadDivide",n[n.KEY_IN_COMPOSITION=109]="KEY_IN_COMPOSITION",n[n.ABNT_C1=110]="ABNT_C1",n[n.ABNT_C2=111]="ABNT_C2",n[n.AudioVolumeMute=112]="AudioVolumeMute",n[n.AudioVolumeUp=113]="AudioVolumeUp",n[n.AudioVolumeDown=114]="AudioVolumeDown",n[n.BrowserSearch=115]="BrowserSearch",n[n.BrowserHome=116]="BrowserHome",n[n.BrowserBack=117]="BrowserBack",n[n.BrowserForward=118]="BrowserForward",n[n.MediaTrackNext=119]="MediaTrackNext",n[n.MediaTrackPrevious=120]="MediaTrackPrevious",n[n.MediaStop=121]="MediaStop",n[n.MediaPlayPause=122]="MediaPlayPause",n[n.LaunchMediaPlayer=123]="LaunchMediaPlayer",n[n.LaunchMail=124]="LaunchMail",n[n.LaunchApp2=125]="LaunchApp2",n[n.Clear=126]="Clear",n[n.MAX_VALUE=127]="MAX_VALUE"})(s=r.KeyCode||(r.KeyCode={}));var i;(function(n){n[n.Hint=1]="Hint",n[n.Info=2]="Info",n[n.Warning=4]="Warning",n[n.Error=8]="Error"})(i=r.MarkerSeverity||(r.MarkerSeverity={}));var a;(function(n){n[n.Unnecessary=1]="Unnecessary",n[n.Deprecated=2]="Deprecated"})(a=r.MarkerTag||(r.MarkerTag={}));var l;(function(n){n[n.Inline=1]="Inline",n[n.Gutter=2]="Gutter"})(l=r.MinimapPosition||(r.MinimapPosition={}));var f;(function(n){n[n.UNKNOWN=0]="UNKNOWN",n[n.TEXTAREA=1]="TEXTAREA",n[n.GUTTER_GLYPH_MARGIN=2]="GUTTER_GLYPH_MARGIN",n[n.GUTTER_LINE_NUMBERS=3]="GUTTER_LINE_NUMBERS",n[n.GUTTER_LINE_DECORATIONS=4]="GUTTER_LINE_DECORATIONS",n[n.GUTTER_VIEW_ZONE=5]="GUTTER_VIEW_ZONE",n[n.CONTENT_TEXT=6]="CONTENT_TEXT",n[n.CONTENT_EMPTY=7]="CONTENT_EMPTY",n[n.CONTENT_VIEW_ZONE=8]="CONTENT_VIEW_ZONE",n[n.CONTENT_WIDGET=9]="CONTENT_WIDGET",n[n.OVERVIEW_RULER=10]="OVERVIEW_RULER",n[n.SCROLLBAR=11]="SCROLLBAR",n[n.OVERLAY_WIDGET=12]="OVERLAY_WIDGET",n[n.OUTSIDE_EDITOR=13]="OUTSIDE_EDITOR"})(f=r.MouseTargetType||(r.MouseTargetType={}));var u;(function(n){n[n.TOP_RIGHT_CORNER=0]="TOP_RIGHT_CORNER",n[n.BOTTOM_RIGHT_CORNER=1]="BOTTOM_RIGHT_CORNER",n[n.TOP_CENTER=2]="TOP_CENTER"})(u=r.OverlayWidgetPositionPreference||(r.OverlayWidgetPositionPreference={}));var _;(function(n){n[n.Left=1]="Left",n[n.Center=2]="Center",n[n.Right=4]="Right",n[n.Full=7]="Full"})(_=r.OverviewRulerLane||(r.OverviewRulerLane={}));var b;(function(n){n[n.Left=0]="Left",n[n.Right=1]="Right",n[n.None=2]="None"})(b=r.PositionAffinity||(r.PositionAffinity={}));var A;(function(n){n[n.Off=0]="Off",n[n.On=1]="On",n[n.Relative=2]="Relative",n[n.Interval=3]="Interval",n[n.Custom=4]="Custom"})(A=r.RenderLineNumbersType||(r.RenderLineNumbersType={}));var P;(function(n){n[n.None=0]="None",n[n.Text=1]="Text",n[n.Blocks=2]="Blocks"})(P=r.RenderMinimap||(r.RenderMinimap={}));var D;(function(n){n[n.Smooth=0]="Smooth",n[n.Immediate=1]="Immediate"})(D=r.ScrollType||(r.ScrollType={}));var k;(function(n){n[n.Auto=1]="Auto",n[n.Hidden=2]="Hidden",n[n.Visible=3]="Visible"})(k=r.ScrollbarVisibility||(r.ScrollbarVisibility={}));var R;(function(n){n[n.LTR=0]="LTR",n[n.RTL=1]="RTL"})(R=r.SelectionDirection||(r.SelectionDirection={}));var I;(function(n){n[n.Invoke=1]="Invoke",n[n.TriggerCharacter=2]="TriggerCharacter",n[n.ContentChange=3]="ContentChange"})(I=r.SignatureHelpTriggerKind||(r.SignatureHelpTriggerKind={}));var F;(function(n){n[n.File=0]="File",n[n.Module=1]="Module",n[n.Namespace=2]="Namespace",n[n.Package=3]="Package",n[n.Class=4]="Class",n[n.Method=5]="Method",n[n.Property=6]="Property",n[n.Field=7]="Field",n[n.Constructor=8]="Constructor",n[n.Enum=9]="Enum",n[n.Interface=10]="Interface",n[n.Function=11]="Function",n[n.Variable=12]="Variable",n[n.Constant=13]="Constant",n[n.String=14]="String",n[n.Number=15]="Number",n[n.Boolean=16]="Boolean",n[n.Array=17]="Array",n[n.Object=18]="Object",n[n.Key=19]="Key",n[n.Null=20]="Null",n[n.EnumMember=21]="EnumMember",n[n.Struct=22]="Struct",n[n.Event=23]="Event",n[n.Operator=24]="Operator",n[n.TypeParameter=25]="TypeParameter"})(F=r.SymbolKind||(r.SymbolKind={}));var O;(function(n){n[n.Deprecated=1]="Deprecated"})(O=r.SymbolTag||(r.SymbolTag={}));var V;(function(n){n[n.Hidden=0]="Hidden",n[n.Blink=1]="Blink",n[n.Smooth=2]="Smooth",n[n.Phase=3]="Phase",n[n.Expand=4]="Expand",n[n.Solid=5]="Solid"})(V=r.TextEditorCursorBlinkingStyle||(r.TextEditorCursorBlinkingStyle={}));var K;(function(n){n[n.Line=1]="Line",n[n.Block=2]="Block",n[n.Underline=3]="Underline",n[n.LineThin=4]="LineThin",n[n.BlockOutline=5]="BlockOutline",n[n.UnderlineThin=6]="UnderlineThin"})(K=r.TextEditorCursorStyle||(r.TextEditorCursorStyle={}));var $;(function(n){n[n.AlwaysGrowsWhenTypingAtEdges=0]="AlwaysGrowsWhenTypingAtEdges",n[n.NeverGrowsWhenTypingAtEdges=1]="NeverGrowsWhenTypingAtEdges",n[n.GrowsOnlyWhenTypingBefore=2]="GrowsOnlyWhenTypingBefore",n[n.GrowsOnlyWhenTypingAfter=3]="GrowsOnlyWhenTypingAfter"})($=r.TrackedRangeStickiness||(r.TrackedRangeStickiness={}));var z;(function(n){n[n.None=0]="None",n[n.Same=1]="Same",n[n.Indent=2]="Indent",n[n.DeepIndent=3]="DeepIndent"})(z=r.WrappingIndent||(r.WrappingIndent={}))});var oe=this&&this.__awaiter||function(U,r,E,e){function N(o){return o instanceof E?o:new E(function(w){w(o)})}return new(E||(E=Promise))(function(o,w){function g(S){try{m(e.next(S))}catch(t){w(t)}}function c(S){try{m(e.throw(S))}catch(t){w(t)}}function m(S){S.done?o(S.value):N(S.value).then(g,c)}m((e=e.apply(U,r||[])).next())})};Y(Q[41],Z([0,1,6,8]),function(U,r,E,e){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.TokenizationRegistry=void 0;class N{constructor(){this._map=new Map,this._factories=new Map,this._onDidChange=new E.Emitter,this.onDidChange=this._onDidChange.event,this._colorMap=null}fire(g){this._onDidChange.fire({changedLanguages:g,changedColorMap:!1})}register(g,c){return this._map.set(g,c),this.fire([g]),(0,e.toDisposable)(()=>{this._map.get(g)===c&&(this._map.delete(g),this.fire([g]))})}registerFactory(g,c){var m;(m=this._factories.get(g))===null||m===void 0||m.dispose();const S=new o(this,g,c);return this._factories.set(g,S),(0,e.toDisposable)(()=>{const t=this._factories.get(g);!t||t!==S||(this._factories.delete(g),t.dispose())})}getOrCreate(g){return oe(this,void 0,void 0,function*(){const c=this.get(g);if(c)return c;const m=this._factories.get(g);return!m||m.isResolved?null:(yield m.resolve(),this.get(g))})}get(g){return this._map.get(g)||null}isResolved(g){if(this.get(g))return!0;const m=this._factories.get(g);return!!(!m||m.isResolved)}setColorMap(g){this._colorMap=g,this._onDidChange.fire({changedLanguages:Array.from(this._map.keys()),changedColorMap:!0})}getColorMap(){return this._colorMap}getDefaultBackground(){return this._colorMap&&this._colorMap.length>2?this._colorMap[2]:null}}r.TokenizationRegistry=N;class o extends e.Disposable{constructor(g,c,m){super();this._registry=g,this._languageId=c,this._factory=m,this._isDisposed=!1,this._resolvePromise=null,this._isResolved=!1}get isResolved(){return this._isResolved}dispose(){this._isDisposed=!0,super.dispose()}resolve(){return oe(this,void 0,void 0,function*(){return this._resolvePromise||(this._resolvePromise=this._create()),this._resolvePromise})}_create(){return oe(this,void 0,void 0,function*(){const g=yield Promise.resolve(this._factory.createTokenizationSupport());this._isResolved=!0,g&&!this._isDisposed&&this._register(this._registry.register(this._languageId,g))})}}}),Y(Q[42],Z([0,1,9,4,41,18]),function(U,r,E,e,N,o){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.TokenizationRegistry=r.InlayHintKind=r.Command=r.FoldingRangeKind=r.SymbolKinds=r.isLocationLink=r.DocumentHighlightKind=r.SignatureHelpTriggerKind=r.InlineCompletionTriggerKind=r.CompletionItemKinds=r.EncodedTokenizationResult=r.TokenizationResult=r.Token=r.TokenMetadata=void 0;class w{static getLanguageId(i){return(i&255)>>>0}static getTokenType(i){return(i&768)>>>8}static getFontStyle(i){return(i&15360)>>>10}static getForeground(i){return(i&8372224)>>>14}static getBackground(i){return(i&4286578688)>>>23}static getClassNameFromMetadata(i){const a=this.getForeground(i);let l="mtk"+a;const f=this.getFontStyle(i);return f&1&&(l+=" mtki"),f&2&&(l+=" mtkb"),f&4&&(l+=" mtku"),f&8&&(l+=" mtks"),l}static getInlineStyleFromMetadata(i,a){const l=this.getForeground(i),f=this.getFontStyle(i);let u=`color: ${a[l]};`;f&1&&(u+="font-style: italic;"),f&2&&(u+="font-weight: bold;");let _="";return f&4&&(_+=" underline"),f&8&&(_+=" line-through"),_&&(u+=`text-decoration:${_};`),u}static getPresentationFromMetadata(i){const a=this.getForeground(i),l=this.getFontStyle(i);return{foreground:a,italic:Boolean(l&1),bold:Boolean(l&2),underline:Boolean(l&4),strikethrough:Boolean(l&8)}}}r.TokenMetadata=w;class g{constructor(i,a,l){this._tokenBrand=void 0,this.offset=i,this.type=a,this.language=l}toString(){return"("+this.offset+", "+this.type+")"}}r.Token=g;class c{constructor(i,a){this._tokenizationResultBrand=void 0,this.tokens=i,this.endState=a}}r.TokenizationResult=c;class m{constructor(i,a){this._encodedTokenizationResultBrand=void 0,this.tokens=i,this.endState=a}}r.EncodedTokenizationResult=m;var S;(function(s){const i=new Map;i.set(0,o.Codicon.symbolMethod),i.set(1,o.Codicon.symbolFunction),i.set(2,o.Codicon.symbolConstructor),i.set(3,o.Codicon.symbolField),i.set(4,o.Codicon.symbolVariable),i.set(5,o.Codicon.symbolClass),i.set(6,o.Codicon.symbolStruct),i.set(7,o.Codicon.symbolInterface),i.set(8,o.Codicon.symbolModule),i.set(9,o.Codicon.symbolProperty),i.set(10,o.Codicon.symbolEvent),i.set(11,o.Codicon.symbolOperator),i.set(12,o.Codicon.symbolUnit),i.set(13,o.Codicon.symbolValue),i.set(15,o.Codicon.symbolEnum),i.set(14,o.Codicon.symbolConstant),i.set(15,o.Codicon.symbolEnum),i.set(16,o.Codicon.symbolEnumMember),i.set(17,o.Codicon.symbolKeyword),i.set(27,o.Codicon.symbolSnippet),i.set(18,o.Codicon.symbolText),i.set(19,o.Codicon.symbolColor),i.set(20,o.Codicon.symbolFile),i.set(21,o.Codicon.symbolReference),i.set(22,o.Codicon.symbolCustomColor),i.set(23,o.Codicon.symbolFolder),i.set(24,o.Codicon.symbolTypeParameter),i.set(25,o.Codicon.account),i.set(26,o.Codicon.issues);function a(u){let _=i.get(u);return _||(console.info("No codicon found for CompletionItemKind "+u),_=o.Codicon.symbolProperty),_}s.toIcon=a;const l=new Map;l.set("method",0),l.set("function",1),l.set("constructor",2),l.set("field",3),l.set("variable",4),l.set("class",5),l.set("struct",6),l.set("interface",7),l.set("module",8),l.set("property",9),l.set("event",10),l.set("operator",11),l.set("unit",12),l.set("value",13),l.set("constant",14),l.set("enum",15),l.set("enum-member",16),l.set("enumMember",16),l.set("keyword",17),l.set("snippet",27),l.set("text",18),l.set("color",19),l.set("file",20),l.set("reference",21),l.set("customcolor",22),l.set("folder",23),l.set("type-parameter",24),l.set("typeParameter",24),l.set("account",25),l.set("issue",26);function f(u,_){let b=l.get(u);return typeof b=="undefined"&&!_&&(b=9),b}s.fromString=f})(S=r.CompletionItemKinds||(r.CompletionItemKinds={}));var t;(function(s){s[s.Automatic=0]="Automatic",s[s.Explicit=1]="Explicit"})(t=r.InlineCompletionTriggerKind||(r.InlineCompletionTriggerKind={}));var d;(function(s){s[s.Invoke=1]="Invoke",s[s.TriggerCharacter=2]="TriggerCharacter",s[s.ContentChange=3]="ContentChange"})(d=r.SignatureHelpTriggerKind||(r.SignatureHelpTriggerKind={}));var h;(function(s){s[s.Text=0]="Text",s[s.Read=1]="Read",s[s.Write=2]="Write"})(h=r.DocumentHighlightKind||(r.DocumentHighlightKind={}));function v(s){return s&&E.URI.isUri(s.uri)&&e.Range.isIRange(s.range)&&(e.Range.isIRange(s.originSelectionRange)||e.Range.isIRange(s.targetSelectionRange))}r.isLocationLink=v;var L;(function(s){const i=new Map;i.set(0,o.Codicon.symbolFile),i.set(1,o.Codicon.symbolModule),i.set(2,o.Codicon.symbolNamespace),i.set(3,o.Codicon.symbolPackage),i.set(4,o.Codicon.symbolClass),i.set(5,o.Codicon.symbolMethod),i.set(6,o.Codicon.symbolProperty),i.set(7,o.Codicon.symbolField),i.set(8,o.Codicon.symbolConstructor),i.set(9,o.Codicon.symbolEnum),i.set(10,o.Codicon.symbolInterface),i.set(11,o.Codicon.symbolFunction),i.set(12,o.Codicon.symbolVariable),i.set(13,o.Codicon.symbolConstant),i.set(14,o.Codicon.symbolString),i.set(15,o.Codicon.symbolNumber),i.set(16,o.Codicon.symbolBoolean),i.set(17,o.Codicon.symbolArray),i.set(18,o.Codicon.symbolObject),i.set(19,o.Codicon.symbolKey),i.set(20,o.Codicon.symbolNull),i.set(21,o.Codicon.symbolEnumMember),i.set(22,o.Codicon.symbolStruct),i.set(23,o.Codicon.symbolEvent),i.set(24,o.Codicon.symbolOperator),i.set(25,o.Codicon.symbolTypeParameter);function a(l){let f=i.get(l);return f||(console.info("No codicon found for SymbolKind "+l),f=o.Codicon.symbolProperty),f}s.toIcon=a})(L=r.SymbolKinds||(r.SymbolKinds={}));class C{constructor(i){this.value=i}}r.FoldingRangeKind=C,C.Comment=new C("comment"),C.Imports=new C("imports"),C.Region=new C("region");var y;(function(s){function i(a){return!a||typeof a!="object"?!1:typeof a.id=="string"&&typeof a.title=="string"}s.is=i})(y=r.Command||(r.Command={}));var p;(function(s){s[s.Type=1]="Type",s[s.Parameter=2]="Parameter"})(p=r.InlayHintKind||(r.InlayHintKind={})),r.TokenizationRegistry=new N.TokenizationRegistry}),Y(Q[43],Z([0,1,27,6,22,9,3,4,30,42,40]),function(U,r,E,e,N,o,w,g,c,m,S){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.createMonacoBaseAPI=r.KeyMod=void 0;class t{static chord(v,L){return(0,N.KeyChord)(v,L)}}r.KeyMod=t,t.CtrlCmd=2048,t.Shift=1024,t.Alt=512,t.WinCtrl=256;function d(){return{editor:void 0,languages:void 0,CancellationTokenSource:E.CancellationTokenSource,Emitter:e.Emitter,KeyCode:S.KeyCode,KeyMod:t,Position:w.Position,Range:g.Range,Selection:c.Selection,SelectionDirection:S.SelectionDirection,MarkerSeverity:S.MarkerSeverity,MarkerTag:S.MarkerTag,Uri:o.URI,Token:m.Token}}r.createMonacoBaseAPI=d}),Y(Q[45],Z([0,1,12,5,9,3,4,32,37,15,33,34,43,7,11,39]),function(U,r,E,e,N,o,w,g,c,m,S,t,d,h,v,L){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.create=r.EditorSimpleWorker=r.MirrorModel=void 0;class C extends c.MirrorTextModel{get uri(){return this._uri}get eol(){return this._eol}getValue(){return this.getText()}getLinesContent(){return this._lines.slice(0)}getLineCount(){return this._lines.length}getLineContent(i){return this._lines[i-1]}getWordAtPosition(i,a){const l=(0,m.getWordAtText)(i.column,(0,m.ensureValidWordDefinition)(a),this._lines[i.lineNumber-1],0);return l?new w.Range(i.lineNumber,l.startColumn,i.lineNumber,l.endColumn):null}words(i){const a=this._lines,l=this._wordenize.bind(this);let f=0,u="",_=0,b=[];return{*[Symbol.iterator](){for(;;)if(_this._lines.length)a=this._lines.length,l=this._lines[a-1].length+1,f=!0;else{const u=this._lines[a-1].length+1;l<1?(l=1,f=!0):l>u&&(l=u,f=!0)}return f?{lineNumber:a,column:l}:i}}r.MirrorModel=C;class y{constructor(i,a){this._host=i,this._models=Object.create(null),this._foreignModuleFactory=a,this._foreignModule=null}dispose(){this._models=Object.create(null)}_getModel(i){return this._models[i]}_getModels(){const i=[];return Object.keys(this._models).forEach(a=>i.push(this._models[a])),i}acceptNewModel(i){this._models[i.url]=new C(N.URI.parse(i.url),i.lines,i.EOL,i.versionId)}acceptModelChanged(i,a){if(!this._models[i])return;this._models[i].onEvents(a)}acceptRemovedModel(i){!this._models[i]||delete this._models[i]}computeUnicodeHighlights(i,a,l){return oe(this,void 0,void 0,function*(){const f=this._getModel(i);return f?L.UnicodeTextModelHighlighter.computeUnicodeHighlights(f,a,l):{ranges:[],hasMore:!1,ambiguousCharacterCount:0,invisibleCharacterCount:0,nonBasicAsciiCharacterCount:0}})}computeDiff(i,a,l,f){return oe(this,void 0,void 0,function*(){const u=this._getModel(i),_=this._getModel(a);if(!u||!_)return null;const b=u.getLinesContent(),A=_.getLinesContent(),D=new g.DiffComputer(b,A,{shouldComputeCharChanges:!0,shouldPostProcessCharChanges:!0,shouldIgnoreTrimWhitespace:l,shouldMakePrettyDiff:!0,maxComputationTime:f}).computeDiff(),k=D.changes.length>0?!1:this._modelsAreIdentical(u,_);return{quitEarly:D.quitEarly,identical:k,changes:D.changes}})}_modelsAreIdentical(i,a){const l=i.getLineCount(),f=a.getLineCount();if(l!==f)return!1;for(let u=1;u<=l;u++){const _=i.getLineContent(u),b=a.getLineContent(u);if(_!==b)return!1}return!0}computeMoreMinimalEdits(i,a){return oe(this,void 0,void 0,function*(){const l=this._getModel(i);if(!l)return a;const f=[];let u;a=a.slice(0).sort((_,b)=>{if(_.range&&b.range)return w.Range.compareRangesUsingStarts(_.range,b.range);const A=_.range?0:1,P=b.range?0:1;return A-P});for(let{range:_,text:b,eol:A}of a){if(typeof A=="number"&&(u=A),w.Range.isEmpty(_)&&!b)continue;const P=l.getValueInRange(_);if(b=b.replace(/\r\n|\n|\r/g,l.eol),P===b)continue;if(Math.max(b.length,P.length)>y._diffLimit){f.push({range:_,text:b});continue}const D=(0,E.stringDiff)(P,b,!1),k=l.offsetAt(w.Range.lift(_).getStartPosition());for(const R of D){const I=l.positionAt(k+R.originalStart),F=l.positionAt(k+R.originalStart+R.originalLength),O={text:b.substr(R.modifiedStart,R.modifiedLength),range:{startLineNumber:I.lineNumber,startColumn:I.column,endLineNumber:F.lineNumber,endColumn:F.column}};l.getValueInRange(O.range)!==O.text&&f.push(O)}}return typeof u=="number"&&f.push({eol:u,text:"",range:{startLineNumber:0,startColumn:0,endLineNumber:0,endColumn:0}}),f})}computeLinks(i){return oe(this,void 0,void 0,function*(){const a=this._getModel(i);return a?(0,S.computeLinks)(a):null})}textualSuggest(i,a,l,f){return oe(this,void 0,void 0,function*(){const u=new v.StopWatch(!0),_=new RegExp(l,f),b=new Set;e:for(let A of i){const P=this._getModel(A);if(!!P){for(let D of P.words(_))if(!(D===a||!isNaN(Number(D)))&&(b.add(D),b.size>y._suggestionsLimit))break e}}return{words:Array.from(b),duration:u.elapsed()}})}computeWordRanges(i,a,l,f){return oe(this,void 0,void 0,function*(){const u=this._getModel(i);if(!u)return Object.create(null);const _=new RegExp(l,f),b=Object.create(null);for(let A=a.startLineNumber;Athis._host.fhr(b,A),_={host:h.createProxyObject(l,f),getMirrorModels:()=>this._getModels()};return this._foreignModuleFactory?(this._foreignModule=this._foreignModuleFactory(_,a),Promise.resolve(h.getAllMethodNames(this._foreignModule))):new Promise((b,A)=>{U([i],P=>{this._foreignModule=P.create(_,a),b(h.getAllMethodNames(this._foreignModule))},A)})}fmr(i,a){if(!this._foreignModule||typeof this._foreignModule[i]!="function")return Promise.reject(new Error("Missing requestHandler or method: "+i));try{return Promise.resolve(this._foreignModule[i].apply(this._foreignModule,a))}catch(l){return Promise.reject(l)}}}r.EditorSimpleWorker=y,y._diffLimit=1e5,y._suggestionsLimit=1e4;function p(s){return new y(s,null)}r.create=p,typeof importScripts=="function"&&(e.globals.monaco=(0,d.createMonacoBaseAPI)())}),function(){var U,r;const E=self.MonacoEnvironment,e=E&&E.baseUrl?E.baseUrl:"../../../",N=typeof((U=self.trustedTypes)===null||U===void 0?void 0:U.createPolicy)=="function"?(r=self.trustedTypes)===null||r===void 0?void 0:r.createPolicy("amdLoader",{createScriptURL:S=>S,createScript:(S,...t)=>{const d=t.slice(0,-1).join(","),h=t.pop().toString();return`(function anonymous(${d}) {
    +${h}
    +})`}}):void 0;function o(){try{return(N?self.eval(N.createScript("","true")):new Function("true")).call(self),!0}catch{return!1}}function w(){return new Promise((S,t)=>{if(typeof self.define=="function"&&self.define.amd)return S();const d=e+"vs/loader.js";if(!(/^((http:)|(https:)|(file:))/.test(d)&&d.substring(0,self.origin.length)!==self.origin)&&o()){fetch(d).then(v=>{if(v.status!==200)throw new Error(v.statusText);return v.text()}).then(v=>{v=`${v}
    +//# sourceURL=${d}`,(N?self.eval(N.createScript("",v)):new Function(v)).call(self),S()}).then(void 0,t);return}N?importScripts(N.createScriptURL(d)):importScripts(d),S()})}const g=function(S){w().then(()=>{require.config({baseUrl:e,catchError:!0,trustedTypesPolicy:N,amdModulesPattern:/^vs\//}),require([S],function(t){setTimeout(function(){let d=t.create((h,v)=>{self.postMessage(h,v)},null);for(self.onmessage=h=>d.onmessage(h.data,h.ports);m.length>0;)self.onmessage(m.shift())},0)})})};let c=!0,m=[];self.onmessage=S=>{if(!c){m.push(S);return}c=!1,g(S.data)}}()}).call(this);
    +
    +//# sourceMappingURL=../../../../min-maps/vs/base/worker/workerMain.js.map
    \ No newline at end of file
    diff --git a/examples/tour-assets/monaco/basic-languages/python/python.js b/examples/tour-assets/monaco/basic-languages/python/python.js
    new file mode 100644
    index 0000000000000000000000000000000000000000..1b187b93349f2fb9e4fe9e8a36814d8a5174021a
    --- /dev/null
    +++ b/examples/tour-assets/monaco/basic-languages/python/python.js
    @@ -0,0 +1,10 @@
    +/*!-----------------------------------------------------------------------------
    + * Copyright (c) Microsoft Corporation. All rights reserved.
    + * Version: 0.33.0(4b1abad427e58dbedc1215d99a0902ffc885fcd4)
    + * Released under the MIT license
    + * https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
    + *-----------------------------------------------------------------------------*/
    +define("vs/basic-languages/python/python", ["require","require"],(require)=>{
    +var moduleExports=(()=>{var d=Object.create;var o=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var u=Object.getPrototypeOf,f=Object.prototype.hasOwnProperty;var l=e=>o(e,"__esModule",{value:!0});var b=(e=>typeof require!="undefined"?require:typeof Proxy!="undefined"?new Proxy(e,{get:(n,t)=>(typeof require!="undefined"?require:n)[t]}):e)(function(e){if(typeof require!="undefined")return require.apply(this,arguments);throw new Error('Dynamic require of "'+e+'" is not supported')});var y=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports),h=(e,n)=>{for(var t in n)o(e,t,{get:n[t],enumerable:!0})},i=(e,n,t,a)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of _(n))!f.call(e,r)&&(t||r!=="default")&&o(e,r,{get:()=>n[r],enumerable:!(a=m(n,r))||a.enumerable});return e},c=(e,n)=>i(l(o(e!=null?d(u(e)):{},"default",!n&&e&&e.__esModule?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e),x=(e=>(n,t)=>e&&e.get(n)||(t=i(l({}),n,1),e&&e.set(n,t),t))(typeof WeakMap!="undefined"?new WeakMap:0);var g=y((v,p)=>{var w=c(b("vs/editor/editor.api"));p.exports=w});var D={};h(D,{conf:()=>k,language:()=>$});var s={};i(s,c(g()));var k={comments:{lineComment:"#",blockComment:["'''","'''"]},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"',notIn:["string"]},{open:"'",close:"'",notIn:["string","comment"]}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],onEnterRules:[{beforeText:new RegExp("^\\s*(?:def|class|for|if|elif|else|while|try|with|finally|except|async).*?:\\s*$"),action:{indentAction:s.languages.IndentAction.Indent}}],folding:{offSide:!0,markers:{start:new RegExp("^\\s*#region\\b"),end:new RegExp("^\\s*#endregion\\b")}}},$={defaultToken:"",tokenPostfix:".python",keywords:["False","None","True","and","as","assert","async","await","break","class","continue","def","del","elif","else","except","exec","finally","for","from","global","if","import","in","is","lambda","nonlocal","not","or","pass","print","raise","return","try","while","with","yield","int","float","long","complex","hex","abs","all","any","apply","basestring","bin","bool","buffer","bytearray","callable","chr","classmethod","cmp","coerce","compile","complex","delattr","dict","dir","divmod","enumerate","eval","execfile","file","filter","format","frozenset","getattr","globals","hasattr","hash","help","id","input","intern","isinstance","issubclass","iter","len","locals","list","map","max","memoryview","min","next","object","oct","open","ord","pow","print","property","reversed","range","raw_input","reduce","reload","repr","reversed","round","self","set","setattr","slice","sorted","staticmethod","str","sum","super","tuple","type","unichr","unicode","vars","xrange","zip","__dict__","__methods__","__members__","__class__","__bases__","__name__","__mro__","__subclasses__","__init__","__import__"],brackets:[{open:"{",close:"}",token:"delimiter.curly"},{open:"[",close:"]",token:"delimiter.bracket"},{open:"(",close:")",token:"delimiter.parenthesis"}],tokenizer:{root:[{include:"@whitespace"},{include:"@numbers"},{include:"@strings"},[/[,:;]/,"delimiter"],[/[{}\[\]()]/,"@brackets"],[/@[a-zA-Z_]\w*/,"tag"],[/[a-zA-Z_]\w*/,{cases:{"@keywords":"keyword","@default":"identifier"}}]],whitespace:[[/\s+/,"white"],[/(^#.*$)/,"comment"],[/'''/,"string","@endDocString"],[/"""/,"string","@endDblDocString"]],endDocString:[[/[^']+/,"string"],[/\\'/,"string"],[/'''/,"string","@popall"],[/'/,"string"]],endDblDocString:[[/[^"]+/,"string"],[/\\"/,"string"],[/"""/,"string","@popall"],[/"/,"string"]],numbers:[[/-?0x([abcdef]|[ABCDEF]|\d)+[lL]?/,"number.hex"],[/-?(\d*\.)?\d+([eE][+\-]?\d+)?[jJ]?[lL]?/,"number"]],strings:[[/'$/,"string.escape","@popall"],[/'/,"string.escape","@stringBody"],[/"$/,"string.escape","@popall"],[/"/,"string.escape","@dblStringBody"]],stringBody:[[/[^\\']+$/,"string","@popall"],[/[^\\']+/,"string"],[/\\./,"string"],[/'/,"string.escape","@popall"],[/\\$/,"string"]],dblStringBody:[[/[^\\"]+$/,"string","@popall"],[/[^\\"]+/,"string"],[/\\./,"string"],[/"/,"string.escape","@popall"],[/\\$/,"string"]]}};return x(D);})();
    +return moduleExports;
    +});
    diff --git a/examples/tour-assets/monaco/editor/editor.main.css b/examples/tour-assets/monaco/editor/editor.main.css
    new file mode 100644
    index 0000000000000000000000000000000000000000..8ae93d13ba39416d28c7e25b7e9f3653ca31e68a
    --- /dev/null
    +++ b/examples/tour-assets/monaco/editor/editor.main.css
    @@ -0,0 +1,6 @@
    +/*!-----------------------------------------------------------
    + * Copyright (c) Microsoft Corporation. All rights reserved.
    + * Version: 0.33.0(c722ca6c7eed3d7987c0d5c3df5c45f6b15e77d1)
    + * Released under the MIT license
    + * https://github.com/microsoft/vscode/blob/main/LICENSE.txt
    + *-----------------------------------------------------------*/.monaco-action-bar{white-space:nowrap;height:100%}.monaco-action-bar .actions-container{display:flex;margin:0 auto;padding:0;height:100%;width:100%;align-items:center}.monaco-action-bar.vertical .actions-container{display:inline-block}.monaco-action-bar .action-item{display:block;align-items:center;justify-content:center;cursor:pointer;position:relative}.monaco-action-bar .action-item.disabled{cursor:default}.monaco-action-bar .action-item .codicon,.monaco-action-bar .action-item .icon{display:block}.monaco-action-bar .action-item .codicon{display:flex;align-items:center;width:16px;height:16px}.monaco-action-bar .action-label{font-size:11px;padding:3px;border-radius:5px}.monaco-action-bar .action-item.disabled .action-label,.monaco-action-bar .action-item.disabled .action-label:before,.monaco-action-bar .action-item.disabled .action-label:hover{opacity:.4}.monaco-action-bar.vertical{text-align:left}.monaco-action-bar.vertical .action-item{display:block}.monaco-action-bar.vertical .action-label.separator{display:block;border-bottom:1px solid #bbb;padding-top:1px;margin-left:.8em;margin-right:.8em}.monaco-action-bar .action-item .action-label.separator{width:1px;height:16px;margin:5px 4px!important;cursor:default;min-width:1px;padding:0;background-color:#bbb}.secondary-actions .monaco-action-bar .action-label{margin-left:6px}.monaco-action-bar .action-item.select-container{overflow:hidden;flex:1;max-width:170px;min-width:60px;display:flex;align-items:center;justify-content:center;margin-right:10px}.monaco-action-bar .action-item.action-dropdown-item{display:flex}.monaco-action-bar .action-item.action-dropdown-item>.action-label{margin-right:1px}.monaco-aria-container{position:absolute;left:-999em}.monaco-text-button{box-sizing:border-box;display:flex;width:100%;padding:4px;text-align:center;cursor:pointer;justify-content:center;align-items:center}.monaco-text-button:focus{outline-offset:2px!important}.monaco-text-button:hover{text-decoration:none!important}.monaco-button.disabled,.monaco-button.disabled:focus{opacity:.4!important;cursor:default}.monaco-text-button>.codicon{margin:0 .2em;color:inherit!important}.monaco-button-dropdown{display:flex;cursor:pointer}.monaco-button-dropdown>.monaco-dropdown-button{margin-left:1px}.monaco-description-button{flex-direction:column}.monaco-description-button .monaco-button-label{font-weight:500}.monaco-description-button .monaco-button-description{font-style:italic}.monaco-description-button .monaco-button-description,.monaco-description-button .monaco-button-label{display:flex;justify-content:center;align-items:center}.monaco-description-button .monaco-button-description>.codicon,.monaco-description-button .monaco-button-label>.codicon{margin:0 .2em;color:inherit!important}.monaco-custom-checkbox{margin-left:2px;float:left;cursor:pointer;overflow:hidden;width:20px;height:20px;border-radius:3px;border:1px solid transparent;padding:1px;box-sizing:border-box;user-select:none;-webkit-user-select:none;-ms-user-select:none}.monaco-custom-checkbox:hover{background-color:var(--vscode-inputOption-hoverBackground)}.hc-black .monaco-custom-checkbox:hover{border:1px dashed var(--vscode-focusBorder)}.hc-black .monaco-custom-checkbox,.hc-black .monaco-custom-checkbox:hover{background:none}.monaco-custom-checkbox.monaco-simple-checkbox{height:18px;width:18px;border:1px solid transparent;border-radius:3px;margin-right:9px;margin-left:0;padding:0;opacity:1;background-size:16px!important}.monaco-custom-checkbox.monaco-simple-checkbox:not(.checked):before{visibility:hidden}@font-face{font-family:codicon;font-display:block;src:url(../base/browser/ui/codicons/codicon/codicon.ttf) format("truetype")}.codicon[class*=codicon-]{font:normal normal normal 16px/1 codicon;display:inline-block;text-decoration:none;text-rendering:auto;text-align:center;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;user-select:none;-webkit-user-select:none;-ms-user-select:none}.codicon-wrench-subaction{opacity:.5}@keyframes codicon-spin{to{transform:rotate(1turn)}}.codicon-gear.codicon-modifier-spin,.codicon-loading.codicon-modifier-spin,.codicon-notebook-state-executing.codicon-modifier-spin,.codicon-sync.codicon-modifier-spin{animation:codicon-spin 1.5s steps(30) infinite}.codicon-modifier-disabled{opacity:.4}.codicon-loading,.codicon-tree-item-loading:before{animation-duration:1s!important;animation-timing-function:cubic-bezier(.53,.21,.29,.67)!important}.context-view{position:absolute;z-index:2500}.context-view.fixed{all:initial;font-family:inherit;font-size:13px;position:fixed;z-index:2500;color:inherit}.monaco-count-badge{padding:3px 6px;border-radius:11px;font-size:11px;min-width:18px;min-height:18px;line-height:11px;font-weight:400;text-align:center;display:inline-block;box-sizing:border-box}.monaco-count-badge.long{padding:2px 3px;border-radius:2px;min-height:auto;line-height:normal}.monaco-dropdown{height:100%;padding:0}.monaco-dropdown>.dropdown-label{cursor:pointer;height:100%;display:flex;align-items:center;justify-content:center}.monaco-dropdown>.dropdown-label>.action-label.disabled{cursor:default}.monaco-dropdown-with-primary{display:flex!important;flex-direction:row;border-radius:5px}.monaco-dropdown-with-primary>.action-container>.action-label{margin-right:0}.monaco-dropdown-with-primary>.dropdown-action-container>.monaco-dropdown>.dropdown-label .codicon[class*=codicon-]{font-size:12px;padding-left:0;padding-right:0;line-height:16px;margin-left:-3px}.monaco-dropdown-with-primary>.dropdown-action-container>.monaco-dropdown>.dropdown-label>.action-label{display:block;background-size:16px;background-position:50%;background-repeat:no-repeat}.monaco-findInput{position:relative}.monaco-findInput .monaco-inputbox{font-size:13px;width:100%}.monaco-findInput>.controls{position:absolute;top:3px;right:2px}.vs .monaco-findInput.disabled{background-color:#e1e1e1}.vs-dark .monaco-findInput.disabled{background-color:#333}.monaco-findInput.highlight-0 .controls{animation:monaco-findInput-highlight-0 .1s linear 0s}.monaco-findInput.highlight-1 .controls{animation:monaco-findInput-highlight-1 .1s linear 0s}.hc-black .monaco-findInput.highlight-0 .controls,.vs-dark .monaco-findInput.highlight-0 .controls{animation:monaco-findInput-highlight-dark-0 .1s linear 0s}.hc-black .monaco-findInput.highlight-1 .controls,.vs-dark .monaco-findInput.highlight-1 .controls{animation:monaco-findInput-highlight-dark-1 .1s linear 0s}@keyframes monaco-findInput-highlight-0{0%{background:rgba(253,255,0,.8)}to{background:transparent}}@keyframes monaco-findInput-highlight-1{0%{background:rgba(253,255,0,.8)}99%{background:transparent}}@keyframes monaco-findInput-highlight-dark-0{0%{background:hsla(0,0%,100%,.44)}to{background:transparent}}@keyframes monaco-findInput-highlight-dark-1{0%{background:hsla(0,0%,100%,.44)}99%{background:transparent}}.monaco-hover{cursor:default;position:absolute;overflow:hidden;z-index:50;user-select:text;-webkit-user-select:text;-ms-user-select:text;box-sizing:initial;animation:fadein .1s linear;line-height:1.5em}.monaco-hover.hidden{display:none}.monaco-hover a:hover{cursor:pointer}.monaco-hover .hover-contents:not(.html-hover-contents){padding:4px 8px}.monaco-hover .markdown-hover>.hover-contents:not(.code-hover-contents){max-width:500px;word-wrap:break-word}.monaco-hover .markdown-hover>.hover-contents:not(.code-hover-contents) hr{min-width:100%}.monaco-hover .code,.monaco-hover p,.monaco-hover ul{margin:8px 0}.monaco-hover code{font-family:var(--monaco-monospace-font)}.monaco-hover hr{box-sizing:border-box;border-left:0;border-right:0;margin:4px -8px -4px;height:1px}.monaco-hover .code:first-child,.monaco-hover p:first-child,.monaco-hover ul:first-child{margin-top:0}.monaco-hover .code:last-child,.monaco-hover p:last-child,.monaco-hover ul:last-child{margin-bottom:0}.monaco-hover ol,.monaco-hover ul{padding-left:20px}.monaco-hover li>p{margin-bottom:0}.monaco-hover li>ul{margin-top:0}.monaco-hover code{border-radius:3px;padding:0 .4em}.monaco-hover .monaco-tokenized-source{white-space:pre-wrap}.monaco-hover .hover-row.status-bar{font-size:12px;line-height:22px}.monaco-hover .hover-row.status-bar .actions{display:flex;padding:0 8px}.monaco-hover .hover-row.status-bar .actions .action-container{margin-right:16px;cursor:pointer}.monaco-hover .hover-row.status-bar .actions .action-container .action .icon{padding-right:4px}.monaco-hover .markdown-hover .hover-contents .codicon{color:inherit;font-size:inherit;vertical-align:middle}.monaco-hover .hover-contents a.code-link,.monaco-hover .hover-contents a.code-link:hover{color:inherit}.monaco-hover .hover-contents a.code-link:before{content:"("}.monaco-hover .hover-contents a.code-link:after{content:")"}.monaco-hover .hover-contents a.code-link>span{text-decoration:underline;border-bottom:1px solid transparent;text-underline-position:under}.monaco-hover .markdown-hover .hover-contents:not(.code-hover-contents):not(.html-hover-contents) span{margin-bottom:4px;display:inline-block}.monaco-hover-content .action-container a{-webkit-user-select:none;user-select:none}.monaco-hover-content .action-container.disabled{pointer-events:none;opacity:.4;cursor:default}.monaco-icon-label{display:flex;overflow:hidden;text-overflow:ellipsis}.monaco-icon-label:before{background-size:16px;background-position:0;background-repeat:no-repeat;padding-right:6px;width:16px;height:22px;line-height:inherit!important;display:inline-block;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;vertical-align:top;flex-shrink:0}.monaco-icon-label>.monaco-icon-label-container{min-width:0;overflow:hidden;text-overflow:ellipsis;flex:1}.monaco-icon-label>.monaco-icon-label-container>.monaco-icon-name-container>.label-name{color:inherit;white-space:pre}.monaco-icon-label>.monaco-icon-label-container>.monaco-icon-name-container>.label-name>.label-separator{margin:0 2px;opacity:.5}.monaco-icon-label>.monaco-icon-label-container>.monaco-icon-description-container>.label-description{opacity:.7;margin-left:.5em;font-size:.9em;white-space:pre}.monaco-icon-label.nowrap>.monaco-icon-label-container>.monaco-icon-description-container>.label-description{white-space:nowrap}.vs .monaco-icon-label>.monaco-icon-label-container>.monaco-icon-description-container>.label-description{opacity:.95}.monaco-icon-label.italic>.monaco-icon-label-container>.monaco-icon-description-container>.label-description,.monaco-icon-label.italic>.monaco-icon-label-container>.monaco-icon-name-container>.label-name{font-style:italic}.monaco-icon-label.deprecated{text-decoration:line-through;opacity:.66}.monaco-icon-label.italic:after{font-style:italic}.monaco-icon-label.strikethrough>.monaco-icon-label-container>.monaco-icon-description-container>.label-description,.monaco-icon-label.strikethrough>.monaco-icon-label-container>.monaco-icon-name-container>.label-name{text-decoration:line-through}.monaco-icon-label:after{opacity:.75;font-size:90%;font-weight:600;margin:auto 16px 0 5px;text-align:center}.monaco-list:focus .selected .monaco-icon-label,.monaco-list:focus .selected .monaco-icon-label:after{color:inherit!important}.monaco-list-row.focused.selected .label-description,.monaco-list-row.selected .label-description{opacity:.8}.monaco-inputbox{position:relative;display:block;padding:0;box-sizing:border-box;font-size:inherit}.monaco-inputbox.idle{border:1px solid transparent}.monaco-inputbox>.ibwrapper>.input,.monaco-inputbox>.ibwrapper>.mirror{padding:4px}.monaco-inputbox>.ibwrapper{position:relative;width:100%;height:100%}.monaco-inputbox>.ibwrapper>.input{display:inline-block;box-sizing:border-box;width:100%;height:100%;line-height:inherit;border:none;font-family:inherit;font-size:inherit;resize:none;color:inherit}.monaco-inputbox>.ibwrapper>input{text-overflow:ellipsis}.monaco-inputbox>.ibwrapper>textarea.input{display:block;-ms-overflow-style:none;scrollbar-width:none;outline:none}.monaco-inputbox>.ibwrapper>textarea.input::-webkit-scrollbar{display:none}.monaco-inputbox>.ibwrapper>textarea.input.empty{white-space:nowrap}.monaco-inputbox>.ibwrapper>.mirror{position:absolute;display:inline-block;width:100%;top:0;left:0;box-sizing:border-box;white-space:pre-wrap;visibility:hidden;word-wrap:break-word}.monaco-inputbox-container{text-align:right}.monaco-inputbox-container .monaco-inputbox-message{display:inline-block;overflow:hidden;text-align:left;width:100%;box-sizing:border-box;padding:.4em;font-size:12px;line-height:17px;margin-top:-1px;word-wrap:break-word}.monaco-inputbox .monaco-action-bar{position:absolute;right:2px;top:4px}.monaco-inputbox .monaco-action-bar .action-item{margin-left:2px}.monaco-inputbox .monaco-action-bar .action-item .codicon{background-repeat:no-repeat;width:16px;height:16px}.monaco-keybinding{display:flex;align-items:center;line-height:10px}.monaco-keybinding>.monaco-keybinding-key{display:inline-block;border-style:solid;border-width:1px;border-radius:3px;vertical-align:middle;font-size:11px;padding:3px 5px;margin:0 2px}.monaco-keybinding>.monaco-keybinding-key:first-child{margin-left:0}.monaco-keybinding>.monaco-keybinding-key:last-child{margin-right:0}.monaco-keybinding>.monaco-keybinding-key-separator{display:inline-block}.monaco-keybinding>.monaco-keybinding-key-chord-separator{width:6px}.monaco-list{position:relative;height:100%;width:100%;white-space:nowrap}.monaco-list.mouse-support{user-select:none;-webkit-user-select:none;-ms-user-select:none}.monaco-list>.monaco-scrollable-element{height:100%}.monaco-list-rows{position:relative;width:100%;height:100%}.monaco-list.horizontal-scrolling .monaco-list-rows{width:auto;min-width:100%}.monaco-list-row{position:absolute;box-sizing:border-box;overflow:hidden;width:100%}.monaco-list.mouse-support .monaco-list-row{cursor:pointer;touch-action:none}.monaco-list-row.scrolling{display:none!important}.monaco-list.element-focused,.monaco-list.selection-multiple,.monaco-list.selection-single{outline:0!important}.monaco-drag-image{display:inline-block;padding:1px 7px;border-radius:10px;font-size:12px;position:absolute;z-index:1000}.monaco-list-type-filter{display:flex;align-items:center;position:absolute;border-radius:2px;padding:0 3px;max-width:calc(100% - 10px);text-overflow:ellipsis;overflow:hidden;text-align:right;box-sizing:border-box;cursor:all-scroll;font-size:13px;line-height:18px;height:20px;z-index:1;top:4px}.monaco-list-type-filter.dragging{transition:top .2s,left .2s}.monaco-list-type-filter.ne{right:4px}.monaco-list-type-filter.nw{left:4px}.monaco-list-type-filter>.controls{display:flex;align-items:center;box-sizing:border-box;transition:width .2s;width:0}.monaco-list-type-filter.dragging>.controls,.monaco-list-type-filter:hover>.controls{width:36px}.monaco-list-type-filter>.controls>*{border:none;box-sizing:border-box;-webkit-appearance:none;-moz-appearance:none;background:none;width:16px;height:16px;flex-shrink:0;margin:0;padding:0;display:flex;align-items:center;justify-content:center;cursor:pointer}.monaco-list-type-filter>.controls>.filter{margin-left:4px}.monaco-list-type-filter-message{position:absolute;box-sizing:border-box;width:100%;height:100%;top:0;left:0;padding:40px 1em 1em;text-align:center;white-space:normal;opacity:.7;pointer-events:none}.monaco-list-type-filter-message:empty{display:none}.monaco-list-type-filter{cursor:grab}.monaco-list-type-filter.dragging{cursor:grabbing}.monaco-mouse-cursor-text{cursor:text}.monaco-progress-container{width:100%;height:5px;overflow:hidden}.monaco-progress-container .progress-bit{width:2%;height:5px;position:absolute;left:0;display:none}.monaco-progress-container.active .progress-bit{display:inherit}.monaco-progress-container.discrete .progress-bit{left:0;transition:width .1s linear}.monaco-progress-container.discrete.done .progress-bit{width:100%}.monaco-progress-container.infinite .progress-bit{animation-name:progress;animation-duration:4s;animation-iteration-count:infinite;transform:translateZ(0);animation-timing-function:linear}.monaco-progress-container.infinite.infinite-long-running .progress-bit{animation-timing-function:steps(100)}@keyframes progress{0%{transform:translateX(0) scaleX(1)}50%{transform:translateX(2500%) scaleX(3)}to{transform:translateX(4900%) scaleX(1)}}:root{--sash-size:4px}.monaco-sash{position:absolute;z-index:35;touch-action:none}.monaco-sash.disabled{pointer-events:none}.monaco-sash.mac.vertical{cursor:col-resize}.monaco-sash.vertical.minimum{cursor:e-resize}.monaco-sash.vertical.maximum{cursor:w-resize}.monaco-sash.mac.horizontal{cursor:row-resize}.monaco-sash.horizontal.minimum{cursor:s-resize}.monaco-sash.horizontal.maximum{cursor:n-resize}.monaco-sash.disabled{cursor:default!important;pointer-events:none!important}.monaco-sash.vertical{cursor:ew-resize;top:0;width:var(--sash-size);height:100%}.monaco-sash.horizontal{cursor:ns-resize;left:0;width:100%;height:var(--sash-size)}.monaco-sash:not(.disabled)>.orthogonal-drag-handle{content:" ";height:calc(var(--sash-size)*2);width:calc(var(--sash-size)*2);z-index:100;display:block;cursor:all-scroll;position:absolute}.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled)>.orthogonal-drag-handle.start,.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled)>.orthogonal-drag-handle.end{cursor:nwse-resize}.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled)>.orthogonal-drag-handle.end,.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled)>.orthogonal-drag-handle.start{cursor:nesw-resize}.monaco-sash.vertical>.orthogonal-drag-handle.start{left:calc(var(--sash-size)*-0.5);top:calc(var(--sash-size)*-1)}.monaco-sash.vertical>.orthogonal-drag-handle.end{left:calc(var(--sash-size)*-0.5);bottom:calc(var(--sash-size)*-1)}.monaco-sash.horizontal>.orthogonal-drag-handle.start{top:calc(var(--sash-size)*-0.5);left:calc(var(--sash-size)*-1)}.monaco-sash.horizontal>.orthogonal-drag-handle.end{top:calc(var(--sash-size)*-0.5);right:calc(var(--sash-size)*-1)}.monaco-sash:before{content:"";pointer-events:none;position:absolute;width:100%;height:100%;transition:background-color .1s ease-out;background:transparent}.monaco-sash.vertical:before{width:var(--sash-hover-size);left:calc(50% - var(--sash-hover-size)/2)}.monaco-sash.horizontal:before{height:var(--sash-hover-size);top:calc(50% - var(--sash-hover-size)/2)}.pointer-events-disabled{pointer-events:none!important}.monaco-sash.debug{background:#0ff}.monaco-sash.debug.disabled{background:rgba(0,255,255,.2)}.monaco-sash.debug:not(.disabled)>.orthogonal-drag-handle{background:red}.monaco-scrollable-element>.scrollbar>.scra{cursor:pointer;font-size:11px!important}.monaco-scrollable-element>.visible{opacity:1;background:transparent;transition:opacity .1s linear}.monaco-scrollable-element>.invisible{opacity:0;pointer-events:none}.monaco-scrollable-element>.invisible.fade{transition:opacity .8s linear}.monaco-scrollable-element>.shadow{position:absolute;display:none}.monaco-scrollable-element>.shadow.top{display:block;top:0;left:3px;height:3px;width:100%}.monaco-scrollable-element>.shadow.left{display:block;top:3px;left:0;height:100%;width:3px}.monaco-scrollable-element>.shadow.top-left-corner{display:block;top:0;left:0;height:3px;width:3px}.monaco-split-view2{position:relative;width:100%;height:100%}.monaco-split-view2>.sash-container{position:absolute;width:100%;height:100%;pointer-events:none}.monaco-split-view2>.sash-container>.monaco-sash{pointer-events:auto}.monaco-split-view2>.monaco-scrollable-element{width:100%;height:100%}.monaco-split-view2>.monaco-scrollable-element>.split-view-container{width:100%;height:100%;white-space:nowrap;position:relative}.monaco-split-view2>.monaco-scrollable-element>.split-view-container>.split-view-view{white-space:normal;position:absolute}.monaco-split-view2>.monaco-scrollable-element>.split-view-container>.split-view-view:not(.visible){display:none}.monaco-split-view2.vertical>.monaco-scrollable-element>.split-view-container>.split-view-view{width:100%}.monaco-split-view2.horizontal>.monaco-scrollable-element>.split-view-container>.split-view-view{height:100%}.monaco-split-view2.separator-border>.monaco-scrollable-element>.split-view-container>.split-view-view:not(:first-child):before{content:" ";position:absolute;top:0;left:0;z-index:5;pointer-events:none;background-color:var(--separator-border)}.monaco-split-view2.separator-border.horizontal>.monaco-scrollable-element>.split-view-container>.split-view-view:not(:first-child):before{height:100%;width:1px}.monaco-split-view2.separator-border.vertical>.monaco-scrollable-element>.split-view-container>.split-view-view:not(:first-child):before{height:1px;width:100%}.monaco-table{display:flex;flex-direction:column;position:relative;height:100%;width:100%;white-space:nowrap}.monaco-table>.monaco-split-view2{border-bottom:1px solid transparent}.monaco-table>.monaco-list{flex:1}.monaco-table-tr{display:flex;height:100%}.monaco-table-th{width:100%;height:100%;font-weight:700;overflow:hidden;text-overflow:ellipsis}.monaco-table-td,.monaco-table-th{box-sizing:border-box;flex-shrink:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.monaco-table>.monaco-split-view2 .monaco-sash.vertical:before{content:"";position:absolute;left:calc(var(--sash-size)/2);width:0;border-left:1px solid transparent}.monaco-table>.monaco-split-view2,.monaco-table>.monaco-split-view2 .monaco-sash.vertical:before{transition:border-color .2s ease-out}.monaco-tl-row{display:flex;height:100%;align-items:center;position:relative}.monaco-tl-indent{height:100%;position:absolute;top:0;left:16px;pointer-events:none}.hide-arrows .monaco-tl-indent{left:12px}.monaco-tl-indent>.indent-guide{display:inline-block;box-sizing:border-box;height:100%;border-left:1px solid transparent;transition:border-color .1s linear}.monaco-tl-contents,.monaco-tl-twistie{height:100%}.monaco-tl-twistie{font-size:10px;text-align:right;padding-right:6px;flex-shrink:0;width:16px;display:flex!important;align-items:center;justify-content:center;transform:translateX(3px)}.monaco-tl-contents{flex:1;overflow:hidden}.monaco-tl-twistie:before{border-radius:20px}.monaco-tl-twistie.collapsed:before{transform:rotate(-90deg)}.monaco-tl-twistie.codicon-tree-item-loading:before{animation:codicon-spin 1.25s steps(30) infinite}.quick-input-widget{position:absolute;width:600px;z-index:2000;padding:0 1px 1px;left:50%;margin-left:-300px}.quick-input-titlebar{display:flex;align-items:center}.quick-input-left-action-bar{display:flex;margin-left:4px;flex:1}.quick-input-title{padding:3px 0;text-align:center;text-overflow:ellipsis;overflow:hidden}.quick-input-right-action-bar{display:flex;margin-right:4px;flex:1}.quick-input-right-action-bar>.actions-container{justify-content:flex-end}.quick-input-titlebar .monaco-action-bar .action-label.codicon{background-position:50%;background-repeat:no-repeat;padding:2px}.quick-input-description{margin:6px}.quick-input-header .quick-input-description{margin:4px 2px}.quick-input-header{display:flex;padding:6px 6px 0;margin-bottom:-2px}.quick-input-widget.hidden-input .quick-input-header{padding:0;margin-bottom:0}.quick-input-and-message{display:flex;flex-direction:column;flex-grow:1;min-width:0;position:relative}.quick-input-check-all{align-self:center;margin:0}.quick-input-filter{flex-grow:1;display:flex;position:relative}.quick-input-box{flex-grow:1}.quick-input-widget.show-checkboxes .quick-input-box,.quick-input-widget.show-checkboxes .quick-input-message{margin-left:5px}.quick-input-visible-count{position:absolute;left:-10000px}.quick-input-count{align-self:center;position:absolute;right:4px;display:flex;align-items:center}.quick-input-count .monaco-count-badge{vertical-align:middle;padding:2px 4px;border-radius:2px;min-height:auto;line-height:normal}.quick-input-action{margin-left:6px}.quick-input-action .monaco-text-button{font-size:11px;padding:0 6px;display:flex;height:27.5px;align-items:center}.quick-input-message{margin-top:-1px;padding:5px;overflow-wrap:break-word}.quick-input-message>.codicon{margin:0 .2em;vertical-align:text-bottom}.quick-input-progress.monaco-progress-container{position:relative}.quick-input-progress.monaco-progress-container,.quick-input-progress.monaco-progress-container .progress-bit{height:2px}.quick-input-list{line-height:22px;margin-top:6px}.quick-input-widget.hidden-input .quick-input-list{margin-top:0}.quick-input-list .monaco-list{overflow:hidden;max-height:440px}.quick-input-list .quick-input-list-entry{box-sizing:border-box;overflow:hidden;display:flex;height:100%;padding:0 6px}.quick-input-list .quick-input-list-entry.quick-input-list-separator-border{border-top-width:1px;border-top-style:solid}.quick-input-list .monaco-list-row[data-index="0"] .quick-input-list-entry.quick-input-list-separator-border{border-top-style:none}.quick-input-list .quick-input-list-label{overflow:hidden;display:flex;height:100%;flex:1}.quick-input-list .quick-input-list-checkbox{align-self:center;margin:0}.quick-input-list .quick-input-list-rows{overflow:hidden;text-overflow:ellipsis;display:flex;flex-direction:column;height:100%;flex:1;margin-left:5px}.quick-input-widget.show-checkboxes .quick-input-list .quick-input-list-rows{margin-left:10px}.quick-input-widget .quick-input-list .quick-input-list-checkbox{display:none}.quick-input-widget.show-checkboxes .quick-input-list .quick-input-list-checkbox{display:inline}.quick-input-list .quick-input-list-rows>.quick-input-list-row{display:flex;align-items:center}.quick-input-list .quick-input-list-rows>.quick-input-list-row .monaco-icon-label,.quick-input-list .quick-input-list-rows>.quick-input-list-row .monaco-icon-label .monaco-icon-label-container>.monaco-icon-name-container{flex:1}.quick-input-list .quick-input-list-rows>.quick-input-list-row .codicon[class*=codicon-]{vertical-align:text-bottom}.quick-input-list .quick-input-list-rows .monaco-highlighted-label span{opacity:1}.quick-input-list .quick-input-list-entry .quick-input-list-entry-keybinding{margin-right:8px}.quick-input-list .quick-input-list-label-meta{opacity:.7;line-height:normal;text-overflow:ellipsis;overflow:hidden}.quick-input-list .monaco-highlighted-label .highlight{font-weight:700}.quick-input-list .quick-input-list-entry .quick-input-list-separator{margin-right:8px}.quick-input-list .quick-input-list-entry-action-bar{display:flex;flex:0;overflow:visible}.quick-input-list .quick-input-list-entry-action-bar .action-label{display:none}.quick-input-list .quick-input-list-entry-action-bar .action-label.codicon{margin-right:4px;padding:0 2px 2px}.quick-input-list .quick-input-list-entry-action-bar{margin-top:1px;margin-right:4px}.quick-input-list .monaco-list-row.focused .quick-input-list-entry-action-bar .action-label,.quick-input-list .quick-input-list-entry .quick-input-list-entry-action-bar .action-label.always-visible,.quick-input-list .quick-input-list-entry:hover .quick-input-list-entry-action-bar .action-label{display:flex}.quick-input-list .monaco-list-row.focused .monaco-keybinding-key,.quick-input-list .monaco-list-row.focused .quick-input-list-entry .quick-input-list-separator{color:inherit}.quick-input-list .monaco-list-row.focused .monaco-keybinding-key{background:none}.monaco-editor .inputarea{min-width:0;min-height:0;margin:0;padding:0;position:absolute;outline:none!important;resize:none;border:none;overflow:hidden;color:transparent;background-color:transparent}.monaco-editor .inputarea.ime-input{z-index:10}.monaco-editor .margin-view-overlays .current-line,.monaco-editor .view-overlays .current-line{display:block;position:absolute;left:0;top:0;box-sizing:border-box}.monaco-editor .margin-view-overlays .current-line.current-line-margin.current-line-margin-both{border-right:0}.monaco-editor .lines-content .cdr{position:absolute}.monaco-editor .glyph-margin{position:absolute;top:0}.monaco-editor .margin-view-overlays .cgmr{position:absolute;display:flex;align-items:center;justify-content:center}.monaco-editor .lines-content .core-guide{position:absolute;box-sizing:border-box}.monaco-editor .margin-view-overlays .line-numbers{font-variant-numeric:tabular-nums;position:absolute;text-align:right;display:inline-block;vertical-align:middle;box-sizing:border-box;cursor:default;height:100%}.monaco-editor .relative-current-line-number{text-align:left;display:inline-block;width:100%}.monaco-editor .margin-view-overlays .line-numbers.lh-odd{margin-top:1px}.mtkcontrol{color:#fff!important;background:#960000!important}.monaco-editor.no-user-select .lines-content,.monaco-editor.no-user-select .view-line,.monaco-editor.no-user-select .view-lines{user-select:none;-webkit-user-select:none;-ms-user-select:none}.monaco-editor .view-lines{white-space:nowrap}.monaco-editor .view-line{position:absolute;width:100%}.monaco-editor .mtkz{display:inline-block}.monaco-editor .lines-decorations{position:absolute;top:0;background:#fff}.monaco-editor .margin-view-overlays .cldr{position:absolute;height:100%}.monaco-editor .margin-view-overlays .cmdr{position:absolute;left:0;width:100%;height:100%}.monaco-editor .minimap.slider-mouseover .minimap-slider{opacity:0;transition:opacity .1s linear}.monaco-editor .minimap.slider-mouseover .minimap-slider.active,.monaco-editor .minimap.slider-mouseover:hover .minimap-slider{opacity:1}.monaco-editor .minimap-shadow-hidden{position:absolute;width:0}.monaco-editor .minimap-shadow-visible{position:absolute;left:-6px;width:6px}.monaco-editor.no-minimap-shadow .minimap-shadow-visible{position:absolute;left:-1px;width:1px}.monaco-editor .overlayWidgets{position:absolute;top:0;left:0}.monaco-editor .view-ruler{position:absolute;top:0}.monaco-editor .scroll-decoration{position:absolute;top:0;left:0;height:6px}.monaco-editor .lines-content .cslr{position:absolute}.monaco-editor .top-left-radius{border-top-left-radius:3px}.monaco-editor .bottom-left-radius{border-bottom-left-radius:3px}.monaco-editor .top-right-radius{border-top-right-radius:3px}.monaco-editor .bottom-right-radius{border-bottom-right-radius:3px}.monaco-editor.hc-black .top-left-radius{border-top-left-radius:0}.monaco-editor.hc-black .bottom-left-radius{border-bottom-left-radius:0}.monaco-editor.hc-black .top-right-radius{border-top-right-radius:0}.monaco-editor.hc-black .bottom-right-radius{border-bottom-right-radius:0}.monaco-editor .cursors-layer{position:absolute;top:0}.monaco-editor .cursors-layer>.cursor{position:absolute;overflow:hidden}.monaco-editor .cursors-layer.cursor-smooth-caret-animation>.cursor{transition:all 80ms}.monaco-editor .cursors-layer.cursor-block-outline-style>.cursor{box-sizing:border-box;background:transparent!important;border-style:solid;border-width:1px}.monaco-editor .cursors-layer.cursor-underline-style>.cursor{border-bottom-width:2px;border-bottom-style:solid;background:transparent!important;box-sizing:border-box}.monaco-editor .cursors-layer.cursor-underline-thin-style>.cursor{border-bottom-width:1px;border-bottom-style:solid;background:transparent!important;box-sizing:border-box}@keyframes monaco-cursor-smooth{0%,20%{opacity:1}60%,to{opacity:0}}@keyframes monaco-cursor-phase{0%,20%{opacity:1}90%,to{opacity:0}}@keyframes monaco-cursor-expand{0%,20%{transform:scaleY(1)}80%,to{transform:scaleY(0)}}.cursor-smooth{animation:monaco-cursor-smooth .5s ease-in-out 0s 20 alternate}.cursor-phase{animation:monaco-cursor-phase .5s ease-in-out 0s 20 alternate}.cursor-expand>.cursor{animation:monaco-cursor-expand .5s ease-in-out 0s 20 alternate}.monaco-diff-editor .diffOverview{z-index:9}.monaco-diff-editor .diffOverview .diffViewport{z-index:10}.monaco-diff-editor.vs .diffOverview{background:rgba(0,0,0,.03)}.monaco-diff-editor.vs-dark .diffOverview{background:hsla(0,0%,100%,.01)}.monaco-scrollable-element.modified-in-monaco-diff-editor.vs-dark .scrollbar,.monaco-scrollable-element.modified-in-monaco-diff-editor.vs .scrollbar{background:transparent}.monaco-scrollable-element.modified-in-monaco-diff-editor.hc-black .scrollbar{background:none}.monaco-scrollable-element.modified-in-monaco-diff-editor .slider{z-index:10}.modified-in-monaco-diff-editor .slider.active{background:hsla(0,0%,67.1%,.4)}.modified-in-monaco-diff-editor.hc-black .slider.active{background:none}.monaco-diff-editor .delete-sign,.monaco-diff-editor .insert-sign,.monaco-editor .delete-sign,.monaco-editor .insert-sign{font-size:11px!important;opacity:.7!important;display:flex!important;align-items:center}.monaco-diff-editor.hc-black .delete-sign,.monaco-diff-editor.hc-black .insert-sign,.monaco-editor.hc-black .delete-sign,.monaco-editor.hc-black .insert-sign{opacity:1}.monaco-editor .inline-added-margin-view-zone,.monaco-editor .inline-deleted-margin-view-zone{text-align:right}.monaco-editor .view-zones .view-lines .view-line span{display:inline-block}.monaco-editor .margin-view-zones .lightbulb-glyph:hover{cursor:pointer}.monaco-diff-editor .diff-review-line-number{text-align:right;display:inline-block}.monaco-diff-editor .diff-review{position:absolute;user-select:none;-webkit-user-select:none;-ms-user-select:none}.monaco-diff-editor .diff-review-summary{padding-left:10px}.monaco-diff-editor .diff-review-shadow{position:absolute}.monaco-diff-editor .diff-review-row{white-space:pre}.monaco-diff-editor .diff-review-table{display:table;min-width:100%}.monaco-diff-editor .diff-review-row{display:table-row;width:100%}.monaco-diff-editor .diff-review-spacer{display:inline-block;width:10px;vertical-align:middle}.monaco-diff-editor .diff-review-spacer>.codicon{font-size:9px!important}.monaco-diff-editor .diff-review-actions{display:inline-block;position:absolute;right:10px;top:2px}.monaco-diff-editor .diff-review-actions .action-label{width:16px;height:16px;margin:2px 0}::-ms-clear{display:none}.monaco-editor .editor-widget input{color:inherit}.monaco-editor{position:relative;overflow:visible;-webkit-text-size-adjust:100%}.monaco-editor .overflow-guard{position:relative;overflow:hidden}.monaco-editor .view-overlays{position:absolute;top:0}.monaco-editor .selection-anchor{background-color:#007acc;width:2px!important}.monaco-editor .bracket-match{box-sizing:border-box}.monaco-editor .contentWidgets .codicon-light-bulb,.monaco-editor .contentWidgets .codicon-lightbulb-autofix{display:flex;align-items:center;justify-content:center}.monaco-editor .contentWidgets .codicon-light-bulb:hover,.monaco-editor .contentWidgets .codicon-lightbulb-autofix:hover{cursor:pointer}.monaco-editor .codelens-decoration{overflow:hidden;display:inline-block;text-overflow:ellipsis;white-space:nowrap;color:var(--vscode-editorCodeLens-foreground)}.monaco-editor .codelens-decoration>a,.monaco-editor .codelens-decoration>span{user-select:none;-webkit-user-select:none;-ms-user-select:none;white-space:nowrap;vertical-align:sub}.monaco-editor .codelens-decoration>a{text-decoration:none}.monaco-editor .codelens-decoration>a:hover{cursor:pointer}.monaco-editor .codelens-decoration>a:hover,.monaco-editor .codelens-decoration>a:hover .codicon{color:var(--vscode-editorLink-activeForeground)!important}.monaco-editor .codelens-decoration .codicon{vertical-align:middle;color:currentColor!important;color:var(--vscode-editorCodeLens-foreground)}.monaco-editor .codelens-decoration>a:hover .codicon:before{cursor:pointer}@keyframes fadein{0%{opacity:0;visibility:visible}to{opacity:1}}.monaco-editor .codelens-decoration.fadein{animation:fadein .1s linear}.colorpicker-widget{height:190px;user-select:none;-webkit-user-select:none;-ms-user-select:none}.colorpicker-color-decoration{border:.1em solid #000;box-sizing:border-box;margin:.1em .2em 0;width:.8em;height:.8em;line-height:.8em;display:inline-block;cursor:pointer}.hc-black .colorpicker-color-decoration,.vs-dark .colorpicker-color-decoration{border:.1em solid #eee}.colorpicker-header{display:flex;height:24px;position:relative;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTZEaa/1AAAAHUlEQVQYV2PYvXu3JAi7uLiAMaYAjAGTQBPYLQkAa/0Zef3qRswAAAAASUVORK5CYII=);background-size:9px 9px;image-rendering:pixelated}.colorpicker-header .picked-color{width:216px;display:flex;align-items:center;justify-content:center;line-height:24px;cursor:pointer;color:#fff;flex:1}.colorpicker-header .picked-color .codicon{color:inherit;font-size:14px;position:absolute;left:8px}.colorpicker-header .picked-color.light{color:#000}.colorpicker-header .original-color{width:74px;z-index:inherit;cursor:pointer}.colorpicker-body{display:flex;padding:8px;position:relative}.colorpicker-body .saturation-wrap{overflow:hidden;height:150px;position:relative;min-width:220px;flex:1}.colorpicker-body .saturation-box{height:150px;position:absolute}.colorpicker-body .saturation-selection{width:9px;height:9px;margin:-5px 0 0 -5px;border:1px solid #fff;border-radius:100%;box-shadow:0 0 2px rgba(0,0,0,.8);position:absolute}.colorpicker-body .strip{width:25px;height:150px}.colorpicker-body .hue-strip{position:relative;margin-left:8px;cursor:grab;background:linear-gradient(180deg,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red)}.colorpicker-body .opacity-strip{position:relative;margin-left:8px;cursor:grab;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTZEaa/1AAAAHUlEQVQYV2PYvXu3JAi7uLiAMaYAjAGTQBPYLQkAa/0Zef3qRswAAAAASUVORK5CYII=);background-size:9px 9px;image-rendering:pixelated}.colorpicker-body .strip.grabbing{cursor:grabbing}.colorpicker-body .slider{position:absolute;top:0;left:-2px;width:calc(100% + 4px);height:4px;box-sizing:border-box;border:1px solid hsla(0,0%,100%,.71);box-shadow:0 0 1px rgba(0,0,0,.85)}.colorpicker-body .strip .overlay{height:150px;pointer-events:none}.monaco-editor.vs .dnd-target{border-right:2px dotted #000;color:#fff}.monaco-editor.vs-dark .dnd-target{border-right:2px dotted #aeafad;color:#51504f}.monaco-editor.hc-black .dnd-target{border-right:2px dotted #fff;color:#000}.monaco-editor.hc-black.mac.mouse-default .view-lines,.monaco-editor.mouse-default .view-lines,.monaco-editor.vs-dark.mac.mouse-default .view-lines{cursor:default}.monaco-editor.hc-black.mac.mouse-copy .view-lines,.monaco-editor.mouse-copy .view-lines,.monaco-editor.vs-dark.mac.mouse-copy .view-lines{cursor:copy}.monaco-editor .find-widget{position:absolute;z-index:35;height:33px;overflow:hidden;line-height:19px;transition:transform .2s linear;padding:0 4px;box-sizing:border-box;transform:translateY(calc(-100% - 10px))}.monaco-editor .find-widget textarea{margin:0}.monaco-editor .find-widget.hiddenEditor{display:none}.monaco-editor .find-widget.replaceToggled>.replace-part{display:flex}.monaco-editor .find-widget.visible{transform:translateY(0)}.monaco-editor .find-widget .monaco-inputbox.synthetic-focus{outline:1px solid -webkit-focus-ring-color;outline-offset:-1px}.monaco-editor .find-widget .monaco-inputbox .input{background-color:transparent;min-height:0}.monaco-editor .find-widget .monaco-findInput .input{font-size:13px}.monaco-editor .find-widget>.find-part,.monaco-editor .find-widget>.replace-part{margin:4px 0 0 17px;font-size:12px;display:flex}.monaco-editor .find-widget>.find-part .monaco-inputbox,.monaco-editor .find-widget>.replace-part .monaco-inputbox{min-height:25px}.monaco-editor .find-widget>.replace-part .monaco-inputbox>.ibwrapper>.mirror{padding-right:22px}.monaco-editor .find-widget>.find-part .monaco-inputbox>.ibwrapper>.input,.monaco-editor .find-widget>.find-part .monaco-inputbox>.ibwrapper>.mirror,.monaco-editor .find-widget>.replace-part .monaco-inputbox>.ibwrapper>.input,.monaco-editor .find-widget>.replace-part .monaco-inputbox>.ibwrapper>.mirror{padding-top:2px;padding-bottom:2px}.monaco-editor .find-widget>.find-part .find-actions,.monaco-editor .find-widget>.replace-part .replace-actions{height:25px;display:flex;align-items:center}.monaco-editor .find-widget .monaco-findInput{vertical-align:middle;display:flex;flex:1}.monaco-editor .find-widget .monaco-findInput .monaco-scrollable-element{width:100%}.monaco-editor .find-widget .monaco-findInput .monaco-scrollable-element .scrollbar.vertical{opacity:0}.monaco-editor .find-widget .matchesCount{display:flex;flex:initial;margin:0 0 0 3px;padding:2px 0 0 2px;height:25px;vertical-align:middle;box-sizing:border-box;text-align:center;line-height:23px}.monaco-editor .find-widget .button{width:16px;height:16px;padding:3px;border-radius:5px;flex:initial;margin-left:3px;background-position:50%;background-repeat:no-repeat;cursor:pointer;display:flex;align-items:center;justify-content:center}.monaco-editor .find-widget .codicon-find-selection{width:22px;height:22px;padding:3px;border-radius:5px}.monaco-editor .find-widget .button.left{margin-left:0;margin-right:3px}.monaco-editor .find-widget .button.wide{width:auto;padding:1px 6px;top:-1px}.monaco-editor .find-widget .button.toggle{position:absolute;top:0;left:3px;width:18px;height:100%;border-radius:0;box-sizing:border-box}.monaco-editor .find-widget .button.toggle.disabled{display:none}.monaco-editor .find-widget .disabled{opacity:.3;cursor:default}.monaco-editor .find-widget>.replace-part{display:none}.monaco-editor .find-widget>.replace-part>.monaco-findInput{position:relative;display:flex;vertical-align:middle;flex:auto;flex-grow:0;flex-shrink:0}.monaco-editor .find-widget>.replace-part>.monaco-findInput>.controls{position:absolute;top:3px;right:2px}.monaco-editor .find-widget.reduced-find-widget .matchesCount{display:none}.monaco-editor .find-widget.narrow-find-widget{max-width:257px!important}.monaco-editor .find-widget.collapsed-find-widget{max-width:170px!important}.monaco-editor .find-widget.collapsed-find-widget .button.next,.monaco-editor .find-widget.collapsed-find-widget .button.previous,.monaco-editor .find-widget.collapsed-find-widget .button.replace,.monaco-editor .find-widget.collapsed-find-widget .button.replace-all,.monaco-editor .find-widget.collapsed-find-widget>.find-part .monaco-findInput .controls{display:none}.monaco-editor .findMatch{animation-duration:0;animation-name:inherit!important}.monaco-editor .find-widget .monaco-sash{left:0!important}.monaco-editor.hc-black .find-widget .button:before{position:relative;top:1px;left:2px}.monaco-editor .margin-view-overlays .codicon-folding-collapsed,.monaco-editor .margin-view-overlays .codicon-folding-expanded{cursor:pointer;opacity:0;transition:opacity .5s;display:flex;align-items:center;justify-content:center;font-size:140%;margin-left:2px}.monaco-editor .margin-view-overlays .codicon.alwaysShowFoldIcons,.monaco-editor .margin-view-overlays .codicon.codicon-folding-collapsed,.monaco-editor .margin-view-overlays:hover .codicon{opacity:1}.monaco-editor .inline-folded:after{color:grey;margin:.1em .2em 0;content:"⋯";display:inline;line-height:1em;cursor:pointer}.monaco-editor .peekview-widget .head .peekview-title .severity-icon{display:inline-block;vertical-align:text-top;margin-right:4px}.monaco-editor .marker-widget{text-overflow:ellipsis;white-space:nowrap}.monaco-editor .marker-widget>.stale{opacity:.6;font-style:italic}.monaco-editor .marker-widget .title{display:inline-block;padding-right:5px}.monaco-editor .marker-widget .descriptioncontainer{position:absolute;white-space:pre;user-select:text;-webkit-user-select:text;-ms-user-select:text;padding:8px 12px 0 20px}.monaco-editor .marker-widget .descriptioncontainer .message{display:flex;flex-direction:column}.monaco-editor .marker-widget .descriptioncontainer .message .details{padding-left:6px}.monaco-editor .marker-widget .descriptioncontainer .message .source,.monaco-editor .marker-widget .descriptioncontainer .message span.code{opacity:.6}.monaco-editor .marker-widget .descriptioncontainer .message a.code-link{opacity:.6;color:inherit}.monaco-editor .marker-widget .descriptioncontainer .message a.code-link:before{content:"("}.monaco-editor .marker-widget .descriptioncontainer .message a.code-link:after{content:")"}.monaco-editor .marker-widget .descriptioncontainer .message a.code-link>span{text-decoration:underline;border-bottom:1px solid transparent;text-underline-position:under;color:var(--vscode-textLink-foreground);color:var(--vscode-textLink-activeForeground)}.monaco-editor .marker-widget .descriptioncontainer .filename{cursor:pointer}.monaco-editor .goto-definition-link{text-decoration:underline;cursor:pointer}.monaco-editor .zone-widget .zone-widget-container.reference-zone-widget{border-top-width:1px;border-bottom-width:1px}.monaco-editor .reference-zone-widget .inline{display:inline-block;vertical-align:top}.monaco-editor .reference-zone-widget .messages{height:100%;width:100%;text-align:center;padding:3em 0}.monaco-editor .reference-zone-widget .ref-tree{line-height:23px;background-color:var(--vscode-peekViewResult-background);color:var(--vscode-peekViewResult-lineForeground)}.monaco-editor .reference-zone-widget .ref-tree .reference{text-overflow:ellipsis;overflow:hidden}.monaco-editor .reference-zone-widget .ref-tree .reference-file{display:inline-flex;width:100%;height:100%;color:var(--vscode-peekViewResult-fileForeground)}.monaco-editor .reference-zone-widget .ref-tree .monaco-list:focus .selected .reference-file{color:inherit!important}.monaco-editor .reference-zone-widget .ref-tree .monaco-list:focus .monaco-list-rows>.monaco-list-row.selected:not(.highlighted){background-color:var(--vscode-peekViewResult-selectionBackground);color:var(--vscode-peekViewResult-selectionForeground)!important}.monaco-editor .reference-zone-widget .ref-tree .reference-file .count{margin-right:12px;margin-left:auto}.monaco-editor .reference-zone-widget .ref-tree .referenceMatch .highlight{background-color:var(--vscode-peekViewResult-matchHighlightBackground)}.monaco-editor .reference-zone-widget .preview .reference-decoration{background-color:var(--vscode-peekViewEditor-matchHighlightBackground);border:2px solid var(--vscode-peekViewEditor-matchHighlightBorder);box-sizing:border-box}.monaco-editor .reference-zone-widget .preview .monaco-editor .inputarea.ime-input,.monaco-editor .reference-zone-widget .preview .monaco-editor .monaco-editor-background{background-color:var(--vscode-peekViewEditor-background)}.monaco-editor .reference-zone-widget .preview .monaco-editor .margin{background-color:var(--vscode-peekViewEditorGutter-background)}.monaco-editor.hc-black .reference-zone-widget .ref-tree .reference-file{font-weight:700}.monaco-editor.hc-black .reference-zone-widget .ref-tree .referenceMatch .highlight{border:1px dotted var(--vscode-contrastActiveBorder,transparent);box-sizing:border-box}.monaco-editor .suggest-preview-additional-widget{white-space:nowrap}.monaco-editor .suggest-preview-additional-widget .content-spacer{color:transparent;white-space:pre}.monaco-editor .suggest-preview-additional-widget .button{display:inline-block;cursor:pointer;text-decoration:underline;text-underline-position:under}.monaco-editor .ghost-text-hidden{opacity:0;font-size:0}.monaco-editor .ghost-text-decoration,.monaco-editor .suggest-preview-text{font-style:italic}.monaco-editor .detected-link,.monaco-editor .detected-link-active{text-decoration:underline;text-underline-position:under}.monaco-editor .detected-link-active{cursor:pointer}.monaco-editor .monaco-editor-overlaymessage{padding-bottom:8px;z-index:10000}.monaco-editor .monaco-editor-overlaymessage.below{padding-bottom:0;padding-top:8px;z-index:10000}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.monaco-editor .monaco-editor-overlaymessage.fadeIn{animation:fadeIn .15s ease-out}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.monaco-editor .monaco-editor-overlaymessage.fadeOut{animation:fadeOut .1s ease-out}.monaco-editor .monaco-editor-overlaymessage .message{padding:1px 4px;color:var(--vscode-inputValidation-infoForeground);background-color:var(--vscode-inputValidation-infoBackground);border:1px solid var(--vscode-inputValidation-infoBorder)}.monaco-editor.hc-black .monaco-editor-overlaymessage .message{border-width:2px}.monaco-editor .monaco-editor-overlaymessage .anchor{width:0!important;height:0!important;z-index:1000;border:8px solid transparent;position:absolute}.monaco-editor .monaco-editor-overlaymessage .anchor.top{border-bottom-color:var(--vscode-inputValidation-infoBorder)}.monaco-editor .monaco-editor-overlaymessage .anchor.below{border-top-color:var(--vscode-inputValidation-infoBorder)}.monaco-editor .monaco-editor-overlaymessage.below .anchor.below,.monaco-editor .monaco-editor-overlaymessage:not(.below) .anchor.top{display:none}.monaco-editor .monaco-editor-overlaymessage.below .anchor.top{display:inherit;top:-8px}.monaco-editor .parameter-hints-widget{z-index:39;display:flex;flex-direction:column;line-height:1.5em}.monaco-editor .parameter-hints-widget>.phwrapper{max-width:440px;display:flex;flex-direction:row}.monaco-editor .parameter-hints-widget.multiple{min-height:3.3em;padding:0}.monaco-editor .parameter-hints-widget.visible{transition:left .05s ease-in-out}.monaco-editor .parameter-hints-widget p,.monaco-editor .parameter-hints-widget ul{margin:8px 0}.monaco-editor .parameter-hints-widget .body,.monaco-editor .parameter-hints-widget .monaco-scrollable-element{display:flex;flex:1;flex-direction:column;min-height:100%}.monaco-editor .parameter-hints-widget .signature{padding:4px 5px}.monaco-editor .parameter-hints-widget .docs{padding:0 10px 0 5px;white-space:pre-wrap}.monaco-editor .parameter-hints-widget .docs.empty{display:none}.monaco-editor .parameter-hints-widget .docs .markdown-docs{white-space:normal}.monaco-editor .parameter-hints-widget .docs .markdown-docs code{font-family:var(--monaco-monospace-font)}.monaco-editor .parameter-hints-widget .docs .code,.monaco-editor .parameter-hints-widget .docs .monaco-tokenized-source{white-space:pre-wrap}.monaco-editor .parameter-hints-widget .docs code{border-radius:3px;padding:0 .4em}.monaco-editor .parameter-hints-widget .controls{display:none;flex-direction:column;align-items:center;min-width:22px;justify-content:flex-end}.monaco-editor .parameter-hints-widget.multiple .controls{display:flex;padding:0 2px}.monaco-editor .parameter-hints-widget.multiple .button{width:16px;height:16px;background-repeat:no-repeat;cursor:pointer}.monaco-editor .parameter-hints-widget .button.previous{bottom:24px}.monaco-editor .parameter-hints-widget .overloads{text-align:center;height:12px;line-height:12px;font-family:var(--monaco-monospace-font)}.monaco-editor .parameter-hints-widget .signature .parameter.active{font-weight:700}.monaco-editor .parameter-hints-widget .documentation-parameter>.parameter{font-weight:700;margin-right:.5em}.monaco-editor .peekview-widget .head{box-sizing:border-box;display:flex;justify-content:space-between;flex-wrap:nowrap}.monaco-editor .peekview-widget .head .peekview-title{display:flex;align-items:center;font-size:13px;margin-left:20px;min-width:0;text-overflow:ellipsis;overflow:hidden}.monaco-editor .peekview-widget .head .peekview-title.clickable{cursor:pointer}.monaco-editor .peekview-widget .head .peekview-title .dirname:not(:empty){font-size:.9em;margin-left:.5em;text-overflow:ellipsis;overflow:hidden}.monaco-editor .peekview-widget .head .peekview-title .meta{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.monaco-editor .peekview-widget .head .peekview-title .dirname{white-space:nowrap}.monaco-editor .peekview-widget .head .peekview-title .filename{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.monaco-editor .peekview-widget .head .peekview-title .meta:not(:empty):before{content:"-";padding:0 .3em}.monaco-editor .peekview-widget .head .peekview-actions{flex:1;text-align:right;padding-right:2px}.monaco-editor .peekview-widget .head .peekview-actions>.monaco-action-bar{display:inline-block}.monaco-editor .peekview-widget .head .peekview-actions>.monaco-action-bar,.monaco-editor .peekview-widget .head .peekview-actions>.monaco-action-bar>.actions-container{height:100%}.monaco-editor .peekview-widget>.body{border-top:1px solid;position:relative}.monaco-editor .peekview-widget .head .peekview-title .codicon{margin-right:4px}.monaco-editor .peekview-widget .monaco-list .monaco-list-row.focused .codicon{color:inherit!important}.monaco-editor .rename-box{z-index:100;color:inherit}.monaco-editor .rename-box.preview{padding:3px 3px 0}.monaco-editor .rename-box .rename-input{padding:3px;width:calc(100% - 6px)}.monaco-editor .rename-box .rename-label{display:none;opacity:.8}.monaco-editor .rename-box.preview .rename-label{display:inherit}.monaco-editor .snippet-placeholder{min-width:2px;outline-style:solid;outline-width:1px;background-color:var(--vscode-editor-snippetTabstopHighlightBackground,transparent);outline-color:var(--vscode-editor-snippetTabstopHighlightBorder,transparent)}.monaco-editor .finish-snippet-placeholder{outline-style:solid;outline-width:1px;background-color:var(--vscode-editor-snippetFinalTabstopHighlightBackground,transparent);outline-color:var(--vscode-editor-snippetFinalTabstopHighlightBorder,transparent)}.monaco-editor .suggest-widget{width:430px;z-index:40;display:flex;flex-direction:column}.monaco-editor .suggest-widget.message{flex-direction:row;align-items:center}.monaco-editor .suggest-details,.monaco-editor .suggest-widget{flex:0 1 auto;width:100%;border:1px solid var(--vscode-editorSuggestWidget-border);background-color:var(--vscode-editorSuggestWidget-background)}.monaco-editor.hc-black .suggest-details,.monaco-editor.hc-black .suggest-widget{border-width:2px}.monaco-editor .suggest-widget .suggest-status-bar{box-sizing:border-box;display:none;flex-flow:row nowrap;justify-content:space-between;width:100%;font-size:80%;padding:0 4px;border-top:1px solid var(--vscode-editorSuggestWidget-border);overflow:hidden}.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar{display:flex}.monaco-editor .suggest-widget .suggest-status-bar .left{padding-right:8px}.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar .action-label{color:var(--vscode-editorSuggestWidgetStatus-foreground)}.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar .action-item:not(:last-of-type) .action-label{margin-right:0}.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar .action-item:not(:last-of-type) .action-label:after{content:", ";margin-right:.3em}.monaco-editor .suggest-widget.with-status-bar .monaco-list .monaco-list-row.focused.string-label>.contents>.main>.right>.readMore,.monaco-editor .suggest-widget.with-status-bar .monaco-list .monaco-list-row>.contents>.main>.right>.readMore{display:none}.monaco-editor .suggest-widget.with-status-bar:not(.docs-side) .monaco-list .monaco-list-row:hover>.contents>.main>.right.can-expand-details>.details-label{width:100%}.monaco-editor .suggest-widget>.message{padding-left:22px}.monaco-editor .suggest-widget>.tree{height:100%;width:100%}.monaco-editor .suggest-widget .monaco-list{user-select:none;-webkit-user-select:none;-ms-user-select:none}.monaco-editor .suggest-widget .monaco-list .monaco-list-row{display:flex;-mox-box-sizing:border-box;box-sizing:border-box;padding-right:10px;background-repeat:no-repeat;background-position:2px 2px;white-space:nowrap;cursor:pointer;touch-action:none}.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused{color:var(--vscode-editorSuggestWidget-selectedForeground)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused .codicon{color:var(--vscode-editorSuggestWidget-selectedIconForeground)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents{flex:1;height:100%;overflow:hidden;padding-left:2px}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main{display:flex;overflow:hidden;text-overflow:ellipsis;white-space:pre;justify-content:space-between}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.left,.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right{display:flex}.monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.focused)>.contents>.main .monaco-icon-label{color:var(--vscode-editorSuggestWidget-foreground)}.monaco-editor .suggest-widget:not(.frozen) .monaco-highlighted-label .highlight{font-weight:700}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main .monaco-highlighted-label .highlight{color:var(--vscode-editorSuggestWidget-highlightForeground)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused>.contents>.main .monaco-highlighted-label .highlight{color:var(--vscode-editorSuggestWidget-focusHighlightForeground)}.monaco-editor .suggest-details>.monaco-scrollable-element>.body>.header>.codicon-close,.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right>.readMore:before{color:inherit;opacity:1;font-size:14px;cursor:pointer}.monaco-editor .suggest-details>.monaco-scrollable-element>.body>.header>.codicon-close{position:absolute;top:6px;right:2px}.monaco-editor .suggest-details>.monaco-scrollable-element>.body>.header>.codicon-close:hover,.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right>.readMore:hover{opacity:1}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right>.details-label{opacity:.7}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.left>.signature-label{overflow:hidden;text-overflow:ellipsis;opacity:.6}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.left>.qualifier-label{margin-left:12px;opacity:.4;font-size:85%;line-height:normal;text-overflow:ellipsis;overflow:hidden;align-self:center}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right>.details-label{font-size:85%;margin-left:1.1em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right>.details-label>.monaco-tokenized-source{display:inline}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right>.details-label{display:none}.monaco-editor .suggest-widget.docs-side .monaco-list .monaco-list-row.focused:not(.string-label)>.contents>.main>.right>.details-label,.monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label)>.contents>.main>.right>.details-label,.monaco-editor .suggest-widget:not(.shows-details) .monaco-list .monaco-list-row.focused>.contents>.main>.right>.details-label{display:inline}.monaco-editor .suggest-widget:not(.docs-side) .monaco-list .monaco-list-row.focused:hover>.contents>.main>.right.can-expand-details>.details-label{width:calc(100% - 26px)}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.left{flex-shrink:1;flex-grow:1;overflow:hidden}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.left>.monaco-icon-label{flex-shrink:0}.monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label)>.contents>.main>.left>.monaco-icon-label{max-width:100%}.monaco-editor .suggest-widget .monaco-list .monaco-list-row.string-label>.contents>.main>.left>.monaco-icon-label{flex-shrink:1}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right{overflow:hidden;flex-shrink:4;max-width:70%}.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right>.readMore{display:inline-block;position:absolute;right:10px;width:18px;height:18px;visibility:hidden}.monaco-editor .suggest-widget.docs-side .monaco-list .monaco-list-row>.contents>.main>.right>.readMore{display:none!important}.monaco-editor .suggest-widget .monaco-list .monaco-list-row.string-label>.contents>.main>.right>.readMore{display:none}.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused.string-label>.contents>.main>.right>.readMore{display:inline-block}.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused:hover>.contents>.main>.right>.readMore{visibility:visible}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .monaco-icon-label.deprecated{opacity:.66;text-decoration:unset}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .monaco-icon-label.deprecated>.monaco-icon-label-container>.monaco-icon-name-container{text-decoration:line-through}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .monaco-icon-label:before{height:100%}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .icon{display:block;height:16px;width:16px;margin-left:2px;background-repeat:no-repeat;background-size:80%;background-position:50%}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .icon.hide{display:none}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon{display:flex;align-items:center;margin-right:4px}.monaco-editor .suggest-widget.no-icons .monaco-list .monaco-list-row .icon,.monaco-editor .suggest-widget.no-icons .monaco-list .monaco-list-row .suggest-icon:before{display:none}.monaco-editor .suggest-widget .monaco-list .monaco-list-row .icon.customcolor .colorspan{margin:0 0 0 .3em;border:.1em solid #000;width:.7em;height:.7em;display:inline-block}.monaco-editor .suggest-details-container{z-index:41}.monaco-editor .suggest-details{display:flex;flex-direction:column;cursor:default;color:var(--vscode-editorSuggestWidget-foreground)}.monaco-editor .suggest-details.focused{border-color:var(--vscode-focusBorder)}.monaco-editor .suggest-details a{color:var(--vscode-textLink-foreground)}.monaco-editor .suggest-details a:hover{color:var(--vscode-textLink-activeForeground)}.monaco-editor .suggest-details code{background-color:var(--vscode-textCodeBlock-background)}.monaco-editor .suggest-details.no-docs{display:none}.monaco-editor .suggest-details>.monaco-scrollable-element{flex:1}.monaco-editor .suggest-details>.monaco-scrollable-element>.body{box-sizing:border-box;height:100%;width:100%}.monaco-editor .suggest-details>.monaco-scrollable-element>.body>.header>.type{flex:2;overflow:hidden;text-overflow:ellipsis;opacity:.7;white-space:pre;margin:0 24px 0 0;padding:4px 0 12px 5px}.monaco-editor .suggest-details>.monaco-scrollable-element>.body>.header>.type.auto-wrap{white-space:normal;word-break:break-all}.monaco-editor .suggest-details>.monaco-scrollable-element>.body>.docs{margin:0;padding:4px 5px;white-space:pre-wrap}.monaco-editor .suggest-details.no-type>.monaco-scrollable-element>.body>.docs{margin-right:24px;overflow:hidden}.monaco-editor .suggest-details>.monaco-scrollable-element>.body>.docs.markdown-docs{padding:0;white-space:normal;min-height:calc(1rem + 8px)}.monaco-editor .suggest-details>.monaco-scrollable-element>.body>.docs.markdown-docs>div,.monaco-editor .suggest-details>.monaco-scrollable-element>.body>.docs.markdown-docs>span:not(:empty){padding:4px 5px}.monaco-editor .suggest-details>.monaco-scrollable-element>.body>.docs.markdown-docs>div>p:first-child{margin-top:0}.monaco-editor .suggest-details>.monaco-scrollable-element>.body>.docs.markdown-docs>div>p:last-child{margin-bottom:0}.monaco-editor .suggest-details>.monaco-scrollable-element>.body>.docs.markdown-docs .monaco-tokenized-source{white-space:pre}.monaco-editor .suggest-details>.monaco-scrollable-element>.body>.docs .code{white-space:pre-wrap;word-wrap:break-word}.monaco-editor .suggest-details>.monaco-scrollable-element>.body>.docs.markdown-docs .codicon{vertical-align:sub}.monaco-editor .suggest-details>.monaco-scrollable-element>.body>p:empty{display:none}.monaco-editor .suggest-details code{border-radius:3px;padding:0 .4em}.monaco-editor .suggest-details ol,.monaco-editor .suggest-details ul{padding-left:20px}.monaco-editor .suggest-details p code{font-family:var(--monaco-monospace-font)}.editor-banner{box-sizing:border-box;cursor:default;width:100%;font-size:12px;display:flex;overflow:visible;height:26px;background:var(--vscode-banner-background)}.editor-banner .icon-container{display:flex;flex-shrink:0;align-items:center;padding:0 6px 0 10px}.editor-banner .icon-container.custom-icon{background-repeat:no-repeat;background-position:50%;background-size:16px;width:16px;padding:0;margin:0 6px 0 10px}.editor-banner .message-container{display:flex;align-items:center;line-height:26px;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.editor-banner .message-container p{margin-block-start:0;margin-block-end:0}.editor-banner .message-actions-container{flex-grow:1;flex-shrink:0;line-height:26px;margin:0 4px}.editor-banner .message-actions-container a.monaco-button{width:inherit;margin:2px 8px;padding:0 12px}.editor-banner .message-actions-container a{padding:3px;margin-left:12px;text-decoration:underline}.editor-banner .action-container{padding:0 10px 0 6px}.editor-banner{background-color:var(--vscode-banner-background)}.editor-banner,.editor-banner .action-container .codicon,.editor-banner .message-actions-container .monaco-link{color:var(--vscode-banner-foreground)}.editor-banner .icon-container .codicon{color:var(--vscode-banner-iconForeground)}.monaco-editor .unicode-highlight{border:1px solid var(--vscode-editorUnicodeHighlight-border);box-sizing:border-box}.monaco-editor .zone-widget{position:absolute;z-index:10}.monaco-editor .zone-widget .zone-widget-container{border-top-style:solid;border-bottom-style:solid;border-top-width:0;border-bottom-width:0;position:relative}.monaco-editor .accessibilityHelpWidget{padding:10px;vertical-align:middle;overflow:scroll}.monaco-editor .iPadShowKeyboard{width:58px;min-width:0;height:36px;min-height:0;margin:0;padding:0;position:absolute;resize:none;overflow:hidden;background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTMiIGhlaWdodD0iMzYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwKSI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00OC4wMzYgNC4wMUg0LjAwOFYzMi4wM2g0NC4wMjhWNC4wMXpNNC4wMDguMDA4QTQuMDAzIDQuMDAzIDAgMDAuMDA1IDQuMDFWMzIuMDNhNC4wMDMgNC4wMDMgMCAwMDQuMDAzIDQuMDAyaDQ0LjAyOGE0LjAwMyA0LjAwMyAwIDAwNC4wMDMtNC4wMDJWNC4wMUE0LjAwMyA0LjAwMyAwIDAwNDguMDM2LjAwOEg0LjAwOHpNOC4wMSA4LjAxM2g0LjAwM3Y0LjAwM0g4LjAxVjguMDEzem0xMi4wMDggMGgtNC4wMDJ2NC4wMDNoNC4wMDJWOC4wMTN6bTQuMDAzIDBoNC4wMDJ2NC4wMDNoLTQuMDAyVjguMDEzem0xMi4wMDggMGgtNC4wMDN2NC4wMDNoNC4wMDNWOC4wMTN6bTQuMDAyIDBoNC4wMDN2NC4wMDNINDAuMDNWOC4wMTN6bS0yNC4wMTUgOC4wMDVIOC4wMXY0LjAwM2g4LjAwNnYtNC4wMDN6bTQuMDAyIDBoNC4wMDN2NC4wMDNoLTQuMDAzdi00LjAwM3ptMTIuMDA4IDBoLTQuMDAzdjQuMDAzaDQuMDAzdi00LjAwM3ptMTIuMDA4IDB2NC4wMDNoLTguMDA1di00LjAwM2g4LjAwNXptLTMyLjAyMSA4LjAwNUg4LjAxdjQuMDAzaDQuMDAzdi00LjAwM3ptNC4wMDMgMGgyMC4wMTN2NC4wMDNIMTYuMDE2di00LjAwM3ptMjguMDE4IDBINDAuMDN2NC4wMDNoNC4wMDN2LTQuMDAzeiIgZmlsbD0iIzQyNDI0MiIvPjwvZz48ZGVmcz48Y2xpcFBhdGggaWQ9ImNsaXAwIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMCAwaDUzdjM2SDB6Ii8+PC9jbGlwUGF0aD48L2RlZnM+PC9zdmc+) 50% no-repeat;border:4px solid #f6f6f6;border-radius:4px}.monaco-editor.vs-dark .iPadShowKeyboard{background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTMiIGhlaWdodD0iMzYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwKSI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00OC4wMzYgNC4wMUg0LjAwOFYzMi4wM2g0NC4wMjhWNC4wMXpNNC4wMDguMDA4QTQuMDAzIDQuMDAzIDAgMDAuMDA1IDQuMDFWMzIuMDNhNC4wMDMgNC4wMDMgMCAwMDQuMDAzIDQuMDAyaDQ0LjAyOGE0LjAwMyA0LjAwMyAwIDAwNC4wMDMtNC4wMDJWNC4wMUE0LjAwMyA0LjAwMyAwIDAwNDguMDM2LjAwOEg0LjAwOHpNOC4wMSA4LjAxM2g0LjAwM3Y0LjAwM0g4LjAxVjguMDEzem0xMi4wMDggMGgtNC4wMDJ2NC4wMDNoNC4wMDJWOC4wMTN6bTQuMDAzIDBoNC4wMDJ2NC4wMDNoLTQuMDAyVjguMDEzem0xMi4wMDggMGgtNC4wMDN2NC4wMDNoNC4wMDNWOC4wMTN6bTQuMDAyIDBoNC4wMDN2NC4wMDNINDAuMDNWOC4wMTN6bS0yNC4wMTUgOC4wMDVIOC4wMXY0LjAwM2g4LjAwNnYtNC4wMDN6bTQuMDAyIDBoNC4wMDN2NC4wMDNoLTQuMDAzdi00LjAwM3ptMTIuMDA4IDBoLTQuMDAzdjQuMDAzaDQuMDAzdi00LjAwM3ptMTIuMDA4IDB2NC4wMDNoLTguMDA1di00LjAwM2g4LjAwNXptLTMyLjAyMSA4LjAwNUg4LjAxdjQuMDAzaDQuMDAzdi00LjAwM3ptNC4wMDMgMGgyMC4wMTN2NC4wMDNIMTYuMDE2di00LjAwM3ptMjguMDE4IDBINDAuMDN2NC4wMDNoNC4wMDN2LTQuMDAzeiIgZmlsbD0iI0M1QzVDNSIvPjwvZz48ZGVmcz48Y2xpcFBhdGggaWQ9ImNsaXAwIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMCAwaDUzdjM2SDB6Ii8+PC9jbGlwUGF0aD48L2RlZnM+PC9zdmc+) 50% no-repeat;border:4px solid #252526}.monaco-editor .tokens-inspect-widget{z-index:50;user-select:text;-webkit-user-select:text;-ms-user-select:text;padding:10px}.tokens-inspect-separator{height:1px;border:0}.monaco-editor .tokens-inspect-widget .tm-token{font-family:var(--monaco-monospace-font)}.monaco-editor .tokens-inspect-widget .tm-token-length{font-weight:400;font-size:60%;float:right}.monaco-editor .tokens-inspect-widget .tm-metadata-table{width:100%}.monaco-editor .tokens-inspect-widget .tm-metadata-value{font-family:var(--monaco-monospace-font);text-align:right}.monaco-editor .tokens-inspect-widget .tm-token-type{font-family:var(--monaco-monospace-font)}.quick-input-widget{font-size:13px}.quick-input-widget .monaco-highlighted-label .highlight{color:#0066bf}.vs .quick-input-widget .monaco-list-row.focused .monaco-highlighted-label .highlight{color:#9dddff}.vs-dark .quick-input-widget .monaco-highlighted-label .highlight{color:#0097fb}.hc-black .quick-input-widget .monaco-highlighted-label .highlight{color:#f38518}.monaco-keybinding>.monaco-keybinding-key{background-color:hsla(0,0%,86.7%,.4);border:1px solid hsla(0,0%,80%,.4);border-bottom-color:hsla(0,0%,73.3%,.4);box-shadow:inset 0 -1px 0 hsla(0,0%,73.3%,.4);color:#555}.hc-black .monaco-keybinding>.monaco-keybinding-key{background-color:transparent;border:1px solid #6fc3df;box-shadow:none;color:#fff}.vs-dark .monaco-keybinding>.monaco-keybinding-key{background-color:hsla(0,0%,50.2%,.17);border:1px solid rgba(51,51,51,.6);border-bottom-color:rgba(68,68,68,.6);box-shadow:inset 0 -1px 0 rgba(68,68,68,.6);color:#ccc}.monaco-editor{font-family:-apple-system,BlinkMacSystemFont,Segoe WPC,Segoe UI,HelveticaNeue-Light,system-ui,Ubuntu,Droid Sans,sans-serif;--monaco-monospace-font:"SF Mono",Monaco,Menlo,Consolas,"Ubuntu Mono","Liberation Mono","DejaVu Sans Mono","Courier New",monospace}.monaco-editor.hc-black .monaco-menu .monaco-action-bar.vertical .action-menu-item:focus .action-label,.monaco-editor.vs-dark .monaco-menu .monaco-action-bar.vertical .action-menu-item:focus .action-label,.monaco-menu .monaco-action-bar.vertical .action-item .action-menu-item:focus .action-label{stroke-width:1.2px}.monaco-hover p{margin:0}.monaco-aria-container{position:absolute!important;top:0;height:1px;width:1px;margin:-1px;overflow:hidden;padding:0;clip:rect(1px,1px,1px,1px);clip-path:inset(50%)}.monaco-editor.hc-black{-ms-high-contrast-adjust:none}@media screen and (-ms-high-contrast:active){.monaco-editor.vs-dark .view-overlays .current-line,.monaco-editor.vs .view-overlays .current-line{border-color:windowtext!important;border-left:0;border-right:0}.monaco-editor.vs-dark .cursor,.monaco-editor.vs .cursor{background-color:windowtext!important}.monaco-editor.vs-dark .dnd-target,.monaco-editor.vs .dnd-target{border-color:windowtext!important}.monaco-editor.vs-dark .selected-text,.monaco-editor.vs .selected-text{background-color:highlight!important}.monaco-editor.vs-dark .view-line,.monaco-editor.vs .view-line{-ms-high-contrast-adjust:none}.monaco-editor.vs-dark .view-line span,.monaco-editor.vs .view-line span{color:windowtext!important}.monaco-editor.vs-dark .view-line span.inline-selected-text,.monaco-editor.vs .view-line span.inline-selected-text{color:highlighttext!important}.monaco-editor.vs-dark .view-overlays,.monaco-editor.vs .view-overlays{-ms-high-contrast-adjust:none}.monaco-editor.vs-dark .reference-decoration,.monaco-editor.vs-dark .selectionHighlight,.monaco-editor.vs-dark .wordHighlight,.monaco-editor.vs-dark .wordHighlightStrong,.monaco-editor.vs .reference-decoration,.monaco-editor.vs .selectionHighlight,.monaco-editor.vs .wordHighlight,.monaco-editor.vs .wordHighlightStrong{border:2px dotted highlight!important;background:transparent!important;box-sizing:border-box}.monaco-editor.vs-dark .rangeHighlight,.monaco-editor.vs .rangeHighlight{background:transparent!important;border:1px dotted activeborder!important;box-sizing:border-box}.monaco-editor.vs-dark .bracket-match,.monaco-editor.vs .bracket-match{border-color:windowtext!important;background:transparent!important}.monaco-editor.vs-dark .currentFindMatch,.monaco-editor.vs-dark .findMatch,.monaco-editor.vs .currentFindMatch,.monaco-editor.vs .findMatch{border:2px dotted activeborder!important;background:transparent!important;box-sizing:border-box}.monaco-editor.vs-dark .find-widget,.monaco-editor.vs .find-widget{border:1px solid windowtext}.monaco-editor.vs-dark .monaco-list .monaco-list-row,.monaco-editor.vs .monaco-list .monaco-list-row{-ms-high-contrast-adjust:none;color:windowtext!important}.monaco-editor.vs-dark .monaco-list .monaco-list-row.focused,.monaco-editor.vs .monaco-list .monaco-list-row.focused{color:highlighttext!important;background-color:highlight!important}.monaco-editor.vs-dark .monaco-list .monaco-list-row:hover,.monaco-editor.vs .monaco-list .monaco-list-row:hover{background:transparent!important;border:1px solid highlight;box-sizing:border-box}.monaco-editor.vs-dark .monaco-scrollable-element>.scrollbar,.monaco-editor.vs .monaco-scrollable-element>.scrollbar{-ms-high-contrast-adjust:none;background:background!important;border:1px solid windowtext;box-sizing:border-box}.monaco-editor.vs-dark .monaco-scrollable-element>.scrollbar>.slider,.monaco-editor.vs .monaco-scrollable-element>.scrollbar>.slider{background:windowtext!important}.monaco-editor.vs-dark .monaco-scrollable-element>.scrollbar>.slider.active,.monaco-editor.vs-dark .monaco-scrollable-element>.scrollbar>.slider:hover,.monaco-editor.vs .monaco-scrollable-element>.scrollbar>.slider.active,.monaco-editor.vs .monaco-scrollable-element>.scrollbar>.slider:hover{background:highlight!important}.monaco-editor.vs-dark .decorationsOverviewRuler,.monaco-editor.vs .decorationsOverviewRuler{opacity:0}.monaco-editor.vs-dark .minimap,.monaco-editor.vs .minimap{display:none}.monaco-editor.vs-dark .squiggly-d-error,.monaco-editor.vs .squiggly-d-error{background:transparent!important;border-bottom:4px double #e47777}.monaco-editor.vs-dark .squiggly-b-info,.monaco-editor.vs-dark .squiggly-c-warning,.monaco-editor.vs .squiggly-b-info,.monaco-editor.vs .squiggly-c-warning{border-bottom:4px double #71b771}.monaco-editor.vs-dark .squiggly-a-hint,.monaco-editor.vs .squiggly-a-hint{border-bottom:4px double #6c6c6c}.monaco-editor.vs-dark .monaco-menu .monaco-action-bar.vertical .action-menu-item:focus .action-label,.monaco-editor.vs .monaco-menu .monaco-action-bar.vertical .action-menu-item:focus .action-label{-ms-high-contrast-adjust:none;color:highlighttext!important;background-color:highlight!important}.monaco-editor.vs-dark .monaco-menu .monaco-action-bar.vertical .action-menu-item:hover .action-label,.monaco-editor.vs .monaco-menu .monaco-action-bar.vertical .action-menu-item:hover .action-label{-ms-high-contrast-adjust:none;background:transparent!important;border:1px solid highlight;box-sizing:border-box}.monaco-diff-editor.vs-dark .diffOverviewRuler,.monaco-diff-editor.vs .diffOverviewRuler{display:none}.monaco-editor.vs-dark .line-delete,.monaco-editor.vs-dark .line-insert,.monaco-editor.vs .line-delete,.monaco-editor.vs .line-insert{background:transparent!important;border:1px solid highlight!important;box-sizing:border-box}.monaco-editor.vs-dark .char-delete,.monaco-editor.vs-dark .char-insert,.monaco-editor.vs .char-delete,.monaco-editor.vs .char-insert{background:transparent!important}}.monaco-action-bar .action-item.menu-entry .action-label.icon{width:16px;height:16px;background-repeat:no-repeat;background-position:50%;background-size:16px}.monaco-action-bar .action-item.menu-entry .action-label{background-image:var(--menu-entry-icon-light)}.hc-black .monaco-action-bar .action-item.menu-entry .action-label,.vs-dark .monaco-action-bar .action-item.menu-entry .action-label{background-image:var(--menu-entry-icon-dark)}.monaco-dropdown-with-default{display:flex!important;flex-direction:row;border-radius:5px}.monaco-dropdown-with-default>.action-container>.action-label{margin-right:0}.monaco-dropdown-with-default>.action-container.menu-entry>.action-label.icon{width:16px;height:16px;background-repeat:no-repeat;background-position:50%;background-size:16px}.monaco-dropdown-with-default>.action-container.menu-entry>.action-label{background-image:var(--menu-entry-icon-light)}.hc-black .monaco-dropdown-with-default>.action-container.menu-entry>.action-label,.vs-dark .monaco-dropdown-with-default>.action-container.menu-entry>.action-label{background-image:var(--menu-entry-icon-dark)}.monaco-dropdown-with-default>.dropdown-action-container>.monaco-dropdown>.dropdown-label .codicon[class*=codicon-]{font-size:12px;padding-left:0;padding-right:0;line-height:16px;margin-left:-3px}.monaco-dropdown-with-default>.dropdown-action-container>.monaco-dropdown>.dropdown-label>.action-label{display:block;background-size:16px;background-position:50%;background-repeat:no-repeat}.context-view .monaco-menu{min-width:130px}
    \ No newline at end of file
    diff --git a/examples/tour-assets/monaco/editor/editor.main.js b/examples/tour-assets/monaco/editor/editor.main.js
    new file mode 100644
    index 0000000000000000000000000000000000000000..23f518dd593608e299f73c2860088ee4c86c40d7
    --- /dev/null
    +++ b/examples/tour-assets/monaco/editor/editor.main.js
    @@ -0,0 +1,805 @@
    +/*!-----------------------------------------------------------
    + * Copyright (c) Microsoft Corporation. All rights reserved.
    + * Version: 0.33.0(c722ca6c7eed3d7987c0d5c3df5c45f6b15e77d1)
    + * Released under the MIT license
    + * https://github.com/microsoft/vscode/blob/main/LICENSE.txt
    + *-----------------------------------------------------------*/(function(){var te=["exports","require","vs/base/common/lifecycle","vs/editor/common/core/range","vs/base/common/event","vs/nls","vs/nls!vs/editor/editor.main","vs/base/browser/dom","vs/base/common/strings","vs/platform/instantiation/common/instantiation","vs/base/common/async","vs/editor/common/core/position","vs/css!vs/editor/editor.main","vs/base/common/errors","vs/platform/theme/common/themeService","vs/editor/browser/editorExtensions","vs/base/common/platform","vs/platform/contextkey/common/contextkey","vs/base/common/arrays","vs/base/common/types","vs/editor/common/services/languageFeatures","vs/base/common/cancellation","vs/editor/common/core/selection","vs/platform/theme/common/colorRegistry","vs/editor/common/editorContextKeys","vs/base/common/uri","vs/platform/commands/common/commands","vs/editor/common/languages","vs/base/common/codicons","vs/editor/browser/services/codeEditorService","vs/base/common/color","vs/base/browser/fastDomNode","vs/editor/common/config/editorOptions","vs/editor/common/languages/languageConfigurationRegistry","vs/base/browser/browser","vs/editor/common/languages/language","vs/platform/registry/common/platform","vs/platform/actions/common/actions","vs/editor/common/model/textModel","vs/platform/notification/common/notification","vs/base/common/objects","vs/platform/configuration/common/configuration","vs/base/common/resources","vs/platform/keybinding/common/keybinding","vs/base/browser/keyboardEvent","vs/base/common/network","vs/base/browser/ui/aria/aria","vs/base/common/actions","vs/editor/common/services/model","vs/base/common/map","vs/editor/common/model","vs/editor/browser/view/viewPart","vs/platform/instantiation/common/extensions","vs/platform/opener/common/opener","vs/editor/common/core/editorColorRegistry","vs/base/common/stopwatch","vs/editor/common/services/resolverService","vs/base/common/iterator","vs/base/common/keyCodes","vs/base/browser/mouseEvent","vs/base/browser/touch","vs/editor/common/cursorCommon","vs/base/browser/ui/widget","vs/editor/common/core/editOperation","vs/editor/browser/config/domFontInfo","vs/platform/accessibility/common/accessibility","vs/platform/log/common/log","vs/editor/common/services/languageFeatureDebounce","vs/base/common/htmlContent","vs/base/browser/ui/scrollbar/scrollableElement","vs/editor/common/core/cursorColumns","vs/editor/common/viewModel","vs/editor/common/standaloneStrings","vs/platform/progress/common/progress","vs/platform/theme/common/iconRegistry","vs/base/common/filters","vs/base/common/severity","vs/editor/common/tokens/lineTokens","vs/platform/contextview/browser/contextView","vs/platform/markers/common/markers","vs/platform/quickinput/common/quickInput","vs/editor/common/languages/modesRegistry","vs/platform/storage/common/storage","vs/base/common/linkedList","vs/base/common/path","vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length","vs/editor/contrib/hover/browser/hoverTypes","vs/base/browser/ui/actionbar/actionbar","vs/editor/common/services/editorWorker","vs/platform/keybinding/common/keybindingsRegistry","vs/platform/telemetry/common/telemetry","vs/base/common/functional","vs/base/browser/event","vs/editor/common/core/stringBuilder","vs/editor/common/textModelEvents","vs/base/common/decorators","vs/base/common/keybindings","vs/base/common/iconLabels","vs/base/browser/globalMouseMoveMonitor","vs/editor/common/core/characterClassifier","vs/editor/common/commands/replaceCommand","vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet","vs/editor/browser/view/dynamicViewOverlay","vs/editor/standalone/common/standaloneTheme","vs/platform/clipboard/common/clipboardService","vs/platform/configuration/common/configurationRegistry","vs/editor/contrib/markdownRenderer/browser/markdownRenderer","vs/platform/quickinput/common/quickAccess","vs/editor/contrib/editorState/browser/editorState","vs/editor/contrib/suggest/browser/suggest","vs/editor/contrib/peekView/browser/peekView","vs/base/browser/ui/tree/tree","vs/base/common/buffer","vs/base/common/numbers","vs/base/common/hash","vs/base/browser/ui/iconLabel/iconLabels","vs/base/browser/ui/sash/sash","vs/base/browser/ui/list/listWidget","vs/editor/browser/view/renderingContext","vs/editor/common/core/eolCounter","vs/editor/common/core/wordCharacterClassifier","vs/editor/common/core/wordHelper","vs/editor/common/languages/languageConfiguration","vs/editor/common/languages/supports","vs/editor/common/languages/nullTokenize","vs/editor/common/viewEventHandler","vs/editor/common/viewLayout/viewLineRenderer","vs/editor/contrib/snippet/browser/snippetParser","vs/base/browser/ui/actionbar/actionViewItems","vs/editor/contrib/gotoSymbol/browser/referencesModel","vs/platform/dialogs/common/dialogs","vs/platform/label/common/label","vs/platform/layout/browser/layoutService","vs/editor/browser/editorDom","vs/platform/theme/common/styler","vs/platform/theme/common/theme","vs/base/common/idGenerator","vs/base/common/lazy","vs/base/common/mime","vs/base/common/range","vs/base/common/scrollable","vs/base/common/diff/diff","vs/base/common/uint","vs/base/browser/ui/codicons/codiconStyles","vs/base/browser/ui/mouseCursor/mouseCursor","vs/css!vs/base/parts/quickinput/browser/media/quickInput","vs/editor/browser/stableEditorScroll","vs/editor/common/config/editorZoom","vs/editor/common/core/textModelDefaults","vs/editor/common/editorCommon","vs/editor/browser/editorBrowser","vs/editor/common/cursor/cursorWordOperations","vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/ast","vs/editor/common/model/textModelSearch","vs/editor/common/viewLayout/lineDecorations","vs/editor/contrib/codeAction/browser/types","vs/editor/common/services/textResourceConfiguration","vs/platform/instantiation/common/serviceCollection","vs/editor/browser/coreCommands","vs/editor/contrib/codeAction/browser/codeAction","vs/editor/contrib/message/browser/messageController","vs/platform/list/browser/listService","vs/platform/undoRedo/common/undoRedo","vs/editor/browser/widget/codeEditorWidget","vs/editor/browser/widget/embeddedCodeEditorWidget","vs/editor/contrib/find/browser/findModel","vs/base/browser/dnd","vs/base/browser/canIUse","vs/base/common/extpath","vs/base/browser/ui/tree/indexTreeModel","vs/base/browser/ui/tree/objectTreeModel","vs/base/browser/formattedTextRenderer","vs/base/browser/ui/scrollbar/scrollbarArrow","vs/base/common/labels","vs/base/browser/ui/checkbox/checkbox","vs/base/browser/ui/list/listView","vs/editor/common/config/fontInfo","vs/editor/common/core/indentation","vs/editor/browser/controller/textAreaInput","vs/editor/browser/view/viewLayer","vs/editor/common/cursor/cursorMoveOperations","vs/editor/common/cursor/cursorDeleteOperations","vs/editor/common/cursor/cursorMoveCommands","vs/editor/common/languages/supports/richEditBrackets","vs/editor/common/model/utils","vs/editor/common/standalone/standaloneEnums","vs/editor/common/textModelGuides","vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer","vs/editor/browser/viewParts/glyphMargin/glyphMargin","vs/editor/common/viewEvents","vs/editor/common/viewModelEventDispatcher","vs/editor/contrib/folding/browser/foldingRanges","vs/editor/contrib/gotoSymbol/browser/link/clickLinkGesture","vs/editor/contrib/inlineCompletions/browser/ghostText","vs/editor/contrib/inlineCompletions/browser/inlineCompletionToGhostText","vs/base/browser/ui/iconLabel/iconLabel","vs/base/browser/ui/tree/abstractTree","vs/base/browser/ui/inputbox/inputBox","vs/base/common/keybindingLabels","vs/platform/instantiation/common/descriptors","vs/editor/browser/services/bulkEditService","vs/editor/common/services/markerDecorations","vs/editor/common/commands/shiftCommand","vs/editor/common/cursor/cursorTypeOperations","vs/editor/contrib/parameterHints/browser/provideSignatureHelp","vs/editor/contrib/documentSymbols/browser/outlineModel","vs/platform/jsonschemas/common/jsonContributionRegistry","vs/editor/contrib/hover/browser/markdownHoverParticipant","vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel","vs/platform/actions/browser/menuEntryActionViewItem","vs/editor/contrib/gotoSymbol/browser/goToCommands","vs/platform/workspace/common/workspace","vs/editor/contrib/snippet/browser/snippetController2","vs/editor/standalone/browser/standaloneServices","vs/base/browser/iframe","vs/base/browser/ui/scrollbar/scrollbarState","vs/base/common/assert","vs/base/common/collections","vs/base/common/glob","vs/base/common/marshalling","vs/base/browser/ui/highlightedlabel/highlightedLabel","vs/base/browser/ui/scrollbar/abstractScrollbar","vs/base/common/worker/simpleWorker","vs/base/parts/quickinput/common/quickInput","vs/css!vs/base/browser/ui/actionbar/actionbar","vs/base/browser/ui/contextview/contextview","vs/base/browser/ui/countBadge/countBadge","vs/css!vs/base/browser/ui/dropdown/dropdown","vs/css!vs/base/browser/ui/findinput/findInput","vs/css!vs/base/browser/ui/list/list","vs/base/browser/ui/hover/hoverWidget","vs/base/browser/ui/splitview/splitview","vs/base/parts/quickinput/browser/quickInputUtils","vs/editor/browser/config/elementSizeObserver","vs/editor/browser/config/tabFocus","vs/editor/browser/view/viewUserInputEvents","vs/editor/browser/viewParts/minimap/minimapCharSheet","vs/editor/browser/controller/textAreaState","vs/editor/browser/widget/diffNavigator","vs/editor/common/core/rgba","vs/editor/common/core/textChange","vs/editor/common/cursor/cursorAtomicMoveOperations","vs/editor/common/editorAction","vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper","vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/parser","vs/editor/common/model/prefixSumComputer","vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase","vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer","vs/editor/common/modelLineProjectionData","vs/editor/common/services/unicodeTextModelHighlighter","vs/editor/common/model/guidesTextModelPart","vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/brackets","vs/editor/common/services/editorBaseApi","vs/editor/common/languages/textToHtmlTokenizer","vs/editor/browser/viewParts/margin/margin","vs/editor/common/viewModel/minimapTokensColorTracker","vs/editor/common/viewModel/overviewZoneManager","vs/editor/contrib/comment/browser/blockCommentCommand","vs/editor/contrib/folding/browser/syntaxRangeProvider","vs/editor/contrib/format/browser/formattingEdit","vs/editor/contrib/hover/browser/hoverOperation","vs/editor/contrib/indentation/browser/indentUtils","vs/editor/contrib/inlayHints/browser/inlayHints","vs/editor/contrib/inlineCompletions/browser/consts","vs/editor/contrib/smartSelect/browser/bracketSelections","vs/editor/contrib/suggest/browser/resizable","vs/editor/standalone/common/monarch/monarchCommon","vs/editor/standalone/common/monarch/monarchLexer","vs/base/browser/ui/findinput/findInputCheckboxes","vs/base/browser/ui/tree/objectTree","vs/editor/browser/config/fontMeasurements","vs/editor/common/viewModel/viewModelDecorations","vs/editor/common/model/editStack","vs/platform/files/common/files","vs/editor/common/services/getSemanticTokens","vs/editor/contrib/codelens/browser/codelens","vs/editor/contrib/colorPicker/browser/color","vs/editor/common/cursor/cursor","vs/platform/contextkey/common/contextkeys","vs/platform/keybinding/common/resolvedKeybindingItem","vs/editor/standalone/browser/standaloneLayoutService","vs/editor/browser/services/editorWorkerService","vs/editor/contrib/suggest/browser/suggestWidgetDetails","vs/platform/history/browser/contextScopedHistoryWidget","vs/editor/browser/viewParts/lines/viewLine","vs/editor/browser/controller/mouseTarget","vs/editor/browser/viewParts/lineNumbers/lineNumbers","vs/editor/common/services/semanticTokensProviderStyling","vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess","vs/editor/contrib/symbolIcons/browser/symbolIcons","vs/editor/standalone/browser/standaloneCodeEditorService","vs/editor/contrib/format/browser/format","vs/editor/contrib/gotoSymbol/browser/goToSymbol","vs/editor/contrib/hover/browser/getHover","vs/editor/contrib/codeAction/browser/codeActionCommands","vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode","vs/editor/contrib/wordOperations/browser/wordOperations","vs/editor/common/services/modelService","vs/editor/browser/widget/diffEditorWidget","vs/editor/contrib/colorPicker/browser/colorDetector","vs/editor/contrib/find/browser/findController","vs/editor/contrib/gotoError/browser/gotoError","vs/editor/contrib/gotoSymbol/browser/peek/referencesController","vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition","vs/editor/contrib/hover/browser/hover","vs/editor/contrib/inlayHints/browser/inlayHintsController","vs/editor/contrib/snippet/browser/snippetSession","vs/editor/contrib/suggest/browser/suggestController","vs/editor/contrib/inlineCompletions/browser/ghostTextController","vs/platform/workspace/common/workspaceTrust","vs/base/browser/ui/list/list","vs/base/browser/ui/list/splice","vs/base/common/cache","vs/base/common/diff/diffChange","vs/base/common/marked/marked","vs/base/common/navigator","vs/base/common/history","vs/base/common/process","vs/base/browser/ui/list/rangeMap","vs/base/browser/ui/scrollbar/scrollbarVisibilityController","vs/base/common/comparers","vs/base/browser/ui/tree/compressedObjectTreeModel","vs/base/common/fuzzyScorer","vs/base/common/search","vs/base/browser/ui/list/rowCache","vs/base/browser/ui/scrollbar/horizontalScrollbar","vs/base/browser/ui/scrollbar/verticalScrollbar","vs/base/browser/markdownRenderer","vs/base/common/uuid","vs/base/browser/defaultWorkerFactory","vs/base/parts/storage/common/storage","vs/css!vs/base/browser/ui/aria/aria","vs/css!vs/base/browser/ui/button/button","vs/base/browser/ui/button/button","vs/css!vs/base/browser/ui/checkbox/checkbox","vs/css!vs/base/browser/ui/codicons/codicon/codicon","vs/css!vs/base/browser/ui/codicons/codicon/codicon-modifiers","vs/css!vs/base/browser/ui/contextview/contextview","vs/css!vs/base/browser/ui/countBadge/countBadge","vs/css!vs/base/browser/ui/hover/hover","vs/css!vs/base/browser/ui/iconLabel/iconlabel","vs/css!vs/base/browser/ui/inputbox/inputBox","vs/css!vs/base/browser/ui/keybindingLabel/keybindingLabel","vs/css!vs/base/browser/ui/mouseCursor/mouseCursor","vs/css!vs/base/browser/ui/progressbar/progressbar","vs/base/browser/ui/progressbar/progressbar","vs/css!vs/base/browser/ui/sash/sash","vs/css!vs/base/browser/ui/scrollbar/media/scrollbars","vs/base/browser/ui/list/listPaging","vs/css!vs/base/browser/ui/splitview/splitview","vs/css!vs/base/browser/ui/table/table","vs/base/browser/ui/table/tableWidget","vs/css!vs/base/browser/ui/tree/media/tree","vs/css!vs/editor/browser/controller/textAreaHandler","vs/css!vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight","vs/css!vs/editor/browser/viewParts/decorations/decorations","vs/css!vs/editor/browser/viewParts/glyphMargin/glyphMargin","vs/css!vs/editor/browser/viewParts/indentGuides/indentGuides","vs/css!vs/editor/browser/viewParts/lineNumbers/lineNumbers","vs/css!vs/editor/browser/viewParts/lines/viewLines","vs/css!vs/editor/browser/viewParts/linesDecorations/linesDecorations","vs/css!vs/editor/browser/viewParts/marginDecorations/marginDecorations","vs/css!vs/editor/browser/viewParts/minimap/minimap","vs/css!vs/editor/browser/viewParts/overlayWidgets/overlayWidgets","vs/css!vs/editor/browser/viewParts/rulers/rulers","vs/css!vs/editor/browser/viewParts/scrollDecoration/scrollDecoration","vs/css!vs/editor/browser/viewParts/selections/selections","vs/css!vs/editor/browser/viewParts/viewCursors/viewCursors","vs/css!vs/editor/browser/widget/media/diffEditor","vs/css!vs/editor/browser/widget/media/diffReview","vs/css!vs/editor/browser/widget/media/editor","vs/css!vs/editor/contrib/anchorSelect/browser/anchorSelect","vs/css!vs/editor/contrib/bracketMatching/browser/bracketMatching","vs/css!vs/editor/contrib/codeAction/browser/lightBulbWidget","vs/css!vs/editor/contrib/codelens/browser/codelensWidget","vs/css!vs/editor/contrib/colorPicker/browser/colorPicker","vs/css!vs/editor/contrib/dnd/browser/dnd","vs/css!vs/editor/contrib/find/browser/findWidget","vs/css!vs/editor/contrib/folding/browser/folding","vs/css!vs/editor/contrib/gotoError/browser/media/gotoErrorWidget","vs/css!vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition","vs/css!vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget","vs/css!vs/editor/contrib/inlineCompletions/browser/ghostText","vs/css!vs/editor/contrib/links/browser/links","vs/css!vs/editor/contrib/message/browser/messageController","vs/css!vs/editor/contrib/parameterHints/browser/parameterHints","vs/css!vs/editor/contrib/peekView/browser/media/peekViewWidget","vs/css!vs/editor/contrib/rename/browser/renameInputField","vs/css!vs/editor/contrib/snippet/browser/snippetSession","vs/css!vs/editor/contrib/suggest/browser/media/suggest","vs/css!vs/editor/contrib/unicodeHighlighter/browser/bannerController","vs/css!vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter","vs/css!vs/editor/contrib/zoneWidget/browser/zoneWidget","vs/css!vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp","vs/css!vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard","vs/css!vs/editor/standalone/browser/inspectTokens/inspectTokens","vs/css!vs/editor/standalone/browser/quickInput/standaloneQuickInput","vs/css!vs/editor/standalone/browser/standalone-tokens","vs/css!vs/platform/actions/browser/menuEntryActionViewItem","vs/css!vs/platform/contextview/browser/contextMenuHandler","vs/editor/browser/config/migrateOptions","vs/editor/browser/viewParts/lines/rangeUtil","vs/editor/browser/viewParts/minimap/minimapCharRenderer","vs/editor/browser/viewParts/minimap/minimapPreBaked","vs/editor/browser/viewParts/minimap/minimapCharRendererFactory","vs/editor/common/commands/trimTrailingWhitespaceCommand","vs/editor/common/commands/surroundSelectionCommand","vs/editor/common/cursor/cursorContext","vs/editor/common/diff/diffComputer","vs/editor/common/editorTheme","vs/editor/common/languageSelector","vs/editor/common/languages/linkComputer","vs/editor/common/cursor/cursorColumnSelection","vs/editor/common/cursor/oneCursor","vs/editor/common/cursor/cursorCollection","vs/editor/common/languages/supports/characterPair","vs/editor/common/languages/supports/indentRules","vs/editor/common/languages/supports/inplaceReplaceSupport","vs/editor/common/languages/supports/onEnter","vs/editor/common/languages/supports/electricCharacter","vs/editor/common/languages/supports/tokenization","vs/editor/common/languageFeatureRegistry","vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/nodeReader","vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/concat23Trees","vs/editor/common/model/indentationGuesser","vs/editor/common/model/intervalTree","vs/editor/common/model/pieceTreeTextBuffer/rbTreeBase","vs/editor/common/model/mirrorTextModel","vs/editor/common/model/textModelPart","vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder","vs/editor/common/services/languagesAssociations","vs/editor/common/services/semanticTokensDto","vs/editor/common/textModelBracketPairs","vs/editor/common/tokenizationRegistry","vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree","vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl","vs/editor/common/model/bracketPairsTextModelPart/fixBrackets","vs/editor/common/services/editorSimpleWorker","vs/editor/common/tokens/contiguousMultilineTokens","vs/editor/common/tokens/contiguousMultilineTokensBuilder","vs/editor/common/model/textModelTokens","vs/editor/common/tokens/contiguousTokensEditing","vs/editor/common/tokens/contiguousTokensStore","vs/editor/common/tokens/sparseMultilineTokens","vs/editor/common/tokens/sparseTokensStore","vs/editor/browser/viewParts/contentWidgets/contentWidgets","vs/editor/browser/viewParts/decorations/decorations","vs/editor/browser/viewParts/linesDecorations/linesDecorations","vs/editor/browser/viewParts/marginDecorations/marginDecorations","vs/editor/browser/viewParts/overlayWidgets/overlayWidgets","vs/editor/browser/viewParts/viewZones/viewZones","vs/editor/common/viewLayout/linesLayout","vs/editor/common/viewLayout/viewLinesViewportData","vs/editor/common/viewModel/modelLineProjection","vs/editor/common/viewModel/monospaceLineBreaksComputer","vs/editor/browser/viewParts/overviewRuler/overviewRuler","vs/editor/common/viewModel/viewContext","vs/editor/common/viewLayout/viewLayout","vs/editor/contrib/caretOperations/browser/moveCaretCommand","vs/editor/contrib/colorPicker/browser/colorPickerModel","vs/editor/contrib/dnd/browser/dragAndDropCommand","vs/editor/contrib/find/browser/replaceAllCommand","vs/editor/contrib/find/browser/replacePattern","vs/editor/contrib/folding/browser/foldingModel","vs/editor/contrib/folding/browser/hiddenRangeModel","vs/editor/contrib/folding/browser/indentRangeProvider","vs/editor/contrib/folding/browser/intializingRangeProvider","vs/editor/contrib/inPlaceReplace/browser/inPlaceReplaceCommand","vs/editor/contrib/inlineCompletions/browser/utils","vs/editor/contrib/linesOperations/browser/copyLinesCommand","vs/editor/contrib/linesOperations/browser/sortLinesCommand","vs/editor/contrib/smartSelect/browser/wordSelections","vs/editor/contrib/suggest/browser/completionModel","vs/editor/contrib/suggest/browser/suggestCommitCharacters","vs/editor/contrib/suggest/browser/suggestOvertypingCapturer","vs/editor/contrib/suggest/browser/wordDistance","vs/editor/standalone/common/monarch/monarchCompile","vs/editor/standalone/browser/colorizer","vs/nls!vs/base/browser/ui/actionbar/actionViewItems","vs/nls!vs/base/browser/ui/findinput/findInput","vs/nls!vs/base/browser/ui/findinput/findInputCheckboxes","vs/nls!vs/base/browser/ui/findinput/replaceInput","vs/nls!vs/base/browser/ui/iconLabel/iconLabelHover","vs/base/browser/ui/iconLabel/iconLabelHover","vs/nls!vs/base/browser/ui/inputbox/inputBox","vs/nls!vs/base/browser/ui/keybindingLabel/keybindingLabel","vs/nls!vs/base/browser/ui/tree/abstractTree","vs/base/browser/ui/tree/dataTree","vs/base/browser/ui/tree/asyncDataTree","vs/nls!vs/base/common/actions","vs/base/browser/ui/dropdown/dropdown","vs/base/browser/ui/dropdown/dropdownActionViewItem","vs/base/browser/ui/findinput/findInput","vs/base/browser/ui/findinput/replaceInput","vs/base/browser/ui/menu/menu","vs/base/parts/quickinput/browser/quickInputBox","vs/nls!vs/base/common/errorMessage","vs/base/common/errorMessage","vs/nls!vs/base/common/keybindingLabels","vs/base/browser/ui/keybindingLabel/keybindingLabel","vs/nls!vs/base/parts/quickinput/browser/quickInput","vs/nls!vs/base/parts/quickinput/browser/quickInputList","vs/base/parts/quickinput/browser/quickInputList","vs/base/parts/quickinput/browser/quickInput","vs/nls!vs/editor/browser/controller/textAreaHandler","vs/nls!vs/editor/browser/coreCommands","vs/nls!vs/editor/browser/editorExtensions","vs/nls!vs/editor/browser/widget/codeEditorWidget","vs/nls!vs/editor/browser/widget/diffEditorWidget","vs/nls!vs/editor/browser/widget/diffReview","vs/nls!vs/editor/browser/widget/inlineDiffMargin","vs/editor/browser/widget/inlineDiffMargin","vs/nls!vs/editor/common/config/editorConfigurationSchema","vs/nls!vs/editor/common/config/editorOptions","vs/editor/browser/config/charWidthReader","vs/editor/browser/view/domLineBreaksComputer","vs/editor/browser/view/viewOverlays","vs/editor/browser/viewParts/viewCursors/viewCursor","vs/nls!vs/editor/common/core/editorColorRegistry","vs/nls!vs/editor/common/editorContextKeys","vs/nls!vs/editor/common/languages/modesRegistry","vs/nls!vs/editor/common/model/editStack","vs/nls!vs/editor/common/standaloneStrings","vs/nls!vs/editor/contrib/anchorSelect/browser/anchorSelect","vs/nls!vs/editor/contrib/bracketMatching/browser/bracketMatching","vs/nls!vs/editor/contrib/caretOperations/browser/caretOperations","vs/nls!vs/editor/contrib/caretOperations/browser/transpose","vs/nls!vs/editor/contrib/clipboard/browser/clipboard","vs/nls!vs/editor/contrib/codeAction/browser/codeActionCommands","vs/nls!vs/editor/contrib/codeAction/browser/lightBulbWidget","vs/nls!vs/editor/contrib/codelens/browser/codelensController","vs/nls!vs/editor/contrib/colorPicker/browser/colorPickerWidget","vs/nls!vs/editor/contrib/comment/browser/comment","vs/nls!vs/editor/contrib/contextmenu/browser/contextmenu","vs/nls!vs/editor/contrib/cursorUndo/browser/cursorUndo","vs/nls!vs/editor/contrib/editorState/browser/keybindingCancellation","vs/nls!vs/editor/contrib/find/browser/findController","vs/nls!vs/editor/contrib/find/browser/findWidget","vs/nls!vs/editor/contrib/folding/browser/folding","vs/nls!vs/editor/contrib/folding/browser/foldingDecorations","vs/nls!vs/editor/contrib/fontZoom/browser/fontZoom","vs/nls!vs/editor/contrib/format/browser/format","vs/nls!vs/editor/contrib/format/browser/formatActions","vs/nls!vs/editor/contrib/gotoError/browser/gotoError","vs/nls!vs/editor/contrib/gotoError/browser/gotoErrorWidget","vs/nls!vs/editor/contrib/gotoSymbol/browser/goToCommands","vs/nls!vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition","vs/nls!vs/editor/contrib/gotoSymbol/browser/peek/referencesController","vs/nls!vs/editor/contrib/gotoSymbol/browser/peek/referencesTree","vs/nls!vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget","vs/nls!vs/editor/contrib/gotoSymbol/browser/referencesModel","vs/nls!vs/editor/contrib/gotoSymbol/browser/symbolNavigation","vs/nls!vs/editor/contrib/hover/browser/hover","vs/nls!vs/editor/contrib/hover/browser/markdownHoverParticipant","vs/nls!vs/editor/contrib/hover/browser/markerHoverParticipant","vs/nls!vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace","vs/nls!vs/editor/contrib/indentation/browser/indentation","vs/nls!vs/editor/contrib/inlayHints/browser/inlayHintsHover","vs/nls!vs/editor/contrib/inlineCompletions/browser/ghostTextController","vs/nls!vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHoverParticipant","vs/nls!vs/editor/contrib/lineSelection/browser/lineSelection","vs/nls!vs/editor/contrib/linesOperations/browser/linesOperations","vs/nls!vs/editor/contrib/linkedEditing/browser/linkedEditing","vs/nls!vs/editor/contrib/links/browser/links","vs/nls!vs/editor/contrib/message/browser/messageController","vs/nls!vs/editor/contrib/multicursor/browser/multicursor","vs/nls!vs/editor/contrib/parameterHints/browser/parameterHints","vs/nls!vs/editor/contrib/parameterHints/browser/parameterHintsWidget","vs/nls!vs/editor/contrib/peekView/browser/peekView","vs/nls!vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess","vs/nls!vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess","vs/nls!vs/editor/contrib/rename/browser/rename","vs/nls!vs/editor/contrib/rename/browser/renameInputField","vs/nls!vs/editor/contrib/smartSelect/browser/smartSelect","vs/nls!vs/editor/contrib/snippet/browser/snippetController2","vs/nls!vs/editor/contrib/snippet/browser/snippetVariables","vs/nls!vs/editor/contrib/suggest/browser/suggest","vs/nls!vs/editor/contrib/suggest/browser/suggestController","vs/nls!vs/editor/contrib/suggest/browser/suggestWidget","vs/nls!vs/editor/contrib/suggest/browser/suggestWidgetDetails","vs/nls!vs/editor/contrib/suggest/browser/suggestWidgetRenderer","vs/nls!vs/editor/contrib/suggest/browser/suggestWidgetStatus","vs/nls!vs/editor/contrib/symbolIcons/browser/symbolIcons","vs/nls!vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode","vs/nls!vs/editor/contrib/tokenization/browser/tokenization","vs/nls!vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter","vs/nls!vs/editor/contrib/unusualLineTerminators/browser/unusualLineTerminators","vs/nls!vs/editor/contrib/wordHighlighter/browser/wordHighlighter","vs/nls!vs/editor/contrib/wordOperations/browser/wordOperations","vs/nls!vs/platform/actions/browser/menuEntryActionViewItem","vs/nls!vs/platform/configuration/common/configurationRegistry","vs/nls!vs/platform/contextkey/browser/contextKeyService","vs/nls!vs/platform/contextkey/common/contextkeys","vs/nls!vs/platform/history/browser/contextScopedHistoryWidget","vs/nls!vs/platform/keybinding/common/abstractKeybindingService","vs/nls!vs/platform/list/browser/listService","vs/nls!vs/platform/markers/common/markers","vs/nls!vs/platform/quickinput/browser/commandsQuickAccess","vs/nls!vs/platform/quickinput/browser/helpQuickAccess","vs/nls!vs/platform/theme/common/colorRegistry","vs/nls!vs/platform/theme/common/iconRegistry","vs/nls!vs/platform/undoRedo/common/undoRedoService","vs/nls!vs/platform/workspace/common/workspace","vs/platform/editor/common/editor","vs/platform/extensions/common/extensions","vs/platform/history/browser/historyWidgetKeybindingHint","vs/platform/instantiation/common/graph","vs/editor/common/services/languageFeaturesService","vs/editor/contrib/links/browser/getLinks","vs/editor/contrib/comment/browser/lineCommentCommand","vs/editor/contrib/linesOperations/browser/moveLinesCommand","vs/editor/contrib/parameterHints/browser/parameterHintsModel","vs/editor/contrib/suggest/browser/suggestAlternatives","vs/editor/contrib/suggest/browser/wordContextKey","vs/editor/browser/config/editorConfiguration","vs/platform/accessibility/browser/accessibilityService","vs/platform/contextkey/browser/contextKeyService","vs/platform/instantiation/common/instantiationService","vs/platform/keybinding/common/abstractKeybindingService","vs/platform/keybinding/common/baseResolvedKeybinding","vs/platform/keybinding/common/keybindingResolver","vs/platform/keybinding/common/usLayoutResolvedKeybinding","vs/platform/contextview/browser/contextViewService","vs/editor/browser/services/webWorker","vs/editor/contrib/documentSymbols/browser/documentSymbols","vs/platform/clipboard/browser/clipboardService","vs/editor/contrib/gotoError/browser/markerNavigationService","vs/platform/markers/common/markerService","vs/editor/browser/services/openerService","vs/platform/quickinput/browser/pickerQuickAccess","vs/editor/common/config/editorConfigurationSchema","vs/editor/common/services/getIconClasses","vs/editor/common/services/languagesRegistry","vs/editor/common/services/languageService","vs/editor/contrib/hover/browser/marginHover","vs/platform/configuration/common/configurationModels","vs/platform/quickinput/browser/helpQuickAccess","vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess","vs/platform/quickinput/browser/quickAccess","vs/editor/contrib/codelens/browser/codeLensCache","vs/editor/contrib/suggest/browser/suggestMemory","vs/platform/quickinput/browser/commandsQuickAccess","vs/editor/contrib/quickAccess/browser/commandsQuickAccess","vs/platform/contextview/browser/contextMenuHandler","vs/editor/browser/controller/mouseHandler","vs/editor/browser/controller/pointerHandler","vs/editor/browser/viewParts/lines/viewLines","vs/editor/browser/services/abstractCodeEditorService","vs/editor/browser/viewParts/editorScrollbar/editorScrollbar","vs/editor/browser/viewParts/minimap/minimap","vs/editor/browser/viewParts/scrollDecoration/scrollDecoration","vs/editor/browser/viewParts/selections/selections","vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight","vs/editor/browser/viewParts/indentGuides/indentGuides","vs/editor/browser/controller/textAreaHandler","vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler","vs/editor/browser/viewParts/rulers/rulers","vs/editor/browser/viewParts/viewCursors/viewCursors","vs/editor/common/model/bracketPairsTextModelPart/colorizedBracketPairsDecorationProvider","vs/editor/common/services/markerDecorationsService","vs/editor/contrib/codeAction/browser/lightBulbWidget","vs/editor/contrib/colorPicker/browser/colorPickerWidget","vs/editor/contrib/gotoSymbol/browser/peek/referencesTree","vs/editor/contrib/inlineCompletions/browser/ghostTextWidget","vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess","vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess","vs/editor/contrib/rename/browser/renameInputField","vs/editor/standalone/common/themes","vs/editor/browser/services/markerDecorations","vs/editor/browser/view/viewController","vs/editor/browser/view","vs/editor/contrib/anchorSelect/browser/anchorSelect","vs/editor/contrib/caretOperations/browser/caretOperations","vs/editor/contrib/caretOperations/browser/transpose","vs/editor/contrib/clipboard/browser/clipboard","vs/editor/contrib/comment/browser/comment","vs/editor/contrib/contextmenu/browser/contextmenu","vs/editor/contrib/cursorUndo/browser/cursorUndo","vs/editor/contrib/editorState/browser/keybindingCancellation","vs/editor/contrib/codeAction/browser/codeActionMenu","vs/editor/contrib/codeAction/browser/codeActionModel","vs/editor/contrib/fontZoom/browser/fontZoom","vs/editor/contrib/format/browser/formatActions","vs/editor/contrib/gotoSymbol/browser/symbolNavigation","vs/editor/contrib/indentation/browser/indentation","vs/editor/contrib/lineSelection/browser/lineSelection","vs/editor/contrib/linesOperations/browser/linesOperations","vs/editor/contrib/codeAction/browser/codeActionUi","vs/editor/contrib/codeAction/browser/codeActionContributions","vs/editor/contrib/rename/browser/rename","vs/editor/contrib/smartSelect/browser/smartSelect","vs/editor/contrib/tokenization/browser/tokenization","vs/editor/contrib/unusualLineTerminators/browser/unusualLineTerminators","vs/editor/contrib/wordPartOperations/browser/wordPartOperations","vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp","vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard","vs/editor/standalone/browser/inspectTokens/inspectTokens","vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess","vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess","vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess","vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast","vs/editor/contrib/suggest/browser/suggestWidgetStatus","vs/platform/actions/common/menuService","vs/platform/contextview/browser/contextMenuService","vs/platform/opener/browser/link","vs/platform/quickinput/browser/quickInput","vs/editor/standalone/browser/quickInput/standaloneQuickInputService","vs/platform/severityIcon/common/severityIcon","vs/editor/browser/widget/diffReview","vs/editor/contrib/parameterHints/browser/parameterHintsWidget","vs/editor/contrib/parameterHints/browser/parameterHints","vs/editor/contrib/suggest/browser/suggestWidgetRenderer","vs/editor/contrib/unicodeHighlighter/browser/bannerController","vs/platform/theme/browser/iconsStyleSheet","vs/editor/standalone/browser/standaloneThemeService","vs/editor/common/viewModel/viewModelLines","vs/editor/common/viewModel/viewModelImpl","vs/editor/contrib/bracketMatching/browser/bracketMatching","vs/editor/contrib/codelens/browser/codelensWidget","vs/editor/contrib/codelens/browser/codelensController","vs/editor/contrib/colorPicker/browser/colorHoverParticipant","vs/editor/contrib/dnd/browser/dnd","vs/editor/contrib/find/browser/findDecorations","vs/editor/contrib/find/browser/findOptionsWidget","vs/editor/contrib/find/browser/findState","vs/editor/contrib/find/browser/findWidget","vs/editor/contrib/folding/browser/foldingDecorations","vs/editor/contrib/folding/browser/folding","vs/editor/contrib/hover/browser/contentHover","vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace","vs/editor/contrib/linkedEditing/browser/linkedEditing","vs/editor/contrib/links/browser/links","vs/editor/contrib/multicursor/browser/multicursor","vs/editor/contrib/suggest/browser/suggestWidget","vs/editor/contrib/viewportSemanticTokens/browser/viewportSemanticTokens","vs/editor/contrib/wordHighlighter/browser/wordHighlighter","vs/editor/contrib/zoneWidget/browser/zoneWidget","vs/editor/contrib/gotoError/browser/gotoErrorWidget","vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget","vs/editor/contrib/hover/browser/markerHoverParticipant","vs/editor/contrib/colorPicker/browser/colorContributions","vs/editor/contrib/inlayHints/browser/inlayHintsLocations","vs/editor/contrib/inlayHints/browser/inlayHintsHover","vs/editor/contrib/inlayHints/browser/inlayHintsContribution","vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch","vs/platform/undoRedo/common/undoRedoService","vs/editor/contrib/snippet/browser/snippetVariables","vs/editor/contrib/suggest/browser/suggestModel","vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider","vs/editor/contrib/inlineCompletions/browser/suggestWidgetPreviewModel","vs/editor/contrib/inlineCompletions/browser/ghostTextModel","vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHoverParticipant","vs/editor/contrib/inlineCompletions/browser/inlineCompletionsContribution","vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter","vs/editor/editor.all","vs/editor/standalone/browser/standaloneCodeEditor","vs/editor/standalone/browser/standaloneEditor","vs/editor/standalone/browser/standaloneLanguages","vs/editor/editor.api","vs/base/browser/dompurify/dompurify","vs/editor/edcore.main"],ie=function(q){for(var e=[],L=0,m=q.length;L=0)},b}();function L(b,C,u){var g;return C.length===0?g=b:g=b.replace(/\{(\d+)\}/g,function(t,n){var i=n[0],o=C[i],c=t;return typeof o=="string"?c=o:(typeof o=="number"||typeof o=="boolean"||o===void 0||o===null)&&(c=String(o)),c}),u.isPseudo&&(g="\uFF3B"+g.replace(/[aouei]/g,"$&$&")+"\uFF3D"),g}function m(b,C){var u=b[C];return u||(u=b["*"],u)?u:null}function k(b,C,u){for(var g=[],t=3;t1?L-1:0),k=1;k/gm),gn=ft(/^data-[\-\w.\u00B7-\uFFFF]/),fn=ft(/^aria-[\-\w]+$/),mn=ft(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),pn=ft(/^(?:\w+script|data):/i),Cn=ft(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),Dt=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(q){return typeof q}:function(q){return q&&typeof Symbol=="function"&&q.constructor===Symbol&&q!==Symbol.prototype?"symbol":typeof q};function gt(q){if(Array.isArray(q)){for(var e=0,L=Array(q.length);e0&&arguments[0]!==void 0?arguments[0]:vn(),e=function(Ee){return bi(Ee)};if(e.version="2.3.1",e.removed=[],!q||!q.document||q.document.nodeType!==9)return e.isSupported=!1,e;var L=q.document,m=q.document,k=q.DocumentFragment,I=q.HTMLTemplateElement,w=q.Node,b=q.Element,C=q.NodeFilter,u=q.NamedNodeMap,g=u===void 0?q.NamedNodeMap||q.MozNamedAttrMap:u,t=q.Text,n=q.Comment,i=q.DOMParser,o=q.trustedTypes,c=b.prototype,d=Rt(c,"cloneNode"),r=Rt(c,"nextSibling"),s=Rt(c,"childNodes"),a=Rt(c,"parentNode");if(typeof I=="function"){var l=m.createElement("template");l.content&&l.content.ownerDocument&&(m=l.content.ownerDocument)}var h=bn(o,L),f=h&&ue?h.createHTML(""):"",S=m,v=S.implementation,p=S.createNodeIterator,_=S.createDocumentFragment,y=S.getElementsByTagName,E=L.importNode,N={};try{N=St(m).documentMode?m.documentMode:{}}catch{}var D={};e.isSupported=typeof a=="function"&&v&&typeof v.createHTMLDocument!="undefined"&&N!==9;var M=un,B=hn,O=gn,T=fn,A=pn,P=Cn,F=mn,W=null,R=Qe({},[].concat(gt(mi),gt(jt),gt(qt),gt($t),gt(pi))),z=null,j=Qe({},[].concat(gt(Ci),gt(Gt),gt(vi),gt(Pt))),$=null,G=null,J=!0,re=!0,se=!1,Z=!1,V=!1,K=!1,X=!1,oe=!1,ae=!1,ee=!0,ue=!1,le=!0,x=!0,H=!1,U={},Q=null,Y=Qe({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),ne=null,ge=Qe({},["audio","video","img","source","image","track"]),fe=null,ce=Qe({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),he="http://www.w3.org/1998/Math/MathML",ve="http://www.w3.org/2000/svg",Ce="http://www.w3.org/1999/xhtml",be=Ce,de=!1,me=null,_e=m.createElement("form"),ye=function(Ee){me&&me===Ee||((!Ee||(typeof Ee=="undefined"?"undefined":Dt(Ee))!=="object")&&(Ee={}),Ee=St(Ee),W="ALLOWED_TAGS"in Ee?Qe({},Ee.ALLOWED_TAGS):R,z="ALLOWED_ATTR"in Ee?Qe({},Ee.ALLOWED_ATTR):j,fe="ADD_URI_SAFE_ATTR"in Ee?Qe(St(ce),Ee.ADD_URI_SAFE_ATTR):ce,ne="ADD_DATA_URI_TAGS"in Ee?Qe(St(ge),Ee.ADD_DATA_URI_TAGS):ge,Q="FORBID_CONTENTS"in Ee?Qe({},Ee.FORBID_CONTENTS):Y,$="FORBID_TAGS"in Ee?Qe({},Ee.FORBID_TAGS):{},G="FORBID_ATTR"in Ee?Qe({},Ee.FORBID_ATTR):{},U="USE_PROFILES"in Ee?Ee.USE_PROFILES:!1,J=Ee.ALLOW_ARIA_ATTR!==!1,re=Ee.ALLOW_DATA_ATTR!==!1,se=Ee.ALLOW_UNKNOWN_PROTOCOLS||!1,Z=Ee.SAFE_FOR_TEMPLATES||!1,V=Ee.WHOLE_DOCUMENT||!1,oe=Ee.RETURN_DOM||!1,ae=Ee.RETURN_DOM_FRAGMENT||!1,ee=Ee.RETURN_DOM_IMPORT!==!1,ue=Ee.RETURN_TRUSTED_TYPE||!1,X=Ee.FORCE_BODY||!1,le=Ee.SANITIZE_DOM!==!1,x=Ee.KEEP_CONTENT!==!1,H=Ee.IN_PLACE||!1,F=Ee.ALLOWED_URI_REGEXP||F,be=Ee.NAMESPACE||Ce,Z&&(re=!1),ae&&(oe=!0),U&&(W=Qe({},[].concat(gt(pi))),z=[],U.html===!0&&(Qe(W,mi),Qe(z,Ci)),U.svg===!0&&(Qe(W,jt),Qe(z,Gt),Qe(z,Pt)),U.svgFilters===!0&&(Qe(W,qt),Qe(z,Gt),Qe(z,Pt)),U.mathMl===!0&&(Qe(W,$t),Qe(z,vi),Qe(z,Pt))),Ee.ADD_TAGS&&(W===R&&(W=St(W)),Qe(W,Ee.ADD_TAGS)),Ee.ADD_ATTR&&(z===j&&(z=St(z)),Qe(z,Ee.ADD_ATTR)),Ee.ADD_URI_SAFE_ATTR&&Qe(fe,Ee.ADD_URI_SAFE_ATTR),Ee.FORBID_CONTENTS&&(Q===Y&&(Q=St(Q)),Qe(Q,Ee.FORBID_CONTENTS)),x&&(W["#text"]=!0),V&&Qe(W,["html","head","body"]),W.table&&(Qe(W,["tbody"]),delete $.tbody),rt&&rt(Ee),me=Ee)},ke=Qe({},["mi","mo","mn","ms","mtext"]),Te=Qe({},["foreignobject","desc","title","annotation-xml"]),Me=Qe({},jt);Qe(Me,qt),Qe(Me,dn);var We=Qe({},$t);Qe(We,cn);var xe=function(Ee){var Re=a(Ee);(!Re||!Re.tagName)&&(Re={namespaceURI:Ce,tagName:"template"});var Ae=_t(Ee.tagName),je=_t(Re.tagName);if(Ee.namespaceURI===ve)return Re.namespaceURI===Ce?Ae==="svg":Re.namespaceURI===he?Ae==="svg"&&(je==="annotation-xml"||ke[je]):Boolean(Me[Ae]);if(Ee.namespaceURI===he)return Re.namespaceURI===Ce?Ae==="math":Re.namespaceURI===ve?Ae==="math"&&Te[je]:Boolean(We[Ae]);if(Ee.namespaceURI===Ce){if(Re.namespaceURI===ve&&!Te[je]||Re.namespaceURI===he&&!ke[je])return!1;var et=Qe({},["title","style","font","a","script"]);return!We[Ae]&&(et[Ae]||!Me[Ae])}return!1},He=function(Ee){It(e.removed,{element:Ee});try{Ee.parentNode.removeChild(Ee)}catch{try{Ee.outerHTML=f}catch{Ee.remove()}}},Le=function(Ee,Re){try{It(e.removed,{attribute:Re.getAttributeNode(Ee),from:Re})}catch{It(e.removed,{attribute:null,from:Re})}if(Re.removeAttribute(Ee),Ee==="is"&&!z[Ee])if(oe||ae)try{He(Re)}catch{}else try{Re.setAttribute(Ee,"")}catch{}},Se=function(Ee){var Re=void 0,Ae=void 0;if(X)Ee=""+Ee;else{var je=gi(Ee,/^[\r\n\t ]+/);Ae=je&&je[0]}var et=h?h.createHTML(Ee):Ee;if(be===Ce)try{Re=new i().parseFromString(et,"text/html")}catch{}if(!Re||!Re.documentElement){Re=v.createDocument(be,"template",null);try{Re.documentElement.innerHTML=de?"":et}catch{}}var Ze=Re.body||Re.documentElement;return Ee&&Ae&&Ze.insertBefore(m.createTextNode(Ae),Ze.childNodes[0]||null),be===Ce?y.call(Re,V?"html":"body")[0]:V?Re.documentElement:Ze},De=function(Ee){return p.call(Ee.ownerDocument||Ee,Ee,C.SHOW_ELEMENT|C.SHOW_COMMENT|C.SHOW_TEXT,null,!1)},Pe=function(Ee){return Ee instanceof t||Ee instanceof n?!1:typeof Ee.nodeName!="string"||typeof Ee.textContent!="string"||typeof Ee.removeChild!="function"||!(Ee.attributes instanceof g)||typeof Ee.removeAttribute!="function"||typeof Ee.setAttribute!="function"||typeof Ee.namespaceURI!="string"||typeof Ee.insertBefore!="function"},Fe=function(Ee){return(typeof w=="undefined"?"undefined":Dt(w))==="object"?Ee instanceof w:Ee&&(typeof Ee=="undefined"?"undefined":Dt(Ee))==="object"&&typeof Ee.nodeType=="number"&&typeof Ee.nodeName=="string"},ze=function(Ee,Re,Ae){!D[Ee]||on(D[Ee],function(je){je.call(e,Re,Ae,me)})},Ke=function(Ee){var Re=void 0;if(ze("beforeSanitizeElements",Ee,null),Pe(Ee)||gi(Ee.nodeName,/[\u0080-\uFFFF]/))return He(Ee),!0;var Ae=_t(Ee.nodeName);if(ze("uponSanitizeElement",Ee,{tagName:Ae,allowedTags:W}),!Fe(Ee.firstElementChild)&&(!Fe(Ee.content)||!Fe(Ee.content.firstElementChild))&&mt(/<[/\w]/g,Ee.innerHTML)&&mt(/<[/\w]/g,Ee.textContent)||Ae==="select"&&mt(/