diff --git a/pr-248/404.html b/pr-248/404.html deleted file mode 100644 index ce4b8e103..000000000 --- a/pr-248/404.html +++ /dev/null @@ -1,2001 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- -

404 - Not found

- -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/assets/images/favicon.png b/pr-248/assets/images/favicon.png deleted file mode 100644 index b695121e0..000000000 Binary files a/pr-248/assets/images/favicon.png and /dev/null differ diff --git a/pr-248/assets/images/github/workflow/branch_comparison.png b/pr-248/assets/images/github/workflow/branch_comparison.png deleted file mode 100644 index 195b1af37..000000000 Binary files a/pr-248/assets/images/github/workflow/branch_comparison.png and /dev/null differ diff --git a/pr-248/assets/images/github/workflow/create_pr.png b/pr-248/assets/images/github/workflow/create_pr.png deleted file mode 100644 index 1008ab276..000000000 Binary files a/pr-248/assets/images/github/workflow/create_pr.png and /dev/null differ diff --git a/pr-248/assets/images/github/workflow/gh_assignee.png b/pr-248/assets/images/github/workflow/gh_assignee.png deleted file mode 100644 index bfdd58ba1..000000000 Binary files a/pr-248/assets/images/github/workflow/gh_assignee.png and /dev/null differ diff --git a/pr-248/assets/images/github/workflow/gh_labels.png b/pr-248/assets/images/github/workflow/gh_labels.png deleted file mode 100644 index f5c11ca40..000000000 Binary files a/pr-248/assets/images/github/workflow/gh_labels.png and /dev/null differ diff --git a/pr-248/assets/images/github/workflow/gh_milestone.png b/pr-248/assets/images/github/workflow/gh_milestone.png deleted file mode 100644 index 839c39ba0..000000000 Binary files a/pr-248/assets/images/github/workflow/gh_milestone.png and /dev/null differ diff --git a/pr-248/assets/images/github/workflow/gh_project.png b/pr-248/assets/images/github/workflow/gh_project.png deleted file mode 100644 index 9697b2a0b..000000000 Binary files a/pr-248/assets/images/github/workflow/gh_project.png and /dev/null differ diff --git a/pr-248/assets/images/github/workflow/issue_list.png b/pr-248/assets/images/github/workflow/issue_list.png deleted file mode 100644 index ec20ee5af..000000000 Binary files a/pr-248/assets/images/github/workflow/issue_list.png and /dev/null differ diff --git a/pr-248/assets/images/github/workflow/issue_tab.png b/pr-248/assets/images/github/workflow/issue_tab.png deleted file mode 100644 index 5ccf9bf7d..000000000 Binary files a/pr-248/assets/images/github/workflow/issue_tab.png and /dev/null differ diff --git a/pr-248/assets/images/github/workflow/issue_template_list.png b/pr-248/assets/images/github/workflow/issue_template_list.png deleted file mode 100644 index bf490d9f6..000000000 Binary files a/pr-248/assets/images/github/workflow/issue_template_list.png and /dev/null differ diff --git a/pr-248/assets/images/github/workflow/merge_conflict.png b/pr-248/assets/images/github/workflow/merge_conflict.png deleted file mode 100644 index b86119352..000000000 Binary files a/pr-248/assets/images/github/workflow/merge_conflict.png and /dev/null differ diff --git a/pr-248/assets/images/github/workflow/merge_conflict_file.png b/pr-248/assets/images/github/workflow/merge_conflict_file.png deleted file mode 100644 index f1d5bcffc..000000000 Binary files a/pr-248/assets/images/github/workflow/merge_conflict_file.png and /dev/null differ diff --git a/pr-248/assets/images/github/workflow/new_feature_template.png b/pr-248/assets/images/github/workflow/new_feature_template.png deleted file mode 100644 index 6592fea02..000000000 Binary files a/pr-248/assets/images/github/workflow/new_feature_template.png and /dev/null differ diff --git a/pr-248/assets/images/github/workflow/pr_comment_snippet.png b/pr-248/assets/images/github/workflow/pr_comment_snippet.png deleted file mode 100644 index 2570dc990..000000000 Binary files a/pr-248/assets/images/github/workflow/pr_comment_snippet.png and /dev/null differ diff --git a/pr-248/assets/images/github/workflow/pr_example.png b/pr-248/assets/images/github/workflow/pr_example.png deleted file mode 100644 index 54c2de973..000000000 Binary files a/pr-248/assets/images/github/workflow/pr_example.png and /dev/null differ diff --git a/pr-248/assets/images/github/workflow/pr_merge.png b/pr-248/assets/images/github/workflow/pr_merge.png deleted file mode 100644 index 8bf4047bb..000000000 Binary files a/pr-248/assets/images/github/workflow/pr_merge.png and /dev/null differ diff --git a/pr-248/assets/images/markdown/issue_md_rendered.png b/pr-248/assets/images/markdown/issue_md_rendered.png deleted file mode 100644 index bf7b4ec92..000000000 Binary files a/pr-248/assets/images/markdown/issue_md_rendered.png and /dev/null differ diff --git a/pr-248/assets/images/markdown/issue_md_text.png b/pr-248/assets/images/markdown/issue_md_text.png deleted file mode 100644 index 34f2a2228..000000000 Binary files a/pr-248/assets/images/markdown/issue_md_text.png and /dev/null differ diff --git a/pr-248/assets/images/sailbot_workspace/workflow/sailbot_bug.png b/pr-248/assets/images/sailbot_workspace/workflow/sailbot_bug.png deleted file mode 100644 index d98be96e2..000000000 Binary files a/pr-248/assets/images/sailbot_workspace/workflow/sailbot_bug.png and /dev/null differ diff --git a/pr-248/assets/images/sailbot_workspace/workflow/vscode_testing_tab.png b/pr-248/assets/images/sailbot_workspace/workflow/vscode_testing_tab.png deleted file mode 100644 index e5add7d90..000000000 Binary files a/pr-248/assets/images/sailbot_workspace/workflow/vscode_testing_tab.png and /dev/null differ diff --git a/pr-248/assets/images/sailing/bear_off.jpg b/pr-248/assets/images/sailing/bear_off.jpg deleted file mode 100644 index 7d89c8d08..000000000 Binary files a/pr-248/assets/images/sailing/bear_off.jpg and /dev/null differ diff --git a/pr-248/assets/images/sailing/bear_off_then_gybe.jpg b/pr-248/assets/images/sailing/bear_off_then_gybe.jpg deleted file mode 100644 index 1e06084fb..000000000 Binary files a/pr-248/assets/images/sailing/bear_off_then_gybe.jpg and /dev/null differ diff --git a/pr-248/assets/images/sailing/bearing_vs_heading.jpg b/pr-248/assets/images/sailing/bearing_vs_heading.jpg deleted file mode 100644 index 9c1274f3a..000000000 Binary files a/pr-248/assets/images/sailing/bearing_vs_heading.jpg and /dev/null differ diff --git a/pr-248/assets/images/sailing/gybe.jpg b/pr-248/assets/images/sailing/gybe.jpg deleted file mode 100644 index a672586a8..000000000 Binary files a/pr-248/assets/images/sailing/gybe.jpg and /dev/null differ diff --git a/pr-248/assets/images/sailing/head_up.jpg b/pr-248/assets/images/sailing/head_up.jpg deleted file mode 100644 index 2d7a9abac..000000000 Binary files a/pr-248/assets/images/sailing/head_up.jpg and /dev/null differ diff --git a/pr-248/assets/images/sailing/parts_of_boat.jpg b/pr-248/assets/images/sailing/parts_of_boat.jpg deleted file mode 100644 index 0341922de..000000000 Binary files a/pr-248/assets/images/sailing/parts_of_boat.jpg and /dev/null differ diff --git a/pr-248/assets/images/sailing/points_of_sail.jpg b/pr-248/assets/images/sailing/points_of_sail.jpg deleted file mode 100644 index 5277bdafc..000000000 Binary files a/pr-248/assets/images/sailing/points_of_sail.jpg and /dev/null differ diff --git a/pr-248/assets/images/sailing/regions_of_hull.jpg b/pr-248/assets/images/sailing/regions_of_hull.jpg deleted file mode 100644 index 0bc7f48c9..000000000 Binary files a/pr-248/assets/images/sailing/regions_of_hull.jpg and /dev/null differ diff --git a/pr-248/assets/images/sailing/tack.jpg b/pr-248/assets/images/sailing/tack.jpg deleted file mode 100644 index aa43aa5ac..000000000 Binary files a/pr-248/assets/images/sailing/tack.jpg and /dev/null differ diff --git a/pr-248/assets/images/sailing/tack_other_meaning.jpg b/pr-248/assets/images/sailing/tack_other_meaning.jpg deleted file mode 100644 index f484c73af..000000000 Binary files a/pr-248/assets/images/sailing/tack_other_meaning.jpg and /dev/null differ diff --git a/pr-248/assets/images/sailing/track_made_good.jpg b/pr-248/assets/images/sailing/track_made_good.jpg deleted file mode 100644 index eb91a9bed..000000000 Binary files a/pr-248/assets/images/sailing/track_made_good.jpg and /dev/null differ diff --git a/pr-248/assets/images/sailing/types_of_turn.jpg b/pr-248/assets/images/sailing/types_of_turn.jpg deleted file mode 100644 index 38be9dda1..000000000 Binary files a/pr-248/assets/images/sailing/types_of_turn.jpg and /dev/null differ diff --git a/pr-248/assets/images/sailing/upwind_downwind_sailing.jpg b/pr-248/assets/images/sailing/upwind_downwind_sailing.jpg deleted file mode 100644 index 1e2e045be..000000000 Binary files a/pr-248/assets/images/sailing/upwind_downwind_sailing.jpg and /dev/null differ diff --git a/pr-248/assets/images/sailing/wind_types.jpg b/pr-248/assets/images/sailing/wind_types.jpg deleted file mode 100644 index 8c7c23d4b..000000000 Binary files a/pr-248/assets/images/sailing/wind_types.jpg and /dev/null differ diff --git a/pr-248/assets/images/social/current/boat_simulator/overview.png b/pr-248/assets/images/social/current/boat_simulator/overview.png deleted file mode 100644 index e69de29bb..000000000 diff --git a/pr-248/assets/images/social/current/controller/overview.png b/pr-248/assets/images/social/current/controller/overview.png deleted file mode 100644 index f97aaa850..000000000 Binary files a/pr-248/assets/images/social/current/controller/overview.png and /dev/null differ diff --git a/pr-248/assets/images/social/current/custom_interfaces/overview.png b/pr-248/assets/images/social/current/custom_interfaces/overview.png deleted file mode 100644 index f97aaa850..000000000 Binary files a/pr-248/assets/images/social/current/custom_interfaces/overview.png and /dev/null differ diff --git a/pr-248/assets/images/social/current/docs/overview.png b/pr-248/assets/images/social/current/docs/overview.png deleted file mode 100644 index f97aaa850..000000000 Binary files a/pr-248/assets/images/social/current/docs/overview.png and /dev/null differ diff --git a/pr-248/assets/images/social/current/local_pathfinding/overview.png b/pr-248/assets/images/social/current/local_pathfinding/overview.png deleted file mode 100644 index f97aaa850..000000000 Binary files a/pr-248/assets/images/social/current/local_pathfinding/overview.png and /dev/null differ diff --git a/pr-248/assets/images/social/current/network_systems/overview.png b/pr-248/assets/images/social/current/network_systems/overview.png deleted file mode 100644 index f97aaa850..000000000 Binary files a/pr-248/assets/images/social/current/network_systems/overview.png and /dev/null differ diff --git a/pr-248/assets/images/social/current/notebooks/overview.png b/pr-248/assets/images/social/current/notebooks/overview.png deleted file mode 100644 index f97aaa850..000000000 Binary files a/pr-248/assets/images/social/current/notebooks/overview.png and /dev/null differ diff --git a/pr-248/assets/images/social/current/overview.png b/pr-248/assets/images/social/current/overview.png deleted file mode 100644 index f97aaa850..000000000 Binary files a/pr-248/assets/images/social/current/overview.png and /dev/null differ diff --git a/pr-248/assets/images/social/current/sailbot_workspace/deployment.png b/pr-248/assets/images/social/current/sailbot_workspace/deployment.png deleted file mode 100644 index b167be6fc..000000000 Binary files a/pr-248/assets/images/social/current/sailbot_workspace/deployment.png and /dev/null differ diff --git a/pr-248/assets/images/social/current/sailbot_workspace/docker_images.png b/pr-248/assets/images/social/current/sailbot_workspace/docker_images.png deleted file mode 100644 index 842536b45..000000000 Binary files a/pr-248/assets/images/social/current/sailbot_workspace/docker_images.png and /dev/null differ diff --git a/pr-248/assets/images/social/current/sailbot_workspace/how_to.png b/pr-248/assets/images/social/current/sailbot_workspace/how_to.png deleted file mode 100644 index fef40ab73..000000000 Binary files a/pr-248/assets/images/social/current/sailbot_workspace/how_to.png and /dev/null differ diff --git a/pr-248/assets/images/social/current/sailbot_workspace/launch_files.png b/pr-248/assets/images/social/current/sailbot_workspace/launch_files.png deleted file mode 100644 index c0c65b442..000000000 Binary files a/pr-248/assets/images/social/current/sailbot_workspace/launch_files.png and /dev/null differ diff --git a/pr-248/assets/images/social/current/sailbot_workspace/overview.png b/pr-248/assets/images/social/current/sailbot_workspace/overview.png deleted file mode 100644 index f97aaa850..000000000 Binary files a/pr-248/assets/images/social/current/sailbot_workspace/overview.png and /dev/null differ diff --git a/pr-248/assets/images/social/current/sailbot_workspace/parameters.png b/pr-248/assets/images/social/current/sailbot_workspace/parameters.png deleted file mode 100644 index e845c76a9..000000000 Binary files a/pr-248/assets/images/social/current/sailbot_workspace/parameters.png and /dev/null differ diff --git a/pr-248/assets/images/social/current/sailbot_workspace/setup.png b/pr-248/assets/images/social/current/sailbot_workspace/setup.png deleted file mode 100644 index 80b2c573c..000000000 Binary files a/pr-248/assets/images/social/current/sailbot_workspace/setup.png and /dev/null differ diff --git a/pr-248/assets/images/social/current/sailbot_workspace/workflow.png b/pr-248/assets/images/social/current/sailbot_workspace/workflow.png deleted file mode 100644 index 2c0fdc6e5..000000000 Binary files a/pr-248/assets/images/social/current/sailbot_workspace/workflow.png and /dev/null differ diff --git a/pr-248/assets/images/social/current/website/overview.png b/pr-248/assets/images/social/current/website/overview.png deleted file mode 100644 index f97aaa850..000000000 Binary files a/pr-248/assets/images/social/current/website/overview.png and /dev/null differ diff --git a/pr-248/assets/images/social/index.png b/pr-248/assets/images/social/index.png deleted file mode 100644 index 44db42150..000000000 Binary files a/pr-248/assets/images/social/index.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/cpp/differences.png b/pr-248/assets/images/social/reference/cpp/differences.png deleted file mode 100644 index 4bd90ca05..000000000 Binary files a/pr-248/assets/images/social/reference/cpp/differences.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/cpp/start.png b/pr-248/assets/images/social/reference/cpp/start.png deleted file mode 100644 index 7e6c154d7..000000000 Binary files a/pr-248/assets/images/social/reference/cpp/start.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/cpp/tools.png b/pr-248/assets/images/social/reference/cpp/tools.png deleted file mode 100644 index 9ccad1c5d..000000000 Binary files a/pr-248/assets/images/social/reference/cpp/tools.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/github/workflow/branches.png b/pr-248/assets/images/social/reference/github/workflow/branches.png deleted file mode 100644 index 8f7729daf..000000000 Binary files a/pr-248/assets/images/social/reference/github/workflow/branches.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/github/workflow/issues.png b/pr-248/assets/images/social/reference/github/workflow/issues.png deleted file mode 100644 index 9476a57f5..000000000 Binary files a/pr-248/assets/images/social/reference/github/workflow/issues.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/github/workflow/overview.png b/pr-248/assets/images/social/reference/github/workflow/overview.png deleted file mode 100644 index f97aaa850..000000000 Binary files a/pr-248/assets/images/social/reference/github/workflow/overview.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/github/workflow/pr.png b/pr-248/assets/images/social/reference/github/workflow/pr.png deleted file mode 100644 index 485248d92..000000000 Binary files a/pr-248/assets/images/social/reference/github/workflow/pr.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/markdown.png b/pr-248/assets/images/social/reference/markdown.png deleted file mode 100644 index fc7c5444d..000000000 Binary files a/pr-248/assets/images/social/reference/markdown.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/python/conventions.png b/pr-248/assets/images/social/reference/python/conventions.png deleted file mode 100644 index c03ad4ecc..000000000 Binary files a/pr-248/assets/images/social/reference/python/conventions.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/python/start.png b/pr-248/assets/images/social/reference/python/start.png deleted file mode 100644 index 7e6c154d7..000000000 Binary files a/pr-248/assets/images/social/reference/python/start.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/python/virtual-environments.png b/pr-248/assets/images/social/reference/python/virtual-environments.png deleted file mode 100644 index 2c83bb343..000000000 Binary files a/pr-248/assets/images/social/reference/python/virtual-environments.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/ros.png b/pr-248/assets/images/social/reference/ros.png deleted file mode 100644 index b31fcf557..000000000 Binary files a/pr-248/assets/images/social/reference/ros.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/sailing/ais_terms.png b/pr-248/assets/images/social/reference/sailing/ais_terms.png deleted file mode 100644 index df902dd86..000000000 Binary files a/pr-248/assets/images/social/reference/sailing/ais_terms.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/sailing/boat_parts.png b/pr-248/assets/images/social/reference/sailing/boat_parts.png deleted file mode 100644 index 458e6a489..000000000 Binary files a/pr-248/assets/images/social/reference/sailing/boat_parts.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/sailing/miscellaneous.png b/pr-248/assets/images/social/reference/sailing/miscellaneous.png deleted file mode 100644 index 41cf4f6f9..000000000 Binary files a/pr-248/assets/images/social/reference/sailing/miscellaneous.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/sailing/overview.png b/pr-248/assets/images/social/reference/sailing/overview.png deleted file mode 100644 index f97aaa850..000000000 Binary files a/pr-248/assets/images/social/reference/sailing/overview.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/sailing/points_of_sail.png b/pr-248/assets/images/social/reference/sailing/points_of_sail.png deleted file mode 100644 index 9c0c7c8a4..000000000 Binary files a/pr-248/assets/images/social/reference/sailing/points_of_sail.png and /dev/null differ diff --git a/pr-248/assets/images/social/reference/sailing/turning.png b/pr-248/assets/images/social/reference/sailing/turning.png deleted file mode 100644 index 1fa8214ed..000000000 Binary files a/pr-248/assets/images/social/reference/sailing/turning.png and /dev/null differ diff --git a/pr-248/assets/javascripts/bundle.c8d2eff1.min.js b/pr-248/assets/javascripts/bundle.c8d2eff1.min.js deleted file mode 100644 index 4b1b31f52..000000000 --- a/pr-248/assets/javascripts/bundle.c8d2eff1.min.js +++ /dev/null @@ -1,29 +0,0 @@ -"use strict";(()=>{var _i=Object.create;var br=Object.defineProperty;var Ai=Object.getOwnPropertyDescriptor;var Ci=Object.getOwnPropertyNames,Ft=Object.getOwnPropertySymbols,ki=Object.getPrototypeOf,vr=Object.prototype.hasOwnProperty,eo=Object.prototype.propertyIsEnumerable;var Zr=(e,t,r)=>t in e?br(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,F=(e,t)=>{for(var r in t||(t={}))vr.call(t,r)&&Zr(e,r,t[r]);if(Ft)for(var r of Ft(t))eo.call(t,r)&&Zr(e,r,t[r]);return e};var to=(e,t)=>{var r={};for(var o in e)vr.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Ft)for(var o of Ft(e))t.indexOf(o)<0&&eo.call(e,o)&&(r[o]=e[o]);return r};var gr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Hi=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Ci(t))!vr.call(e,n)&&n!==r&&br(e,n,{get:()=>t[n],enumerable:!(o=Ai(t,n))||o.enumerable});return e};var jt=(e,t,r)=>(r=e!=null?_i(ki(e)):{},Hi(t||!e||!e.__esModule?br(r,"default",{value:e,enumerable:!0}):r,e));var ro=(e,t,r)=>new Promise((o,n)=>{var i=c=>{try{a(r.next(c))}catch(p){n(p)}},s=c=>{try{a(r.throw(c))}catch(p){n(p)}},a=c=>c.done?o(c.value):Promise.resolve(c.value).then(i,s);a((r=r.apply(e,t)).next())});var no=gr((xr,oo)=>{(function(e,t){typeof xr=="object"&&typeof oo!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(xr,function(){"use strict";function e(r){var o=!0,n=!1,i=null,s={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function a(C){return!!(C&&C!==document&&C.nodeName!=="HTML"&&C.nodeName!=="BODY"&&"classList"in C&&"contains"in C.classList)}function c(C){var ct=C.type,Ne=C.tagName;return!!(Ne==="INPUT"&&s[ct]&&!C.readOnly||Ne==="TEXTAREA"&&!C.readOnly||C.isContentEditable)}function p(C){C.classList.contains("focus-visible")||(C.classList.add("focus-visible"),C.setAttribute("data-focus-visible-added",""))}function l(C){C.hasAttribute("data-focus-visible-added")&&(C.classList.remove("focus-visible"),C.removeAttribute("data-focus-visible-added"))}function f(C){C.metaKey||C.altKey||C.ctrlKey||(a(r.activeElement)&&p(r.activeElement),o=!0)}function u(C){o=!1}function h(C){a(C.target)&&(o||c(C.target))&&p(C.target)}function w(C){a(C.target)&&(C.target.classList.contains("focus-visible")||C.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(C.target))}function A(C){document.visibilityState==="hidden"&&(n&&(o=!0),Z())}function Z(){document.addEventListener("mousemove",J),document.addEventListener("mousedown",J),document.addEventListener("mouseup",J),document.addEventListener("pointermove",J),document.addEventListener("pointerdown",J),document.addEventListener("pointerup",J),document.addEventListener("touchmove",J),document.addEventListener("touchstart",J),document.addEventListener("touchend",J)}function te(){document.removeEventListener("mousemove",J),document.removeEventListener("mousedown",J),document.removeEventListener("mouseup",J),document.removeEventListener("pointermove",J),document.removeEventListener("pointerdown",J),document.removeEventListener("pointerup",J),document.removeEventListener("touchmove",J),document.removeEventListener("touchstart",J),document.removeEventListener("touchend",J)}function J(C){C.target.nodeName&&C.target.nodeName.toLowerCase()==="html"||(o=!1,te())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",A,!0),Z(),r.addEventListener("focus",h,!0),r.addEventListener("blur",w,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var zr=gr((kt,Vr)=>{/*! - * clipboard.js v2.0.11 - * https://clipboardjs.com/ - * - * Licensed MIT © Zeno Rocha - */(function(t,r){typeof kt=="object"&&typeof Vr=="object"?Vr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof kt=="object"?kt.ClipboardJS=r():t.ClipboardJS=r()})(kt,function(){return function(){var e={686:function(o,n,i){"use strict";i.d(n,{default:function(){return Li}});var s=i(279),a=i.n(s),c=i(370),p=i.n(c),l=i(817),f=i.n(l);function u(D){try{return document.execCommand(D)}catch(M){return!1}}var h=function(M){var O=f()(M);return u("cut"),O},w=h;function A(D){var M=document.documentElement.getAttribute("dir")==="rtl",O=document.createElement("textarea");O.style.fontSize="12pt",O.style.border="0",O.style.padding="0",O.style.margin="0",O.style.position="absolute",O.style[M?"right":"left"]="-9999px";var I=window.pageYOffset||document.documentElement.scrollTop;return O.style.top="".concat(I,"px"),O.setAttribute("readonly",""),O.value=D,O}var Z=function(M,O){var I=A(M);O.container.appendChild(I);var W=f()(I);return u("copy"),I.remove(),W},te=function(M){var O=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},I="";return typeof M=="string"?I=Z(M,O):M instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(M==null?void 0:M.type)?I=Z(M.value,O):(I=f()(M),u("copy")),I},J=te;function C(D){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?C=function(O){return typeof O}:C=function(O){return O&&typeof Symbol=="function"&&O.constructor===Symbol&&O!==Symbol.prototype?"symbol":typeof O},C(D)}var ct=function(){var M=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},O=M.action,I=O===void 0?"copy":O,W=M.container,K=M.target,Ce=M.text;if(I!=="copy"&&I!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(K!==void 0)if(K&&C(K)==="object"&&K.nodeType===1){if(I==="copy"&&K.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(I==="cut"&&(K.hasAttribute("readonly")||K.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Ce)return J(Ce,{container:W});if(K)return I==="cut"?w(K):J(K,{container:W})},Ne=ct;function Pe(D){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Pe=function(O){return typeof O}:Pe=function(O){return O&&typeof Symbol=="function"&&O.constructor===Symbol&&O!==Symbol.prototype?"symbol":typeof O},Pe(D)}function xi(D,M){if(!(D instanceof M))throw new TypeError("Cannot call a class as a function")}function Xr(D,M){for(var O=0;O0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof W.action=="function"?W.action:this.defaultAction,this.target=typeof W.target=="function"?W.target:this.defaultTarget,this.text=typeof W.text=="function"?W.text:this.defaultText,this.container=Pe(W.container)==="object"?W.container:document.body}},{key:"listenClick",value:function(W){var K=this;this.listener=p()(W,"click",function(Ce){return K.onClick(Ce)})}},{key:"onClick",value:function(W){var K=W.delegateTarget||W.currentTarget,Ce=this.action(K)||"copy",It=Ne({action:Ce,container:this.container,target:this.target(K),text:this.text(K)});this.emit(It?"success":"error",{action:Ce,text:It,trigger:K,clearSelection:function(){K&&K.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(W){return hr("action",W)}},{key:"defaultTarget",value:function(W){var K=hr("target",W);if(K)return document.querySelector(K)}},{key:"defaultText",value:function(W){return hr("text",W)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(W){var K=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return J(W,K)}},{key:"cut",value:function(W){return w(W)}},{key:"isSupported",value:function(){var W=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],K=typeof W=="string"?[W]:W,Ce=!!document.queryCommandSupported;return K.forEach(function(It){Ce=Ce&&!!document.queryCommandSupported(It)}),Ce}}]),O}(a()),Li=Mi},828:function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function s(a,c){for(;a&&a.nodeType!==n;){if(typeof a.matches=="function"&&a.matches(c))return a;a=a.parentNode}}o.exports=s},438:function(o,n,i){var s=i(828);function a(l,f,u,h,w){var A=p.apply(this,arguments);return l.addEventListener(u,A,w),{destroy:function(){l.removeEventListener(u,A,w)}}}function c(l,f,u,h,w){return typeof l.addEventListener=="function"?a.apply(null,arguments):typeof u=="function"?a.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(A){return a(A,f,u,h,w)}))}function p(l,f,u,h){return function(w){w.delegateTarget=s(w.target,f),w.delegateTarget&&h.call(l,w)}}o.exports=c},879:function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var s=Object.prototype.toString.call(i);return i!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var s=Object.prototype.toString.call(i);return s==="[object Function]"}},370:function(o,n,i){var s=i(879),a=i(438);function c(u,h,w){if(!u&&!h&&!w)throw new Error("Missing required arguments");if(!s.string(h))throw new TypeError("Second argument must be a String");if(!s.fn(w))throw new TypeError("Third argument must be a Function");if(s.node(u))return p(u,h,w);if(s.nodeList(u))return l(u,h,w);if(s.string(u))return f(u,h,w);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function p(u,h,w){return u.addEventListener(h,w),{destroy:function(){u.removeEventListener(h,w)}}}function l(u,h,w){return Array.prototype.forEach.call(u,function(A){A.addEventListener(h,w)}),{destroy:function(){Array.prototype.forEach.call(u,function(A){A.removeEventListener(h,w)})}}}function f(u,h,w){return a(document.body,u,h,w)}o.exports=c},817:function(o){function n(i){var s;if(i.nodeName==="SELECT")i.focus(),s=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var a=i.hasAttribute("readonly");a||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),a||i.removeAttribute("readonly"),s=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),p=document.createRange();p.selectNodeContents(i),c.removeAllRanges(),c.addRange(p),s=c.toString()}return s}o.exports=n},279:function(o){function n(){}n.prototype={on:function(i,s,a){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:s,ctx:a}),this},once:function(i,s,a){var c=this;function p(){c.off(i,p),s.apply(a,arguments)}return p._=s,this.on(i,p,a)},emit:function(i){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[i]||[]).slice(),c=0,p=a.length;for(c;c{"use strict";/*! - * escape-html - * Copyright(c) 2012-2013 TJ Holowaychuk - * Copyright(c) 2015 Andreas Lubbe - * Copyright(c) 2015 Tiancheng "Timothy" Gu - * MIT Licensed - */var Va=/["'&<>]/;qn.exports=za;function za(e){var t=""+e,r=Va.exec(t);if(!r)return t;var o,n="",i=0,s=0;for(i=r.index;i0&&i[i.length-1])&&(p[0]===6||p[0]===2)){r=0;continue}if(p[0]===3&&(!i||p[1]>i[0]&&p[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function V(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],s;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(a){s={error:a}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(s)throw s.error}}return i}function z(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||a(u,h)})})}function a(u,h){try{c(o[u](h))}catch(w){f(i[0][3],w)}}function c(u){u.value instanceof ot?Promise.resolve(u.value.v).then(p,l):f(i[0][2],u)}function p(u){a("next",u)}function l(u){a("throw",u)}function f(u,h){u(h),i.shift(),i.length&&a(i[0][0],i[0][1])}}function so(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof ue=="function"?ue(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(s){return new Promise(function(a,c){s=e[i](s),n(a,c,s.done,s.value)})}}function n(i,s,a,c){Promise.resolve(c).then(function(p){i({value:p,done:a})},s)}}function k(e){return typeof e=="function"}function pt(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var Wt=pt(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: -`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` - `):"",this.name="UnsubscriptionError",this.errors=r}});function Ve(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ie=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var s=this._parentage;if(s)if(this._parentage=null,Array.isArray(s))try{for(var a=ue(s),c=a.next();!c.done;c=a.next()){var p=c.value;p.remove(this)}}catch(A){t={error:A}}finally{try{c&&!c.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}else s.remove(this);var l=this.initialTeardown;if(k(l))try{l()}catch(A){i=A instanceof Wt?A.errors:[A]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=ue(f),h=u.next();!h.done;h=u.next()){var w=h.value;try{co(w)}catch(A){i=i!=null?i:[],A instanceof Wt?i=z(z([],V(i)),V(A.errors)):i.push(A)}}}catch(A){o={error:A}}finally{try{h&&!h.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new Wt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)co(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Ve(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Ve(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Er=Ie.EMPTY;function Dt(e){return e instanceof Ie||e&&"closed"in e&&k(e.remove)&&k(e.add)&&k(e.unsubscribe)}function co(e){k(e)?e():e.unsubscribe()}var ke={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var lt={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,s=n.isStopped,a=n.observers;return i||s?Er:(this.currentObservers=null,a.push(r),new Ie(function(){o.currentObservers=null,Ve(a,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,s=o.isStopped;n?r.error(i):s&&r.complete()},t.prototype.asObservable=function(){var r=new j;return r.source=this,r},t.create=function(r,o){return new vo(r,o)},t}(j);var vo=function(e){se(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:Er},t}(v);var St={now:function(){return(St.delegate||Date).now()},delegate:void 0};var Ot=function(e){se(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=St);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,s=o._infiniteTimeWindow,a=o._timestampProvider,c=o._windowTime;n||(i.push(r),!s&&i.push(a.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,s=n._buffer,a=s.slice(),c=0;c0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=ut.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var s=r.actions;o!=null&&((i=s[s.length-1])===null||i===void 0?void 0:i.id)!==o&&(ut.cancelAnimationFrame(o),r._scheduled=void 0)},t}(zt);var yo=function(e){se(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o=this._scheduled;this._scheduled=void 0;var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t}(qt);var de=new yo(xo);var L=new j(function(e){return e.complete()});function Kt(e){return e&&k(e.schedule)}function _r(e){return e[e.length-1]}function Je(e){return k(_r(e))?e.pop():void 0}function Ae(e){return Kt(_r(e))?e.pop():void 0}function Qt(e,t){return typeof _r(e)=="number"?e.pop():t}var dt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Yt(e){return k(e==null?void 0:e.then)}function Bt(e){return k(e[ft])}function Gt(e){return Symbol.asyncIterator&&k(e==null?void 0:e[Symbol.asyncIterator])}function Jt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Di(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Xt=Di();function Zt(e){return k(e==null?void 0:e[Xt])}function er(e){return ao(this,arguments,function(){var r,o,n,i;return Ut(this,function(s){switch(s.label){case 0:r=e.getReader(),s.label=1;case 1:s.trys.push([1,,9,10]),s.label=2;case 2:return[4,ot(r.read())];case 3:return o=s.sent(),n=o.value,i=o.done,i?[4,ot(void 0)]:[3,5];case 4:return[2,s.sent()];case 5:return[4,ot(n)];case 6:return[4,s.sent()];case 7:return s.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function tr(e){return k(e==null?void 0:e.getReader)}function N(e){if(e instanceof j)return e;if(e!=null){if(Bt(e))return Ni(e);if(dt(e))return Vi(e);if(Yt(e))return zi(e);if(Gt(e))return Eo(e);if(Zt(e))return qi(e);if(tr(e))return Ki(e)}throw Jt(e)}function Ni(e){return new j(function(t){var r=e[ft]();if(k(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Vi(e){return new j(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?g(function(n,i){return e(n,i,o)}):ce,ye(1),r?Qe(t):jo(function(){return new or}))}}function $r(e){return e<=0?function(){return L}:x(function(t,r){var o=[];t.subscribe(S(r,function(n){o.push(n),e=2,!0))}function le(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new v}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,s=i===void 0?!0:i,a=e.resetOnRefCountZero,c=a===void 0?!0:a;return function(p){var l,f,u,h=0,w=!1,A=!1,Z=function(){f==null||f.unsubscribe(),f=void 0},te=function(){Z(),l=u=void 0,w=A=!1},J=function(){var C=l;te(),C==null||C.unsubscribe()};return x(function(C,ct){h++,!A&&!w&&Z();var Ne=u=u!=null?u:r();ct.add(function(){h--,h===0&&!A&&!w&&(f=Pr(J,c))}),Ne.subscribe(ct),!l&&h>0&&(l=new it({next:function(Pe){return Ne.next(Pe)},error:function(Pe){A=!0,Z(),f=Pr(te,n,Pe),Ne.error(Pe)},complete:function(){w=!0,Z(),f=Pr(te,s),Ne.complete()}}),N(C).subscribe(l))})(p)}}function Pr(e,t){for(var r=[],o=2;oe.next(document)),e}function R(e,t=document){return Array.from(t.querySelectorAll(e))}function P(e,t=document){let r=me(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function me(e,t=document){return t.querySelector(e)||void 0}function Re(){var e,t,r,o;return(o=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?o:void 0}var la=T(d(document.body,"focusin"),d(document.body,"focusout")).pipe(be(1),q(void 0),m(()=>Re()||document.body),B(1));function vt(e){return la.pipe(m(t=>e.contains(t)),Y())}function Vo(e,t){return T(d(e,"mouseenter").pipe(m(()=>!0)),d(e,"mouseleave").pipe(m(()=>!1))).pipe(t?be(t):ce,q(!1))}function Ue(e){return{x:e.offsetLeft,y:e.offsetTop}}function zo(e){return T(d(window,"load"),d(window,"resize")).pipe(Me(0,de),m(()=>Ue(e)),q(Ue(e)))}function ir(e){return{x:e.scrollLeft,y:e.scrollTop}}function et(e){return T(d(e,"scroll"),d(window,"resize")).pipe(Me(0,de),m(()=>ir(e)),q(ir(e)))}function qo(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)qo(e,r)}function E(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)qo(o,n);return o}function ar(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function gt(e){let t=E("script",{src:e});return H(()=>(document.head.appendChild(t),T(d(t,"load"),d(t,"error").pipe(b(()=>Ar(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),_(()=>document.head.removeChild(t)),ye(1))))}var Ko=new v,ma=H(()=>typeof ResizeObserver=="undefined"?gt("https://unpkg.com/resize-observer-polyfill"):$(void 0)).pipe(m(()=>new ResizeObserver(e=>{for(let t of e)Ko.next(t)})),b(e=>T(qe,$(e)).pipe(_(()=>e.disconnect()))),B(1));function pe(e){return{width:e.offsetWidth,height:e.offsetHeight}}function Ee(e){return ma.pipe(y(t=>t.observe(e)),b(t=>Ko.pipe(g(({target:r})=>r===e),_(()=>t.unobserve(e)),m(()=>pe(e)))),q(pe(e)))}function xt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function sr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var Qo=new v,fa=H(()=>$(new IntersectionObserver(e=>{for(let t of e)Qo.next(t)},{threshold:0}))).pipe(b(e=>T(qe,$(e)).pipe(_(()=>e.disconnect()))),B(1));function yt(e){return fa.pipe(y(t=>t.observe(e)),b(t=>Qo.pipe(g(({target:r})=>r===e),_(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function Yo(e,t=16){return et(e).pipe(m(({y:r})=>{let o=pe(e),n=xt(e);return r>=n.height-o.height-t}),Y())}var cr={drawer:P("[data-md-toggle=drawer]"),search:P("[data-md-toggle=search]")};function Bo(e){return cr[e].checked}function Be(e,t){cr[e].checked!==t&&cr[e].click()}function We(e){let t=cr[e];return d(t,"change").pipe(m(()=>t.checked),q(t.checked))}function ua(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function da(){return T(d(window,"compositionstart").pipe(m(()=>!0)),d(window,"compositionend").pipe(m(()=>!1))).pipe(q(!1))}function Go(){let e=d(window,"keydown").pipe(g(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:Bo("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),g(({mode:t,type:r})=>{if(t==="global"){let o=Re();if(typeof o!="undefined")return!ua(o,r)}return!0}),le());return da().pipe(b(t=>t?L:e))}function ve(){return new URL(location.href)}function st(e,t=!1){if(G("navigation.instant")&&!t){let r=E("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function Jo(){return new v}function Xo(){return location.hash.slice(1)}function Zo(e){let t=E("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function ha(e){return T(d(window,"hashchange"),e).pipe(m(Xo),q(Xo()),g(t=>t.length>0),B(1))}function en(e){return ha(e).pipe(m(t=>me(`[id="${t}"]`)),g(t=>typeof t!="undefined"))}function At(e){let t=matchMedia(e);return nr(r=>t.addListener(()=>r(t.matches))).pipe(q(t.matches))}function tn(){let e=matchMedia("print");return T(d(window,"beforeprint").pipe(m(()=>!0)),d(window,"afterprint").pipe(m(()=>!1))).pipe(q(e.matches))}function Ur(e,t){return e.pipe(b(r=>r?t():L))}function Wr(e,t){return new j(r=>{let o=new XMLHttpRequest;return o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network error"))}),o.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{var i;if(n.lengthComputable)t.progress$.next(n.loaded/n.total*100);else{let s=(i=o.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(n.loaded/+s*100)}}),t.progress$.next(5)),o.send(),()=>o.abort()})}function De(e,t){return Wr(e,t).pipe(b(r=>r.text()),m(r=>JSON.parse(r)),B(1))}function rn(e,t){let r=new DOMParser;return Wr(e,t).pipe(b(o=>o.text()),m(o=>r.parseFromString(o,"text/html")),B(1))}function on(e,t){let r=new DOMParser;return Wr(e,t).pipe(b(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),B(1))}function nn(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function an(){return T(d(window,"scroll",{passive:!0}),d(window,"resize",{passive:!0})).pipe(m(nn),q(nn()))}function sn(){return{width:innerWidth,height:innerHeight}}function cn(){return d(window,"resize",{passive:!0}).pipe(m(sn),q(sn()))}function pn(){return Q([an(),cn()]).pipe(m(([e,t])=>({offset:e,size:t})),B(1))}function pr(e,{viewport$:t,header$:r}){let o=t.pipe(X("size")),n=Q([o,r]).pipe(m(()=>Ue(e)));return Q([r,t,n]).pipe(m(([{height:i},{offset:s,size:a},{x:c,y:p}])=>({offset:{x:s.x-c,y:s.y-p+i},size:a})))}function ba(e){return d(e,"message",t=>t.data)}function va(e){let t=new v;return t.subscribe(r=>e.postMessage(r)),t}function ln(e,t=new Worker(e)){let r=ba(t),o=va(t),n=new v;n.subscribe(o);let i=o.pipe(ee(),oe(!0));return n.pipe(ee(),$e(r.pipe(U(i))),le())}var ga=P("#__config"),Et=JSON.parse(ga.textContent);Et.base=`${new URL(Et.base,ve())}`;function we(){return Et}function G(e){return Et.features.includes(e)}function ge(e,t){return typeof t!="undefined"?Et.translations[e].replace("#",t.toString()):Et.translations[e]}function Te(e,t=document){return P(`[data-md-component=${e}]`,t)}function ne(e,t=document){return R(`[data-md-component=${e}]`,t)}function xa(e){let t=P(".md-typeset > :first-child",e);return d(t,"click",{once:!0}).pipe(m(()=>P(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function mn(e){if(!G("announce.dismiss")||!e.childElementCount)return L;if(!e.hidden){let t=P(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return H(()=>{let t=new v;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),xa(e).pipe(y(r=>t.next(r)),_(()=>t.complete()),m(r=>F({ref:e},r)))})}function ya(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function fn(e,t){let r=new v;return r.subscribe(({hidden:o})=>{e.hidden=o}),ya(e,t).pipe(y(o=>r.next(o)),_(()=>r.complete()),m(o=>F({ref:e},o)))}function Ct(e,t){return t==="inline"?E("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},E("div",{class:"md-tooltip__inner md-typeset"})):E("div",{class:"md-tooltip",id:e,role:"tooltip"},E("div",{class:"md-tooltip__inner md-typeset"}))}function un(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return E("aside",{class:"md-annotation",tabIndex:0},Ct(t),E("a",{href:r,class:"md-annotation__index",tabIndex:-1},E("span",{"data-md-annotation-id":e})))}else return E("aside",{class:"md-annotation",tabIndex:0},Ct(t),E("span",{class:"md-annotation__index",tabIndex:-1},E("span",{"data-md-annotation-id":e})))}function dn(e){return E("button",{class:"md-clipboard md-icon",title:ge("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}function Dr(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(c=>!e.terms[c]).reduce((c,p)=>[...c,E("del",null,p)," "],[]).slice(0,-1),i=we(),s=new URL(e.location,i.base);G("search.highlight")&&s.searchParams.set("h",Object.entries(e.terms).filter(([,c])=>c).reduce((c,[p])=>`${c} ${p}`.trim(),""));let{tags:a}=we();return E("a",{href:`${s}`,class:"md-search-result__link",tabIndex:-1},E("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&E("div",{class:"md-search-result__icon md-icon"}),r>0&&E("h1",null,e.title),r<=0&&E("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&e.tags.map(c=>{let p=a?c in a?`md-tag-icon md-tag--${a[c]}`:"md-tag-icon":"";return E("span",{class:`md-tag ${p}`},c)}),o>0&&n.length>0&&E("p",{class:"md-search-result__terms"},ge("search.result.term.missing"),": ",...n)))}function hn(e){let t=e[0].score,r=[...e],o=we(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),s=r.findIndex(l=>l.scoreDr(l,1)),...c.length?[E("details",{class:"md-search-result__more"},E("summary",{tabIndex:-1},E("div",null,c.length>0&&c.length===1?ge("search.result.more.one"):ge("search.result.more.other",c.length))),...c.map(l=>Dr(l,1)))]:[]];return E("li",{class:"md-search-result__item"},p)}function bn(e){return E("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>E("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?ar(r):r)))}function Nr(e){let t=`tabbed-control tabbed-control--${e}`;return E("div",{class:t,hidden:!0},E("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function vn(e){return E("div",{class:"md-typeset__scrollwrap"},E("div",{class:"md-typeset__table"},e))}function Ea(e){let t=we(),r=new URL(`../${e.version}/`,t.base);return E("li",{class:"md-version__item"},E("a",{href:`${r}`,class:"md-version__link"},e.title))}function gn(e,t){return E("div",{class:"md-version"},E("button",{class:"md-version__current","aria-label":ge("select.version")},t.title),E("ul",{class:"md-version__list"},e.map(Ea)))}var wa=0;function Ta(e,t){document.body.append(e);let{width:r}=pe(e);e.style.setProperty("--md-tooltip-width",`${r}px`),e.remove();let o=sr(t),n=typeof o!="undefined"?et(o):$({x:0,y:0}),i=T(vt(t),Vo(t)).pipe(Y());return Q([i,n]).pipe(m(([s,a])=>{let{x:c,y:p}=Ue(t),l=pe(t),f=t.closest("table");return f&&t.parentElement&&(c+=f.offsetLeft+t.parentElement.offsetLeft,p+=f.offsetTop+t.parentElement.offsetTop),{active:s,offset:{x:c-a.x+l.width/2-r/2,y:p-a.y+l.height+8}}}))}function Ge(e){let t=e.title;if(!t.length)return L;let r=`__tooltip_${wa++}`,o=Ct(r,"inline"),n=P(".md-typeset",o);return n.innerHTML=t,H(()=>{let i=new v;return i.subscribe({next({offset:s}){o.style.setProperty("--md-tooltip-x",`${s.x}px`),o.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){o.style.removeProperty("--md-tooltip-x"),o.style.removeProperty("--md-tooltip-y")}}),T(i.pipe(g(({active:s})=>s)),i.pipe(be(250),g(({active:s})=>!s))).subscribe({next({active:s}){s?(e.insertAdjacentElement("afterend",o),e.setAttribute("aria-describedby",r),e.removeAttribute("title")):(o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t))},complete(){o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t)}}),i.pipe(Me(16,de)).subscribe(({active:s})=>{o.classList.toggle("md-tooltip--active",s)}),i.pipe(_t(125,de),g(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:s})=>s)).subscribe({next(s){s?o.style.setProperty("--md-tooltip-0",`${-s}px`):o.style.removeProperty("--md-tooltip-0")},complete(){o.style.removeProperty("--md-tooltip-0")}}),Ta(o,e).pipe(y(s=>i.next(s)),_(()=>i.complete()),m(s=>F({ref:e},s)))}).pipe(ze(ie))}function Sa(e,t){let r=H(()=>Q([zo(e),et(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:s,height:a}=pe(e);return{x:o-i.x+s/2,y:n-i.y+a/2}}));return vt(e).pipe(b(o=>r.pipe(m(n=>({active:o,offset:n})),ye(+!o||1/0))))}function xn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return H(()=>{let i=new v,s=i.pipe(ee(),oe(!0));return i.subscribe({next({offset:a}){e.style.setProperty("--md-tooltip-x",`${a.x}px`),e.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),yt(e).pipe(U(s)).subscribe(a=>{e.toggleAttribute("data-md-visible",a)}),T(i.pipe(g(({active:a})=>a)),i.pipe(be(250),g(({active:a})=>!a))).subscribe({next({active:a}){a?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe(Me(16,de)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(_t(125,de),g(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?e.style.setProperty("--md-tooltip-0",`${-a}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),d(n,"click").pipe(U(s),g(a=>!(a.metaKey||a.ctrlKey))).subscribe(a=>{a.stopPropagation(),a.preventDefault()}),d(n,"mousedown").pipe(U(s),ae(i)).subscribe(([a,{active:c}])=>{var p;if(a.button!==0||a.metaKey||a.ctrlKey)a.preventDefault();else if(c){a.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(p=Re())==null||p.blur()}}),r.pipe(U(s),g(a=>a===o),Ye(125)).subscribe(()=>e.focus()),Sa(e,t).pipe(y(a=>i.next(a)),_(()=>i.complete()),m(a=>F({ref:e},a)))})}function Oa(e){return e.tagName==="CODE"?R(".c, .c1, .cm",e):[e]}function Ma(e){let t=[];for(let r of Oa(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let s;for(;s=/(\(\d+\))(!)?/.exec(i.textContent);){let[,a,c]=s;if(typeof c=="undefined"){let p=i.splitText(s.index);i=p.splitText(a.length),t.push(p)}else{i.textContent=a,t.push(i);break}}}}return t}function yn(e,t){t.append(...Array.from(e.childNodes))}function lr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,s=new Map;for(let a of Ma(t)){let[,c]=a.textContent.match(/\((\d+)\)/);me(`:scope > li:nth-child(${c})`,e)&&(s.set(c,un(c,i)),a.replaceWith(s.get(c)))}return s.size===0?L:H(()=>{let a=new v,c=a.pipe(ee(),oe(!0)),p=[];for(let[l,f]of s)p.push([P(".md-typeset",f),P(`:scope > li:nth-child(${l})`,e)]);return o.pipe(U(c)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of p)l?yn(f,u):yn(u,f)}),T(...[...s].map(([,l])=>xn(l,t,{target$:r}))).pipe(_(()=>a.complete()),le())})}function En(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return En(t)}}function wn(e,t){return H(()=>{let r=En(e);return typeof r!="undefined"?lr(r,e,t):L})}var Tn=jt(zr());var La=0;function Sn(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return Sn(t)}}function _a(e){return Ee(e).pipe(m(({width:t})=>({scrollable:xt(e).width>t})),X("scrollable"))}function On(e,t){let{matches:r}=matchMedia("(hover)"),o=H(()=>{let n=new v,i=n.pipe($r(1));n.subscribe(({scrollable:c})=>{c&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let s=[];if(Tn.default.isSupported()&&(e.closest(".copy")||G("content.code.copy")&&!e.closest(".no-copy"))){let c=e.closest("pre");c.id=`__code_${La++}`;let p=dn(c.id);c.insertBefore(p,e),G("content.tooltips")&&s.push(Ge(p))}let a=e.closest(".highlight");if(a instanceof HTMLElement){let c=Sn(a);if(typeof c!="undefined"&&(a.classList.contains("annotate")||G("content.code.annotate"))){let p=lr(c,e,t);s.push(Ee(a).pipe(U(i),m(({width:l,height:f})=>l&&f),Y(),b(l=>l?p:L)))}}return _a(e).pipe(y(c=>n.next(c)),_(()=>n.complete()),m(c=>F({ref:e},c)),$e(...s))});return G("content.lazy")?yt(e).pipe(g(n=>n),ye(1),b(()=>o)):o}function Aa(e,{target$:t,print$:r}){let o=!0;return T(t.pipe(m(n=>n.closest("details:not([open])")),g(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(g(n=>n||!o),y(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Mn(e,t){return H(()=>{let r=new v;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),Aa(e,t).pipe(y(o=>r.next(o)),_(()=>r.complete()),m(o=>F({ref:e},o)))})}var Ln=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel rect,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel rect{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color);stroke-width:.05rem}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs #classDiagram-compositionEnd,defs #classDiagram-compositionStart,defs #classDiagram-dependencyEnd,defs #classDiagram-dependencyStart,defs #classDiagram-extensionEnd,defs #classDiagram-extensionStart{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs #classDiagram-aggregationEnd,defs #classDiagram-aggregationStart{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}.attributeBoxEven,.attributeBoxOdd{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityBox{fill:var(--md-mermaid-label-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityLabel{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.relationshipLabelBox{fill:var(--md-mermaid-label-bg-color);fill-opacity:1;background-color:var(--md-mermaid-label-bg-color);opacity:1}.relationshipLabel{fill:var(--md-mermaid-label-fg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs #ONE_OR_MORE_END *,defs #ONE_OR_MORE_START *,defs #ONLY_ONE_END *,defs #ONLY_ONE_START *,defs #ZERO_OR_MORE_END *,defs #ZERO_OR_MORE_START *,defs #ZERO_OR_ONE_END *,defs #ZERO_OR_ONE_START *{stroke:var(--md-mermaid-edge-color)!important}defs #ZERO_OR_MORE_END circle,defs #ZERO_OR_MORE_START circle{fill:var(--md-mermaid-label-bg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var qr,ka=0;function Ha(){return typeof mermaid=="undefined"||mermaid instanceof Element?gt("https://unpkg.com/mermaid@10.7.0/dist/mermaid.min.js"):$(void 0)}function _n(e){return e.classList.remove("mermaid"),qr||(qr=Ha().pipe(y(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Ln,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),B(1))),qr.subscribe(()=>ro(this,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${ka++}`,r=E("div",{class:"mermaid"}),o=e.textContent,{svg:n,fn:i}=yield mermaid.render(t,o),s=r.attachShadow({mode:"closed"});s.innerHTML=n,e.replaceWith(r),i==null||i(s)})),qr.pipe(m(()=>({ref:e})))}var An=E("table");function Cn(e){return e.replaceWith(An),An.replaceWith(vn(e)),$({ref:e})}function $a(e){let t=e.find(r=>r.checked)||e[0];return T(...e.map(r=>d(r,"change").pipe(m(()=>P(`label[for="${r.id}"]`))))).pipe(q(P(`label[for="${t.id}"]`)),m(r=>({active:r})))}function kn(e,{viewport$:t,target$:r}){let o=P(".tabbed-labels",e),n=R(":scope > input",e),i=Nr("prev");e.append(i);let s=Nr("next");return e.append(s),H(()=>{let a=new v,c=a.pipe(ee(),oe(!0));Q([a,Ee(e)]).pipe(U(c),Me(1,de)).subscribe({next([{active:p},l]){let f=Ue(p),{width:u}=pe(p);e.style.setProperty("--md-indicator-x",`${f.x}px`),e.style.setProperty("--md-indicator-width",`${u}px`);let h=ir(o);(f.xh.x+l.width)&&o.scrollTo({left:Math.max(0,f.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),Q([et(o),Ee(o)]).pipe(U(c)).subscribe(([p,l])=>{let f=xt(o);i.hidden=p.x<16,s.hidden=p.x>f.width-l.width-16}),T(d(i,"click").pipe(m(()=>-1)),d(s,"click").pipe(m(()=>1))).pipe(U(c)).subscribe(p=>{let{width:l}=pe(o);o.scrollBy({left:l*p,behavior:"smooth"})}),r.pipe(U(c),g(p=>n.includes(p))).subscribe(p=>p.click()),o.classList.add("tabbed-labels--linked");for(let p of n){let l=P(`label[for="${p.id}"]`);l.replaceChildren(E("a",{href:`#${l.htmlFor}`,tabIndex:-1},...Array.from(l.childNodes))),d(l.firstElementChild,"click").pipe(U(c),g(f=>!(f.metaKey||f.ctrlKey)),y(f=>{f.preventDefault(),f.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${l.htmlFor}`),l.click()})}return G("content.tabs.link")&&a.pipe(Le(1),ae(t)).subscribe(([{active:p},{offset:l}])=>{let f=p.innerText.trim();if(p.hasAttribute("data-md-switching"))p.removeAttribute("data-md-switching");else{let u=e.offsetTop-l.y;for(let w of R("[data-tabs]"))for(let A of R(":scope > input",w)){let Z=P(`label[for="${A.id}"]`);if(Z!==p&&Z.innerText.trim()===f){Z.setAttribute("data-md-switching",""),A.click();break}}window.scrollTo({top:e.offsetTop-u});let h=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([f,...h])])}}),a.pipe(U(c)).subscribe(()=>{for(let p of R("audio, video",e))p.pause()}),$a(n).pipe(y(p=>a.next(p)),_(()=>a.complete()),m(p=>F({ref:e},p)))}).pipe(ze(ie))}function Hn(e,{viewport$:t,target$:r,print$:o}){return T(...R(".annotate:not(.highlight)",e).map(n=>wn(n,{target$:r,print$:o})),...R("pre:not(.mermaid) > code",e).map(n=>On(n,{target$:r,print$:o})),...R("pre.mermaid",e).map(n=>_n(n)),...R("table:not([class])",e).map(n=>Cn(n)),...R("details",e).map(n=>Mn(n,{target$:r,print$:o})),...R("[data-tabs]",e).map(n=>kn(n,{viewport$:t,target$:r})),...R("[title]",e).filter(()=>G("content.tooltips")).map(n=>Ge(n)))}function Ra(e,{alert$:t}){return t.pipe(b(r=>T($(!0),$(!1).pipe(Ye(2e3))).pipe(m(o=>({message:r,active:o})))))}function $n(e,t){let r=P(".md-typeset",e);return H(()=>{let o=new v;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),Ra(e,t).pipe(y(n=>o.next(n)),_(()=>o.complete()),m(n=>F({ref:e},n)))})}function Pa({viewport$:e}){if(!G("header.autohide"))return $(!1);let t=e.pipe(m(({offset:{y:n}})=>n),Ke(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),Y()),o=We("search");return Q([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),Y(),b(n=>n?r:$(!1)),q(!1))}function Rn(e,t){return H(()=>Q([Ee(e),Pa(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),Y((r,o)=>r.height===o.height&&r.hidden===o.hidden),B(1))}function Pn(e,{header$:t,main$:r}){return H(()=>{let o=new v,n=o.pipe(ee(),oe(!0));o.pipe(X("active"),je(t)).subscribe(([{active:s},{hidden:a}])=>{e.classList.toggle("md-header--shadow",s&&!a),e.hidden=a});let i=fe(R("[title]",e)).pipe(g(()=>G("content.tooltips")),re(s=>Ge(s)));return r.subscribe(o),t.pipe(U(n),m(s=>F({ref:e},s)),$e(i.pipe(U(n))))})}function Ia(e,{viewport$:t,header$:r}){return pr(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=pe(e);return{active:o>=n}}),X("active"))}function In(e,t){return H(()=>{let r=new v;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=me(".md-content h1");return typeof o=="undefined"?L:Ia(o,t).pipe(y(n=>r.next(n)),_(()=>r.complete()),m(n=>F({ref:e},n)))})}function Fn(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),Y()),n=o.pipe(b(()=>Ee(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),X("bottom"))));return Q([o,n,t]).pipe(m(([i,{top:s,bottom:a},{offset:{y:c},size:{height:p}}])=>(p=Math.max(0,p-Math.max(0,s-c,i)-Math.max(0,p+c-a)),{offset:s-i,height:p,active:s-i<=c})),Y((i,s)=>i.offset===s.offset&&i.height===s.height&&i.active===s.active))}function Fa(e){let t=__md_get("__palette")||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return $(...e).pipe(re(o=>d(o,"change").pipe(m(()=>o))),q(e[r]),m(o=>({index:e.indexOf(o),color:{media:o.getAttribute("data-md-color-media"),scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),B(1))}function jn(e){let t=R("input",e),r=E("meta",{name:"theme-color"});document.head.appendChild(r);let o=E("meta",{name:"color-scheme"});document.head.appendChild(o);let n=At("(prefers-color-scheme: light)");return H(()=>{let i=new v;return i.subscribe(s=>{if(document.body.setAttribute("data-md-color-switching",""),s.color.media==="(prefers-color-scheme)"){let a=matchMedia("(prefers-color-scheme: light)"),c=document.querySelector(a.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");s.color.scheme=c.getAttribute("data-md-color-scheme"),s.color.primary=c.getAttribute("data-md-color-primary"),s.color.accent=c.getAttribute("data-md-color-accent")}for(let[a,c]of Object.entries(s.color))document.body.setAttribute(`data-md-color-${a}`,c);for(let a=0;a{let s=Te("header"),a=window.getComputedStyle(s);return o.content=a.colorScheme,a.backgroundColor.match(/\d+/g).map(c=>(+c).toString(16).padStart(2,"0")).join("")})).subscribe(s=>r.content=`#${s}`),i.pipe(Oe(ie)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),Fa(t).pipe(U(n.pipe(Le(1))),at(),y(s=>i.next(s)),_(()=>i.complete()),m(s=>F({ref:e},s)))})}function Un(e,{progress$:t}){return H(()=>{let r=new v;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(y(o=>r.next({value:o})),_(()=>r.complete()),m(o=>({ref:e,value:o})))})}var Kr=jt(zr());function ja(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function Wn({alert$:e}){Kr.default.isSupported()&&new j(t=>{new Kr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||ja(P(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(y(t=>{t.trigger.focus()}),m(()=>ge("clipboard.copied"))).subscribe(e)}function Dn(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,e}function Ua(e,t){let r=new Map;for(let o of R("url",e)){let n=P("loc",o),i=[Dn(new URL(n.textContent),t)];r.set(`${i[0]}`,i);for(let s of R("[rel=alternate]",o)){let a=s.getAttribute("href");a!=null&&i.push(Dn(new URL(a),t))}}return r}function mr(e){return on(new URL("sitemap.xml",e)).pipe(m(t=>Ua(t,new URL(e))),he(()=>$(new Map)))}function Wa(e,t){if(!(e.target instanceof Element))return L;let r=e.target.closest("a");if(r===null)return L;if(r.target||e.metaKey||e.ctrlKey)return L;let o=new URL(r.href);return o.search=o.hash="",t.has(`${o}`)?(e.preventDefault(),$(new URL(r.href))):L}function Nn(e){let t=new Map;for(let r of R(":scope > *",e.head))t.set(r.outerHTML,r);return t}function Vn(e){for(let t of R("[href], [src]",e))for(let r of["href","src"]){let o=t.getAttribute(r);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){t[r]=t[r];break}}return $(e)}function Da(e){for(let o of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...G("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let n=me(o),i=me(o,e);typeof n!="undefined"&&typeof i!="undefined"&&n.replaceWith(i)}let t=Nn(document);for(let[o,n]of Nn(e))t.has(o)?t.delete(o):document.head.appendChild(n);for(let o of t.values()){let n=o.getAttribute("name");n!=="theme-color"&&n!=="color-scheme"&&o.remove()}let r=Te("container");return Fe(R("script",r)).pipe(b(o=>{let n=e.createElement("script");if(o.src){for(let i of o.getAttributeNames())n.setAttribute(i,o.getAttribute(i));return o.replaceWith(n),new j(i=>{n.onload=()=>i.complete()})}else return n.textContent=o.textContent,o.replaceWith(n),L}),ee(),oe(document))}function zn({location$:e,viewport$:t,progress$:r}){let o=we();if(location.protocol==="file:")return L;let n=mr(o.base);$(document).subscribe(Vn);let i=d(document.body,"click").pipe(je(n),b(([c,p])=>Wa(c,p)),le()),s=d(window,"popstate").pipe(m(ve),le());i.pipe(ae(t)).subscribe(([c,{offset:p}])=>{history.replaceState(p,""),history.pushState(null,"",c)}),T(i,s).subscribe(e);let a=e.pipe(X("pathname"),b(c=>rn(c,{progress$:r}).pipe(he(()=>(st(c,!0),L)))),b(Vn),b(Da),le());return T(a.pipe(ae(e,(c,p)=>p)),e.pipe(X("pathname"),b(()=>e),X("hash")),e.pipe(Y((c,p)=>c.pathname===p.pathname&&c.hash===p.hash),b(()=>i),y(()=>history.back()))).subscribe(c=>{var p,l;history.state!==null||!c.hash?window.scrollTo(0,(l=(p=history.state)==null?void 0:p.y)!=null?l:0):(history.scrollRestoration="auto",Zo(c.hash),history.scrollRestoration="manual")}),e.subscribe(()=>{history.scrollRestoration="manual"}),d(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),t.pipe(X("offset"),be(100)).subscribe(({offset:c})=>{history.replaceState(c,"")}),a}var Qn=jt(Kn());function Yn(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,s)=>`${i}${s}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return s=>(0,Qn.default)(s).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function Ht(e){return e.type===1}function fr(e){return e.type===3}function Bn(e,t){let r=ln(e);return T($(location.protocol!=="file:"),We("search")).pipe(He(o=>o),b(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:G("search.suggest")}}})),r}function Gn({document$:e}){let t=we(),r=De(new URL("../versions.json",t.base)).pipe(he(()=>L)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:s,aliases:a})=>s===i||a.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),b(n=>d(document.body,"click").pipe(g(i=>!i.metaKey&&!i.ctrlKey),ae(o),b(([i,s])=>{if(i.target instanceof Element){let a=i.target.closest("a");if(a&&!a.target&&n.has(a.href)){let c=a.href;return!i.target.closest(".md-version")&&n.get(c)===s?L:(i.preventDefault(),$(c))}}return L}),b(i=>{let{version:s}=n.get(i);return mr(new URL(i)).pipe(m(a=>{let p=ve().href.replace(t.base,"");return a.has(p.split("#")[0])?new URL(`../${s}/${p}`,t.base):new URL(i)}))})))).subscribe(n=>st(n,!0)),Q([r,o]).subscribe(([n,i])=>{P(".md-header__topic").appendChild(gn(n,i))}),e.pipe(b(()=>o)).subscribe(n=>{var s;let i=__md_get("__outdated",sessionStorage);if(i===null){i=!0;let a=((s=t.version)==null?void 0:s.default)||"latest";Array.isArray(a)||(a=[a]);e:for(let c of a)for(let p of n.aliases.concat(n.version))if(new RegExp(c,"i").test(p)){i=!1;break e}__md_set("__outdated",i,sessionStorage)}if(i)for(let a of ne("outdated"))a.hidden=!1})}function Ka(e,{worker$:t}){let{searchParams:r}=ve();r.has("q")&&(Be("search",!0),e.value=r.get("q"),e.focus(),We("search").pipe(He(i=>!i)).subscribe(()=>{let i=ve();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=vt(e),n=T(t.pipe(He(Ht)),d(e,"keyup"),o).pipe(m(()=>e.value),Y());return Q([n,o]).pipe(m(([i,s])=>({value:i,focus:s})),B(1))}function Jn(e,{worker$:t}){let r=new v,o=r.pipe(ee(),oe(!0));Q([t.pipe(He(Ht)),r],(i,s)=>s).pipe(X("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(X("focus")).subscribe(({focus:i})=>{i&&Be("search",i)}),d(e.form,"reset").pipe(U(o)).subscribe(()=>e.focus());let n=P("header [for=__search]");return d(n,"click").subscribe(()=>e.focus()),Ka(e,{worker$:t}).pipe(y(i=>r.next(i)),_(()=>r.complete()),m(i=>F({ref:e},i)),B(1))}function Xn(e,{worker$:t,query$:r}){let o=new v,n=Yo(e.parentElement).pipe(g(Boolean)),i=e.parentElement,s=P(":scope > :first-child",e),a=P(":scope > :last-child",e);We("search").subscribe(l=>a.setAttribute("role",l?"list":"presentation")),o.pipe(ae(r),Ir(t.pipe(He(Ht)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:s.textContent=f.length?ge("search.result.none"):ge("search.result.placeholder");break;case 1:s.textContent=ge("search.result.one");break;default:let u=ar(l.length);s.textContent=ge("search.result.other",u)}});let c=o.pipe(y(()=>a.innerHTML=""),b(({items:l})=>T($(...l.slice(0,10)),$(...l.slice(10)).pipe(Ke(4),jr(n),b(([f])=>f)))),m(hn),le());return c.subscribe(l=>a.appendChild(l)),c.pipe(re(l=>{let f=me("details",l);return typeof f=="undefined"?L:d(f,"toggle").pipe(U(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(g(fr),m(({data:l})=>l)).pipe(y(l=>o.next(l)),_(()=>o.complete()),m(l=>F({ref:e},l)))}function Qa(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=ve();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function Zn(e,t){let r=new v,o=r.pipe(ee(),oe(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),d(e,"click").pipe(U(o)).subscribe(n=>n.preventDefault()),Qa(e,t).pipe(y(n=>r.next(n)),_(()=>r.complete()),m(n=>F({ref:e},n)))}function ei(e,{worker$:t,keyboard$:r}){let o=new v,n=Te("search-query"),i=T(d(n,"keydown"),d(n,"focus")).pipe(Oe(ie),m(()=>n.value),Y());return o.pipe(je(i),m(([{suggest:a},c])=>{let p=c.split(/([\s-]+)/);if(a!=null&&a.length&&p[p.length-1]){let l=a[a.length-1];l.startsWith(p[p.length-1])&&(p[p.length-1]=l)}else p.length=0;return p})).subscribe(a=>e.innerHTML=a.join("").replace(/\s/g," ")),r.pipe(g(({mode:a})=>a==="search")).subscribe(a=>{switch(a.type){case"ArrowRight":e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText);break}}),t.pipe(g(fr),m(({data:a})=>a)).pipe(y(a=>o.next(a)),_(()=>o.complete()),m(()=>({ref:e})))}function ti(e,{index$:t,keyboard$:r}){let o=we();try{let n=Bn(o.search,t),i=Te("search-query",e),s=Te("search-result",e);d(e,"click").pipe(g(({target:c})=>c instanceof Element&&!!c.closest("a"))).subscribe(()=>Be("search",!1)),r.pipe(g(({mode:c})=>c==="search")).subscribe(c=>{let p=Re();switch(c.type){case"Enter":if(p===i){let l=new Map;for(let f of R(":first-child [href]",s)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,h])=>h-u);f.click()}c.claim()}break;case"Escape":case"Tab":Be("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof p=="undefined")i.focus();else{let l=[i,...R(":not(details) > [href], summary, details[open] [href]",s)],f=Math.max(0,(Math.max(0,l.indexOf(p))+l.length+(c.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}c.claim();break;default:i!==Re()&&i.focus()}}),r.pipe(g(({mode:c})=>c==="global")).subscribe(c=>{switch(c.type){case"f":case"s":case"/":i.focus(),i.select(),c.claim();break}});let a=Jn(i,{worker$:n});return T(a,Xn(s,{worker$:n,query$:a})).pipe($e(...ne("search-share",e).map(c=>Zn(c,{query$:a})),...ne("search-suggest",e).map(c=>ei(c,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,qe}}function ri(e,{index$:t,location$:r}){return Q([t,r.pipe(q(ve()),g(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>Yn(o.config)(n.searchParams.get("h"))),m(o=>{var s;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let a=i.nextNode();a;a=i.nextNode())if((s=a.parentElement)!=null&&s.offsetHeight){let c=a.textContent,p=o(c);p.length>c.length&&n.set(a,p)}for(let[a,c]of n){let{childNodes:p}=E("span",null,c);a.replaceWith(...Array.from(p))}return{ref:e,nodes:n}}))}function Ya(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return Q([r,t]).pipe(m(([{offset:i,height:s},{offset:{y:a}}])=>(s=s+Math.min(n,Math.max(0,a-i))-n,{height:s,locked:a>=i+n})),Y((i,s)=>i.height===s.height&&i.locked===s.locked))}function Qr(e,o){var n=o,{header$:t}=n,r=to(n,["header$"]);let i=P(".md-sidebar__scrollwrap",e),{y:s}=Ue(i);return H(()=>{let a=new v,c=a.pipe(ee(),oe(!0)),p=a.pipe(Me(0,de));return p.pipe(ae(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*s}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),p.pipe(He()).subscribe(()=>{for(let l of R(".md-nav__link--active[href]",e)){if(!l.clientHeight)continue;let f=l.closest(".md-sidebar__scrollwrap");if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:h}=pe(f);f.scrollTo({top:u-h/2})}}}),fe(R("label[tabindex]",e)).pipe(re(l=>d(l,"click").pipe(Oe(ie),m(()=>l),U(c)))).subscribe(l=>{let f=P(`[id="${l.htmlFor}"]`);P(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),Ya(e,r).pipe(y(l=>a.next(l)),_(()=>a.complete()),m(l=>F({ref:e},l)))})}function oi(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return Lt(De(`${r}/releases/latest`).pipe(he(()=>L),m(o=>({version:o.tag_name})),Qe({})),De(r).pipe(he(()=>L),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),Qe({}))).pipe(m(([o,n])=>F(F({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return De(r).pipe(m(o=>({repositories:o.public_repos})),Qe({}))}}function ni(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return De(r).pipe(he(()=>L),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),Qe({}))}function ii(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return oi(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return ni(r,o)}return L}var Ba;function Ga(e){return Ba||(Ba=H(()=>{let t=__md_get("__source",sessionStorage);if(t)return $(t);if(ne("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return L}return ii(e.href).pipe(y(o=>__md_set("__source",o,sessionStorage)))}).pipe(he(()=>L),g(t=>Object.keys(t).length>0),m(t=>({facts:t})),B(1)))}function ai(e){let t=P(":scope > :last-child",e);return H(()=>{let r=new v;return r.subscribe(({facts:o})=>{t.appendChild(bn(o)),t.classList.add("md-source__repository--active")}),Ga(e).pipe(y(o=>r.next(o)),_(()=>r.complete()),m(o=>F({ref:e},o)))})}function Ja(e,{viewport$:t,header$:r}){return Ee(document.body).pipe(b(()=>pr(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),X("hidden"))}function si(e,t){return H(()=>{let r=new v;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(G("navigation.tabs.sticky")?$({hidden:!1}):Ja(e,t)).pipe(y(o=>r.next(o)),_(()=>r.complete()),m(o=>F({ref:e},o)))})}function Xa(e,{viewport$:t,header$:r}){let o=new Map,n=R(".md-nav__link",e);for(let a of n){let c=decodeURIComponent(a.hash.substring(1)),p=me(`[id="${c}"]`);typeof p!="undefined"&&o.set(a,p)}let i=r.pipe(X("height"),m(({height:a})=>{let c=Te("main"),p=P(":scope > :first-child",c);return a+.8*(p.offsetTop-c.offsetTop)}),le());return Ee(document.body).pipe(X("height"),b(a=>H(()=>{let c=[];return $([...o].reduce((p,[l,f])=>{for(;c.length&&o.get(c[c.length-1]).tagName>=f.tagName;)c.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let h=f.offsetParent;for(;h;h=h.offsetParent)u+=h.offsetTop;return p.set([...c=[...c,l]].reverse(),u)},new Map))}).pipe(m(c=>new Map([...c].sort(([,p],[,l])=>p-l))),je(i),b(([c,p])=>t.pipe(Rr(([l,f],{offset:{y:u},size:h})=>{let w=u+h.height>=Math.floor(a.height);for(;f.length;){let[,A]=f[0];if(A-p=u&&!w)f=[l.pop(),...f];else break}return[l,f]},[[],[...c]]),Y((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([a,c])=>({prev:a.map(([p])=>p),next:c.map(([p])=>p)})),q({prev:[],next:[]}),Ke(2,1),m(([a,c])=>a.prev.length{let i=new v,s=i.pipe(ee(),oe(!0));if(i.subscribe(({prev:a,next:c})=>{for(let[p]of c)p.classList.remove("md-nav__link--passed"),p.classList.remove("md-nav__link--active");for(let[p,[l]]of a.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",p===a.length-1)}),G("toc.follow")){let a=T(t.pipe(be(1),m(()=>{})),t.pipe(be(250),m(()=>"smooth")));i.pipe(g(({prev:c})=>c.length>0),je(o.pipe(Oe(ie))),ae(a)).subscribe(([[{prev:c}],p])=>{let[l]=c[c.length-1];if(l.offsetHeight){let f=sr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:h}=pe(f);f.scrollTo({top:u-h/2,behavior:p})}}})}return G("navigation.tracking")&&t.pipe(U(s),X("offset"),be(250),Le(1),U(n.pipe(Le(1))),at({delay:250}),ae(i)).subscribe(([,{prev:a}])=>{let c=ve(),p=a[a.length-1];if(p&&p.length){let[l]=p,{hash:f}=new URL(l.href);c.hash!==f&&(c.hash=f,history.replaceState({},"",`${c}`))}else c.hash="",history.replaceState({},"",`${c}`)}),Xa(e,{viewport$:t,header$:r}).pipe(y(a=>i.next(a)),_(()=>i.complete()),m(a=>F({ref:e},a)))})}function Za(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:s}})=>s),Ke(2,1),m(([s,a])=>s>a&&a>0),Y()),i=r.pipe(m(({active:s})=>s));return Q([i,n]).pipe(m(([s,a])=>!(s&&a)),Y(),U(o.pipe(Le(1))),oe(!0),at({delay:250}),m(s=>({hidden:s})))}function pi(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new v,s=i.pipe(ee(),oe(!0));return i.subscribe({next({hidden:a}){e.hidden=a,a?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(U(s),X("height")).subscribe(({height:a})=>{e.style.top=`${a+16}px`}),d(e,"click").subscribe(a=>{a.preventDefault(),window.scrollTo({top:0})}),Za(e,{viewport$:t,main$:o,target$:n}).pipe(y(a=>i.next(a)),_(()=>i.complete()),m(a=>F({ref:e},a)))}function li({document$:e}){e.pipe(b(()=>R(".md-ellipsis")),re(t=>yt(t).pipe(U(e.pipe(Le(1))),g(r=>r),m(()=>t),ye(1))),g(t=>t.offsetWidth{let r=t.innerText,o=t.closest("a")||t;return o.title=r,Ge(o).pipe(U(e.pipe(Le(1))),_(()=>o.removeAttribute("title")))})).subscribe(),e.pipe(b(()=>R(".md-status")),re(t=>Ge(t))).subscribe()}function mi({document$:e,tablet$:t}){e.pipe(b(()=>R(".md-toggle--indeterminate")),y(r=>{r.indeterminate=!0,r.checked=!1}),re(r=>d(r,"change").pipe(Fr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),ae(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function es(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function fi({document$:e}){e.pipe(b(()=>R("[data-md-scrollfix]")),y(t=>t.removeAttribute("data-md-scrollfix")),g(es),re(t=>d(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function ui({viewport$:e,tablet$:t}){Q([We("search"),t]).pipe(m(([r,o])=>r&&!o),b(r=>$(r).pipe(Ye(r?400:100))),ae(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function ts(){return location.protocol==="file:"?gt(`${new URL("search/search_index.js",Yr.base)}`).pipe(m(()=>__index),B(1)):De(new URL("search/search_index.json",Yr.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var rt=No(),Rt=Jo(),wt=en(Rt),Br=Go(),_e=pn(),ur=At("(min-width: 960px)"),hi=At("(min-width: 1220px)"),bi=tn(),Yr=we(),vi=document.forms.namedItem("search")?ts():qe,Gr=new v;Wn({alert$:Gr});var Jr=new v;G("navigation.instant")&&zn({location$:Rt,viewport$:_e,progress$:Jr}).subscribe(rt);var di;((di=Yr.version)==null?void 0:di.provider)==="mike"&&Gn({document$:rt});T(Rt,wt).pipe(Ye(125)).subscribe(()=>{Be("drawer",!1),Be("search",!1)});Br.pipe(g(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=me("link[rel=prev]");typeof t!="undefined"&&st(t);break;case"n":case".":let r=me("link[rel=next]");typeof r!="undefined"&&st(r);break;case"Enter":let o=Re();o instanceof HTMLLabelElement&&o.click()}});li({document$:rt});mi({document$:rt,tablet$:ur});fi({document$:rt});ui({viewport$:_e,tablet$:ur});var tt=Rn(Te("header"),{viewport$:_e}),$t=rt.pipe(m(()=>Te("main")),b(e=>Fn(e,{viewport$:_e,header$:tt})),B(1)),rs=T(...ne("consent").map(e=>fn(e,{target$:wt})),...ne("dialog").map(e=>$n(e,{alert$:Gr})),...ne("header").map(e=>Pn(e,{viewport$:_e,header$:tt,main$:$t})),...ne("palette").map(e=>jn(e)),...ne("progress").map(e=>Un(e,{progress$:Jr})),...ne("search").map(e=>ti(e,{index$:vi,keyboard$:Br})),...ne("source").map(e=>ai(e))),os=H(()=>T(...ne("announce").map(e=>mn(e)),...ne("content").map(e=>Hn(e,{viewport$:_e,target$:wt,print$:bi})),...ne("content").map(e=>G("search.highlight")?ri(e,{index$:vi,location$:Rt}):L),...ne("header-title").map(e=>In(e,{viewport$:_e,header$:tt})),...ne("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?Ur(hi,()=>Qr(e,{viewport$:_e,header$:tt,main$:$t})):Ur(ur,()=>Qr(e,{viewport$:_e,header$:tt,main$:$t}))),...ne("tabs").map(e=>si(e,{viewport$:_e,header$:tt})),...ne("toc").map(e=>ci(e,{viewport$:_e,header$:tt,main$:$t,target$:wt})),...ne("top").map(e=>pi(e,{viewport$:_e,header$:tt,main$:$t,target$:wt})))),gi=rt.pipe(b(()=>os),$e(rs),B(1));gi.subscribe();window.document$=rt;window.location$=Rt;window.target$=wt;window.keyboard$=Br;window.viewport$=_e;window.tablet$=ur;window.screen$=hi;window.print$=bi;window.alert$=Gr;window.progress$=Jr;window.component$=gi;})(); -//# sourceMappingURL=bundle.c8d2eff1.min.js.map - diff --git a/pr-248/assets/javascripts/bundle.c8d2eff1.min.js.map b/pr-248/assets/javascripts/bundle.c8d2eff1.min.js.map deleted file mode 100644 index fc522dbae..000000000 --- a/pr-248/assets/javascripts/bundle.c8d2eff1.min.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/clipboard/dist/clipboard.js", "node_modules/escape-html/index.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/rxjs/node_modules/tslib/tslib.es6.js", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/hover/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/tooltip/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/components/progress/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/ellipsis/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], - "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountProgress,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantNavigation,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchEllipsis,\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 960px)\")\nconst screen$ = watchMedia(\"(min-width: 1220px)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up progress indicator */\nconst progress$ = new Subject()\n\n/* Set up instant navigation, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantNavigation({ location$, viewport$, progress$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchEllipsis({ document$ })\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Progress bar */\n ...getComponentElements(\"progress\")\n .map(el => mountProgress(el, { progress$ })),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.progress$ = progress$ /* Progress indicator subject */\nwindow.component$ = component$ /* Component observable */\n", "/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (_) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\r\n}) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n}\r\n\r\nexport function __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n o[\"default\"] = v;\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n *\n * @class Subscription\n */\nexport class Subscription implements SubscriptionLike {\n /** @nocollapse */\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n * @return {void}\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n *\n * @class Subscriber\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @nocollapse\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param {T} [value] The `next` value.\n * @return {void}\n */\n next(value?: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param {any} [err] The `error` exception.\n * @return {void}\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n * @return {void}\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as (((value: T) => void) | undefined),\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent\n * @param subscriber The stopped subscriber\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n *\n * @class Observable\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @constructor\n * @param {Function} subscribe the function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @owner Observable\n * @method create\n * @param {Function} subscribe? the subscriber function to be passed to the Observable constructor\n * @return {Observable} a new observable\n * @nocollapse\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @method lift\n * @param operator the operator defining the operation to take on the observable\n * @return a new observable with the Operator applied\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param {Observer|Function} observerOrNext (optional) Either an observer with methods to be called,\n * or the first of three possible handlers, which is the handler for each value emitted from the subscribed\n * Observable.\n * @param {Function} error (optional) A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param {Function} complete (optional) A handler for a terminal event resulting from successful completion.\n * @return {Subscription} a subscription reference to the registered handlers\n * @method subscribe\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next a handler for each value emitted by the observable\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @method Symbol.observable\n * @return {Observable} this instance of the observable\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n * @method pipe\n * @return {Observable} the Observable result of all of the operators having\n * been called in the order they were passed in.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @method toPromise\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @nocollapse\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return {Observable} Observable that the Subject casts to\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\n/**\n * @class AnonymousSubject\n */\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param bufferSize The size of the buffer to replay on subscription\n * @param windowTime The amount of time the buffered items will stay buffered\n * @param timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n *\n * @class Action\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler.\n * @return {void}\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n * @return {any}\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @class Scheduler\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return {number} A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param {function(state: ?T): ?Subscription} work A function representing a\n * task, or some unit of work to be executed by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler itself.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @return {Subscription} A subscription in order to be able to unsubscribe\n * the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @type {boolean}\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @type {any}\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n const flushId = this._scheduled;\n this._scheduled = undefined;\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:
\n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/current/sailbot_workspace/deployment/index.html b/pr-248/current/sailbot_workspace/deployment/index.html deleted file mode 100644 index 7d1af2412..000000000 --- a/pr-248/current/sailbot_workspace/deployment/index.html +++ /dev/null @@ -1,2352 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Deployment - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -
-

Source code

-

The source code for deployment -can be found in the sailbot_workspace -GitHub repository. Its README has been copied below.

-
-

Deployment

-

Deploying our software to our autonomous sailboat's main computer.

-

Scripts

-

start_container.sh

-

Runs the base -image. A new container is created every time this is run. The default container name is sailbot_deployment_container. -Container names are unique, so if you want to use multiple deployment containers (e.g., from different branches) -you will have to update the variable CONTAINER_NAME in the script.

-

Usage:

-
    -
  • Runs the base image used by the Dev Container by default: ./start_container.sh
  • -
  • Run a specific version of the base image by specifying its ID: ./start_container.sh <IMAGE_ID>
  • -
-

setup_boot.sh

-

Configures programs and scripts that need to run when the main computer boots. Only needs to be run once unless the -script is updated. Does not need to be rerun if any scripts or programs it targets are updated, with the exception of -renaming or moving the file.

-

Usage:

-
    -
  • Must be run as root
  • -
  • sudo ./setup_boot.sh
  • -
-

Deployment container commands

-
    -
  • Exit out of a container: exit
  • -
  • Start an existing container: docker start -ia <container name>
  • -
  • Delete an existing container: docker rm <container name>
  • -
  • Find the container ID of a container: docker ps -a
  • -
-

Deploy software

-
-

These commands are run in the in the root directory of this repository

-
-
    -
  1. Run the setup script: ./setup.sh
  2. -
  3. Run the build script with the quick build flag: ./build.sh -q
  4. -
-

Develop software

-

The deployment container isn't intended for development, but if you discover a bug and want to quickly push a fix:

-
    -
  1. Fix the issue in a terminal outside the deployment container
  2. -
  3. Run the software to verify your fix in a terminal inside the deployment container
  4. -
  5. Commit and push your fix in a terminal outside the deployment container
  6. -
- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/current/sailbot_workspace/docker_images/index.html b/pr-248/current/sailbot_workspace/docker_images/index.html deleted file mode 100644 index 397206027..000000000 --- a/pr-248/current/sailbot_workspace/docker_images/index.html +++ /dev/null @@ -1,2212 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Images - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -

Docker Images

-

A table detailing the Docker images used to create the Dev Container can be found below. -Click on an image to learn more about its features and how to update it.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ImageParent ImageSource CodeWhy it is RebuiltWhere it is Built
pre-baseUbuntu 22.04base-dev.DockerfileTo install ROS or OMPLPersonal computer
basepre-basebase-dev.DockerfileTo install core dependenciesWorkflow dispatch
local-basebasebase-dev.DockerfileTo install core dev dependenciesWorkflow dispatch
devlocal-basebase-dev.DockerfileTo install dev dependenciesWorkflow dispatch
Dev ContainerdevDockerfileTo configure the Dev ContainerVS Code
docsmkdocs-materialdocs.DockerfileTo install and run docs siteVS Code (optional)
websitejavascript-nodewebsite.DockerfileTo install and run websiteVS Code (optional)
- - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/current/sailbot_workspace/how_to/index.html b/pr-248/current/sailbot_workspace/how_to/index.html deleted file mode 100644 index c2cb3a728..000000000 --- a/pr-248/current/sailbot_workspace/how_to/index.html +++ /dev/null @@ -1,2853 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - How-To's - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - -

How-To's

-

Run VS Code commands, tasks, and launch configurations

-
-

MacOS keyboard shortcuts

-

For keyboard shortcuts on MacOS, substitute Ctrl with Cmd.

-
-

VS Code commands can be run in the Command Palette. -Open the Command Palette from the View menu or with Ctrl+Shift+P.

-

Tasks can be run using the Tasks: Run Task VS Code command. Build tasks can be run with Ctrl+Shift+B.

-

Launch configurations can be run from the Run and Debug view.

-

You can also run VS Code commands, tasks, launch configurations, and much more by typing their prefixes -into an empty Command Palette. Open an empty Command Palette with Ctrl+P or by clicking the box in the center -of the title bar. See the list below for some prefixes and their functions. -For prefixes that are words, you will have to append a space to them to bring up their functions.

-
    -
  • Nothing: files
  • -
  • >: VS Code commands
  • -
  • task: tasks
  • -
  • debug: launch configurations
  • -
  • ?: list all prefixes and their functions
  • -
-

Work with containerized applications

-
-

New in v1.1.0

-
-

We have containerized the following applications for a variety of reasons:

- -

Running containerized applications

-

In the first section of dockerComposeFile of .devcontainer/devcontainer.json, there is a list of files: -each file contains the configuration for one or more applications.

-

The ones that are commented out are not run. To run them:

-
    -
  1. Uncomment the Docker Compose file(s) that the application(s) you desire to run are defined in
      -
    • Programs that are defined in the uncommented Docker Compose files will be started and stopped with Sailbot Workspace
    • -
    -
  2. -
  3. Run the Dev Containers: Rebuild Container VS Code command to restart Sailbot Workspace
  4. -
-

To stop running them:

-
    -
  1. Comment out the corresponding Docker Compose file
  2. -
  3. Stop the application's container: see Managing containerized applications
  4. -
-

Viewing MongoDB data

-

Connect the MongoDB VS Code extension to the running database: -Create a Connection for Deployment

-
    -
  • Use the default methods: "Paste Connection String" and "Open from Overview Page"
  • -
  • Our database's connection string is mongodb://localhost:27017
  • -
  • See the MongoDB VS Code extension docs for how - to use it to navigate or explore the database
  • -
-

Opening Docs or Website

-

Docs runs on port 8000 and Website 3005. You can see them in your browser at localhost:<port>. To open them using VS Code:

-
    -
  1. Run the Ports: Focus on Ports View VS Code command
  2. -
  3. Open the site by hovering over its local address and clicking either "Open in Browser" or "Preview in Editor"
      -
    • The local address of Docs is the line with a port of 8000
    • -
    • The local address of Website is the line with a port of 3005
    • -
    -
  4. -
-
-

Turn off auto saving

-

Changes made to their files are loaded when they are saved, so if Auto Save is on, turn it off -so that the Docs/Website servers aren't continuously reloading. Auto Save is on by default in GitHub Codespaces

-
-

Managing containerized applications

-

Each application runs in a Docker container. Containers can be managed using Docker Desktop or CLI commands:

-
    -
  • -

    View Sailbot Workspace containers

    -
    -
    -
    -
      -
    1. Select "Containers" in the top right
    2. -
    3. Expand "sailbot_workspace_devcontainer"
        -
      • The "Status" column shows whether a container is running or not
      • -
      -
    4. -
    -
    -
    -
    docker ps -a
    -
    -
      -
    • Sailbot Workspace containers should be named something like sailbot_workspace_devcontainer-<application>-<number>
    • -
    • The STATUS column shows whether a container is running or not
    • -
    -
    -
    -
    -
  • -
  • -

    View a container's logs, the output of the container (including errors that caused it to stop)

    -
    -
    -
    -
      -
    1. Click on a container
    2. -
    3. Navigate to the "Logs" view if not already on it
    4. -
    -
    -
    -
    docker logs <container>
    -
    -
    -
    -
    -
  • -
  • -

    Start a container that is not running

    -
    -
    -
    -
      -
    1. Click start
    2. -
    -
    -
    -
    docker start <container>
    -
    -
    -
    -
    -
  • -
  • -

    Stop a container that is running

    -
    -
    -
    -
      -
    1. Click stop
    2. -
    -
    -
    -
    docker stop <container>
    -
    -
    -
    -
    -
  • -
-

Manage software packages

-
-

Why can't I just install the dependencies myself in the command line interface with pip or apt?

-

Although this will temporarily work, installing apt and/or Python dependencies directly in sailbot workspace using -the commandline interface will not persist between container instances. The dependencies will need to be manually -installed every single time you create a new instance of sailbot workspace, which is not feasible when we start to -use many dependencies at once.

-

Of course, one could also install dependencies inside the sailbot workspace Docker images to allow such dependencies -to persist across container instances. However, putting dependencies inside package.xml distinguishes between -what dependencies are needed for ROS packages and what dependencies are needed for infrastructure purposes.

-
-

Add apt or python dependencies to ROS packages

-

If running your ROS packages requires external dependencies from an apt repository or python package, one of the following -tags should be added to the package.xml file in the root directory of the ROS package:

-
<depend>ROSDEP_KEY</depend>
-<build_depend>ROSDEP_KEY</build_depend>
-<build_export_depend>ROSDEP_KEY</build_export_depend>
-<exec_depend>ROSDEP_KEY</exec_depend>
-<test_depend>ROSDEP_KEY</test_depend>
-
-
    -
  • -

    Learn what each tag is used for here.

    -
  • -
  • -

    Replace ROSDEP_KEY with the rosdep key for the dependency, which can be found online.

    -
      -
    • Use the key associated with ubuntu since sailbot workspace uses Ubuntu, or debian which Ubuntu is based on
    • -
    • Do not include the square brackets in package.xml
    • -
    -
    -
    -
    -
      -
    • Rosdep keys for apt repositories can be found here
    • -
    -
    -
    -
      -
    • Rosdep keys for python packages can be found here
    • -
    • Since we use Python 3, look for the packages that start with python3- (python- is usually for Python 2)
    • -
    -
    -
    -
    -
  • -
  • -

    If there isn't rosdep key for the dependency, you can add your own to custom-rosdep.yaml - in the root directory of the ROS package

    -
  • -
-

After completing these steps, run the setup task and the -desired dependencies should be installed. ROS uses a dependency management utility, rosdep, to handle the installation -of dependencies. In addition to runtime dependencies, rosdep also handles dependencies for build time, dependencies for -testing, sharing dependencies between ROS packages, and more. -See the ROS documentation on rosdep -to learn more.

-

Add dependencies to a Docker image

-

There are a couple cases where you would want to add dependencies to a Docker image instead of ROS package:

-
    -
  1. The dependency is not used to build/run/test a ROS package
  2. -
  3. There is no apt or pip package for your dependency so you have to build from source
  4. -
-

To verify your changes, you can add them to .devcontainer/Dockerfile then -run the Dev Containers: Rebuild Container VS Code command. Once verified, migrate the changes to one of the upstream -images: base, local-base, dev, or pre-base.

-

Enable GitHub Copilot in Sailbot Workspace

-

GitHub Copilot is an AI paired programming tool that can help you accelerate your development by providing suggestions -for whole lines or entire functions inside your editor.1 To enable GitHub Copilot:

-
    -
  1. -

    Apply to GitHub Global Campus as a student -to use GitHub Copilot and get other student benefits for free. It may take a few days for your student status to be -verified. In the meantime, you can still continue with the next steps. However, you will need to use the GitHub Copilot -free trial until your account is verified.

    -
  2. -
  3. -

    Sign up for GitHub Copilot for your personal account. -If it offers a free trial, then take it. You should see a page telling you that you can use GitHub Copilot for free -(if you have a verified student account).

    -
  4. -
  5. -

    Uncomment the github.copilot extension in .devcontainer/devcontainer.json and run the - Dev Containers: Rebuild Container VS Code command

    -
  6. -
  7. -

    Sign into your GitHub account in VS Code. The GitHub Copilot extension should automatically prompt you to sign into -your account if you are not already.

    -
    -VS Code is not prompting me to sign into my account -

    You may already be signed in into your GitHub account. You can check by clicking on the -Accounts icon in the bottom-left corner in VS Code and verify that you see your GitHub account.

    -

    If you do not see your account, you can get the sign in prompt by trying:

    -
      -
    • Reloading the VS Code window: Ctrl+Shift+P and select Developer: Reload Window
    • -
    • Rebuilding the devcontainer: Ctrl+Shift+P and select Dev Containers: Rebuild Container
    • -
    • If using a Mac, use Cmd instead of Ctrl
    • -
    -
    -
  8. -
  9. -

    If all the previous steps were done correctly, you should see the GitHub Copilot icon in -the bottom-right corner of VS Code without any error messages. For more information on how to use Copilot and a tutorial, -refer to:

    - -
  10. -
-

Use your dotfiles

-

Dotfiles are configuration files for various programs.2

-
-More about dotfiles -
    -
  • They are called dotfiles because their filenames start with a dot (.)
  • -
  • On Linux and MacOS, files and directories that begin with a dot are hidden by default
  • -
  • To list dotfiles using the ls command, specify the -a argument: ls -a
  • -
-
-

Dotfiles that are commonly modified include:

-
    -
  • Bash: ~/.bashrc
  • -
  • Git: ~/.gitconfig
  • -
  • Vim: ~/.vimrc
  • -
-

To use your dotfiles:

-
    -
  1. Ensure that the base, local-base, or dev image - installs the programs that the dotfiles correspond to
  2. -
  3. -

    Copy the dotfiles to the .devcontainer/config/ directory. - If a dotfile is located in a child directory, you will have to created it. - For example, if a dotfile's path is ~/.config/ex_dotfile, you will need to copy it to .devcontainer/config/.config/ex_dotfile

    -
    -

    Special cases

    -
      -
    • ~/.gitconfig: there is no need copy your Git dotfile, as Dev Containers do this automatically
    • -
    • ~/.bashrc: don't copy your Bash dotfile, as it would override the one created in the dev image. -Instead, add your bash configuration .aliases.bash or .functions.bash in the config directory, as these are sourced -by the created Bash dotfile.
    • -
    -
    -
  4. -
  5. -

    Run the Dev Containers: Rebuild Container VS Code command

    -
  6. -
-

Run Raye's software

-

Raye was our previous project. Her software can be run in the raye branch:

-
    -
  1. Switch to the raye branch: git switch raye
  2. -
  3. Rebuild the Dev Container: run the Dev Containers: Rebuild Container VS Code command
  4. -
  5. If you want to run Raye's local pathfinding visualizer, - complete step 2 of the setup instructions
  6. -
-
-

raye branch disclaimers

-
    -
  1. Since raye (and Raye's codebase in general) is not in active development, it may not be 100% functional - or contain all the features in main
  2. -
  3. raye is more memory intensive than main because the parent image of its Dev Container is much larger; - this may lead to worse performance
  4. -
-
-

Build Raye's ROS packages

-

To build Raye's ROS packages, run the following commands:

-
roscd
-catkin_make
-
-

Run packages from different workspaces

-

The raye branch has two ROS workspaces: one for Raye and one for the new project. -To run ROS packages, you will have to source the overlay of the workspace that it is in:

-
-
-
-
srcnew
-
-
-
-
srcraye
-
-
-
-
-

Then you can run launch files or package-specific executables in that workspace with:

-
-
-
-

ros2 launch ... or ros2 run ..., respectively.

-
-
-

roslaunch ... or rosrun ..., respectively.

-
-
-
-

Raye's known issues

-
-

Run commands for Raye packages are very slow

-

On non-Ubuntu-based Linux operating systems, Run commands for Raye packages may take a long time to start-up. -This is because the system has trouble resolving the local hostname.

-

To resolve this bug, run the commands below in the Dev Container:

-
echo 'export ROS_HOSTNAME=localhost' >> ~/.bashrc
-echo 'export ROS_MASTER_URI=http://localhost:11311' >> ~/.bashrc
-
-
- - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/current/sailbot_workspace/launch_files/index.html b/pr-248/current/sailbot_workspace/launch_files/index.html deleted file mode 100644 index b12eb4fb6..000000000 --- a/pr-248/current/sailbot_workspace/launch_files/index.html +++ /dev/null @@ -1,2522 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Launch Files - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - -

ROS Launch Files in Sailbot Workspace

-

ROS 2 Launch files allow us to programatically start up and configure multiple ROS nodes.1 -Within Sailbot Workspace, ROS launch files are used to start up our ROS packages with ease. -Additionally, we take advantage of the hierarchical properties of launch files by defining a global -entry point that invokes the launch files of all ROS packages in the system.

-

Launch File Architecture

-

There are two launch processes that we utilize: namely the Package Launch Process and the Global launch process.

-

The Package Launch Process

- - - - -

The package launch process is intended to start up a specific ROS package by directly using the package launch file. -The process is as follows:

-
    -
  1. The package launch file is invoked with the user passing arguments via the CLI and specifying a configuration file.
  2. -
  3. Global argument declarations and environment variables are loaded into the launch process.
  4. -
  5. Local arguments, specific to the package, are declared.
  6. -
  7. Both global and local arguments are parsed based on the argument declarations and are set for use upon start up.
  8. -
  9. The ROS nodes belonging to the package begin execution, utilizing the ROS parameters from the configuration file.
  10. -
-
-When launching individual packages, be aware of dependencies between ROS packages -

Some packages rely on the data produced by other packages in the system. This may cause only -partial functionality of the ROS node(s) that are running inside the launched package. Therefore, -it may be necessary to launch multiple packages manually to get the desired functionality.

-
-

The Global Launch Process

- - - - -

The global launch process is intended to start up the entire system (both the development and production environments). -This process invokes the package launch files for each ROS package used in the system through a global launch file. -The process is as follows:

-
    -
  1. The global launch file is invoked with the user passing arguments via the CLI and specifying a configuration file.
  2. -
  3. Environment variables common to all ROS packages are declared. In addition, the global arguments common across -all ROS packages are declared.
  4. -
  5. For each package launch file:
      -
    • The CLI arguments, global argument declarations, and environment variables are passed into the package launch - file.
    • -
    • Local arguments, specific to the package, are declared. Both the global and local arguments are parsed based on the - argument declarations and are set for use upon start up.
    • -
    • The ROS nodes belonging to the package begin execution, utilizing the ROS parameters from the configuration file.
    • -
    -
  6. -
-

Invoking Launch Files

-
-Stopping the execution of a launch file -

Entering Ctrl+C in the terminal where the launch file was invoked will stop all associated -ROS packages from running.

-
-

Use Cmd+C for Mac OS

-
-
-

Package Launch

-

At the bare minimum, the following packages need to be built with the Build or Build All VS Code task before launching:

-
    -
  • custom_interfaces
  • -
  • The package you want to launch
  • -
-

Packages only need to be rebuilt either when the workspace is first set up, or if any changes are made to the ROS -package. Once built, the package launch file can be invoked either in the CLI or using a VS Code command:

-
-
-
-

Either the package and launch file name, or the path to the launch file can be used:

-
    -
  • Method 1: ros2 launch <package> <launch file>. This method can only be used when a launch file - is part of a built ROS package.
  • -
  • Method 2: ros2 launch <path to launch file>. This method can be used regardless if a launch file - is in a ROS package or not.
  • -
-
-

Launch via CLI Examples

-

Let's launch local pathfinding using both CLI methods:

-

Method 1 -

ros2 launch local_pathfinding main_launch.py
-

-

Method 2 -

ros2 launch $ROS_WORKSPACE/src/local_pathfinding/launch/main_launch.py
-

-
-
-
-

Run the following VS Code command from the Run and Debug tab: ROS: Launch (workspace)

-

There will be a prompt to select which launch file to run. Select the desired launch file.

-
-
-
-

Global Launch

-

Before running the system, be sure to run the Build All VS Code task to build all ROS packages. If the ROS launch -debug configuration is being used, then this step is not necessary as the Build All task is ran automatically before -launch.

-
-
-
-

Run the entire system with the following CLI command:

-
ros2 launch $ROS_WORKSPACE/src/global_launch/main_launch.py
-
-
-
-

Run the following VS Code command from the Run and Debug tab: ROS: Launch (workspace)

-

There will be a prompt to select which launch file to run. Select the desired launch file.

-
-
-
-

Remember to that you need to potentially reload the window if the nodes are not being detected -by VS Code. This usually happens when somebody build for the first time. Also, note that the global -launch file is not part of a ROS package, so the path to the global launch file always -must be provided. This is not always the case when a launch file is contained within a ROS package.

-

Using CLI Arguments

-

Invoking the launch files as is will provide the system with the default CLI arguments. As an example, -the following command will launch local pathfinding while setting the log level to "debug":

-
ros2 launch local_pathfinding main_launch.py log_level:=debug
-
-

It can also be ran with the VS Code command named ROS: Launch.

-

Passing arguments takes the form of <arg name>:=<arg value>. To list the arguments that a launch file takes, -simply add the -s flag at the end of the launch command.

-
-Example using the -s flag in a launch command -

Let's add the -s flag after the global launch command to see the list of arguments:

-
ros2 launch $ROS_WORKSPACE/src/global_launch/main_launch.py -s
-
-

The following output is observed in the terminal (as of September 2023):

-
Arguments (pass arguments as '<name>:=<value>'):
-
-'config':
-    Path to ROS parameter config file. Controls ROS parameters passed into ROS nodes
-    (default: '/workspaces/sailbot_workspace/src/global_launch/config/globals.yaml')
-
-'log_level':
-    Logging severity level. A logger will only process log messages with severity levels at or higher than the
-    specified severity. Valid choices are: ['debug', 'info', 'warn', 'error', 'fatal']
-    (default: 'info')
-
-'mode':
-    System mode. Decides whether the system is ran with development or production interfaces. Valid choices are:
-    ['production', 'development']
-    (default: 'development')
-
-
-
-Example using multiple CLI arguments -
ros2 launch local_pathfinding main_launch.py log_level:=debug mode:=production
-
-
-
-Example passing local launch arguments to the global launch file -

As long as an argument is valid inside one of the package launch files, it may be passed to the global launch -file without generating any errors. This is valid even though the argument doesn't show up in the argument list for -the global launch file. For example, the following will run:

-
ros2 launch $ROS_WORKSPACE/src/global_launch/main_launch.py enable_sim_multithreading:=true
-
-

Compare the argument list between the global launch file and the package launch file for the boat_simulator -package. It will be observed that the argument enable_sim_multithreading shows up in the boat_simulator -package argument list, but not for the global launch file.

-
-

ROS Parameter Config File

-

All launch files in Sailbot Workspace accept a configuration file, which controls the ROS parameters that the ROS nodes -in the system have access to. This makes our system highly configurable and customizable during development and testing. -See more about ROS parameters.

- - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/current/sailbot_workspace/overview/index.html b/pr-248/current/sailbot_workspace/overview/index.html deleted file mode 100644 index 0eb0d15b9..000000000 --- a/pr-248/current/sailbot_workspace/overview/index.html +++ /dev/null @@ -1,2439 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Overview - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -
-

Source code

-

The source code for Sailbot Workspace can be found in the -sailbot_workspace GitHub repository. -Its README has been copied below.

-
-

Sailbot Workspace

-

Build Images -Tests

-

This repository will get you set up to develop UBCSailbot's software on VS Code. It is based on athackst's -vscode_ros2_workspace.

-

Features

-

An overview of Sailbot Workspace's features can be found below. -See our docs site for how to use these features.

-

Style

-

C++ and Python linters and formatters are integrated into Sailbot Workspace:

-
    -
  • ament_flake8
  • -
  • ament_lint_cmake
  • -
  • ament_xmllint
  • -
  • black
  • -
  • clang-tidy
  • -
  • isort
  • -
-

The ament linters are configured to be consistent with the -ROS style guide.

-

Dev Container

-

Dev Containers enable us to use a -Docker container as a fully-featured development environment -containing all our configuration and dependencies. -Our Dev Container configuration can be found in .devcontainer/.

-

Multi-Root Workspace

-

Workspaces are VS Code instances that contain one or more folders. -Our workspace configuration file can be found at -sailbot.code-workspace.

-

Our software spans many repositories: software team repositories. -Multi-root workspaces -make it easy to work with multiple repositories at the same time. -Our roots are defined in the folders section of our workspace file.

-

Debugging

-

Launch configurations -have been created to debug our software. They are defined in the launch section of -our workspace file.

-

Tasks

-

Tasks provide an alternative to memorizing the multitude of -CLI commands we use to setup, build, lint, test, and run our software. They are defined in tasks section of -our workspace file.

-

Continuous Integration

-

Actions -were used to build our Docker containers -and lint and test our code the same way it is done locally in Sailbot Workspace on GitHub. -We use a reusable workflow -to create a single source of truth for our tests across all our repositories. -Our CI can be found in .github/workflows/.

-

Customization

-

This repository supports user-specific configuration files. To set this up, see -How to use your dotfiles.

-

Run Raye's Software

-

Raye was our previous project. -Her software can be run in the raye branch -following the instructions in How to run Raye's software. -The initial differences between the main and raye branches are summarized in -this PR.

-

Documentation

-

Further documentation, including setup and run instructions, can be found on our Docs website.

- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/current/sailbot_workspace/parameters/index.html b/pr-248/current/sailbot_workspace/parameters/index.html deleted file mode 100644 index 170cbc3aa..000000000 --- a/pr-248/current/sailbot_workspace/parameters/index.html +++ /dev/null @@ -1,2626 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Parameters - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
- -
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -
-

Source code

-

The README for ROS parameterization -can be found in the sailbot_workspace -GitHub repository. Its README has been copied below.

-
-

Sailbot ROS Parameter Configuration

-

The description of each parameter contained in globals.yaml are described in this README. Descriptions of parameters -for each node are included. These parameters can be changed dynamically as well via the command line interface. To -learn more, see the ROS 2 documentation on ROS 2 Parameters.

-

Each parameter is specified in the following format:

-
    -
  • Description: The description of the parameter.
  • -
  • Datatype: The datatype. If it happens to be an array, the datatype of the elements should be specified and the length -of the array.
  • -
  • Range/Acceptable Values: Ranges of integers and floating point values are specified with interval notation. -Namely, [] denotes inclusive boundaries, while () denotes non-inclusive boundaries. For strings, the acceptable -values are listed.
  • -
-

Additional information may be included when necessary.

-
-

[!IMPORTANT] -This document should be updated when any changes occur to the ROS parameters specified in globals.yaml.

-
-

Global Parameters

-

ROS parameters common across all ROS nodes in the network.

-

pub_period_sec

-
    -
  • Description: The period at which the publishers publish.
  • -
  • Datatype: double
  • -
  • Range: (0.0, MAX_DOUBLE)
  • -
-

Local Pathfinding Parameters

-

ROS parameters specific to the nodes in the local_pathfinding package.

-

mgp_main

-

global_path_filepath

-
    -
  • Description: The absolute filepath to a global path csv file.
  • -
  • Datatype: string
  • -
  • Acceptable Values: Any valid filepath to a properly formatted csv file.
  • -
-

interval_spacing

-
    -
  • Description: The upper bound on spacing between each point in the global path in km.
  • -
  • Datatype: double
  • -
  • Range: (0.0, MAX_DOUBLE)
  • -
-

write

-
    -
  • Description: Whether or not to write a generated global path to a new csv file.
  • -
  • Datatype: boolean
  • -
  • Acceptable Values: true, false
  • -
-

gps_threshold

-
    -
  • Description: A new path will be generated if the GPS position changed by more thangps_threshold*interval_spacing.
  • -
  • Datatype: double
  • -
  • Acceptable Values: (1.0, MAX_DOUBLE)
  • -
-

force

-
    -
  • Description: Force the mock global path callback to update the global path when set to true.
  • -
  • Datatype: boolean
  • -
  • Acceptable Values: true, false
  • -
- -

path_planner

-
    -
  • Description: The path planner to use. Planners are from OMPL Library.
  • -
  • Datatype: string
  • -
  • Acceptable Values: "bitstar", "bfmtstar", "fmtstar", "informedrrtstar", "lazylbtrrt", "lazyprmstar", - "lbtrrt", "prmstar", "rrtconnect", "rrtsharp", "rrtstar", "rrtxstatic", "sorrtstar"
  • -
-

Boat Simulator Parameters

-

ROS parameters specific to the nodes in the boat simulator.

-

low_level_control_node

-

info_log_throttle_period_sec

-
    -
  • Description: Limits the info logs to avoid overwhelming the terminal.
  • -
  • Datatype: double
  • -
  • Range: (0.0, MAX_DOUBLE)
  • -
-

logging_throttle_period_sec

-
    -
  • Description: Controls the message logging throttle period.
  • -
  • Datatype: double
  • -
  • Range: (0.0, MAX_DOUBLE)
  • -
-

qos_depth

-
    -
  • Description: The maximum number of subscription messages to queue for further processing.
  • -
  • Datatype: int
  • -
  • Range: [1, MAX_INT)
  • -
-

rudder.actuation_execution_period_sec

-
    -
  • Description: The period at which the main loop in the rudder action server executes in seconds.
  • -
  • Datatype: double
  • -
  • Range: (0.0, MAX_DOUBLE)
  • -
-

rudder.disable_actuation

-
    -
  • Description: Controls whether or not rudder actuation is disabled. If true, the rudder angle is fixed to some value. -Otherwise, the PID mechanism is used to control the rudder angle.
  • -
  • Datatype: boolean
  • -
  • Acceptable Values: true, false
  • -
-

rudder.fixed_angle_deg

-
    -
  • Description: The angle to fix the rudder in degrees. Only used if rudder.disable_actuation is true.
  • -
  • Datatype: double
  • -
  • Range: [-45.0, 45.0]
  • -
-

rudder.pid.buffer_size

-
    -
  • Description: The buffer size of PID that stores previously computed errors over time.
  • -
  • Datatype: int
  • -
  • Range: [1, MAX_INT)
  • -
-

rudder.pid.kd

-
    -
  • Description: The PID Derivative constant for the rudder. Only used if rudder.disable_actuation is false.
  • -
  • Datatype: double
  • -
  • Range: [0.0, MAX_DOUBLE)
  • -
-

rudder.pid.ki

-
    -
  • Description: The PID Integral constant for the rudder. Only used if rudder.disable_actuation is false.
  • -
  • Datatype: double
  • -
  • Range: [0.0, MAX_DOUBLE)
  • -
-

rudder.pid.kp

-
    -
  • Description: The PID Proportionality constant for the rudder. Only used if rudder.disable_actuation is false.
  • -
  • Datatype: double
  • -
  • Range: [0.0, MAX_DOUBLE)
  • -
-

wingsail.actuation_execution_period_sec

-
    -
  • Description: The period at which the main loop in the sail action server executes in seconds.
  • -
  • Datatype: double
  • -
  • Range: (0.0, MAX_DOUBLE)
  • -
-

wingsail.actuation_speed_deg_per_sec

-
    -
  • Description: The speed at which the wingsail trim tab actuates in degrees per second.
  • -
  • Datatype: double
  • -
  • Range: (0.0, MAX_DOUBLE)
  • -
-

wingsail.disable_actuation

-
    -
  • Description: Controls whether or not wingsail trim tab actuation is disabled. If true, the trim tab is fixed to some -value. Otherwise, the trim tab angle is determined by the wingsail controller.
  • -
  • Datatype: boolean
  • -
  • Acceptable Values: true, false
  • -
-

wingsail.fixed_angle_degree

-
    -
  • Description: Fixed the wingsail trim tab to some angle in degrees. Only used if wingsail.disable_actuation is true.
  • -
  • Datatype: double
  • -
  • Range: [-180.0, 180.0)
  • -
-

physics_engine_node

-

action_send_goal_timeout_sec

-
    -
  • Description: How long the action clients wait for the action server to respond to a request before timing out in seconds.
  • -
  • Datatype: double
  • -
  • Range: (0.0, MAX_DOUBLE)
  • -
-

info_log_throttle_period_sec

-
    -
  • Description: Limits the info logs to avoid overwhelming the terminal.
  • -
  • Datatype: double
  • -
  • Range: (0.0, MAX_DOUBLE)
  • -
-

logging_throttle_period_sec

-
    -
  • Description: Controls the message logging throttle period.
  • -
  • Datatype: double
  • -
  • Range: (0.0, MAX_DOUBLE)
  • -
-

qos_depth

-
    -
  • Description: The maximum number of subscription messages to queue for further processing.
  • -
  • Datatype: int
  • -
  • Range: [1, MAX_INT)
  • -
-

rudder.actuation_request_period_sec

-
    -
  • Description: How often the rudder action client requests a rudder actuation in seconds.
  • -
  • Datatype: double
  • -
  • Range: (0.0, MAX_DOUBLE)
  • -
-

wind_sensor.constant_params.value

-
    -
  • Description: Specifies the constant vector returned by the constant generator that represents the wind velocity in kmph. -Namely, the same value is fixed in the wind sensors. The value is an array containing the x and y components of the -velocity. Only used if wind_sensor.generator_type is constant.
  • -
  • Datatype: double array, length 2
  • -
  • Range: (MIN_DOUBLE, MAX_DOUBLE)
  • -
-

wind_sensor.gaussian_params.corr_xy

-
    -
  • Description: The correlation coefficient between x and y components of the wind velocity. Only used if -wind_sensor.generator_type is gaussian.
  • -
  • Datatype: double
  • -
  • Range: [-1.0, 1.0]
  • -
-

wind_sensor.gaussian_params.mean

-
    -
  • Description: The mean wind velocity parameter in kmph for the gaussian generator. The mean is an array containing -the x and y components of the velocity. Only used if wind_sensor.generator_type is gaussian.
  • -
  • Datatype: double array, length 2
  • -
  • Range: (MIN_DOUBLE, MAX_DOUBLE)
  • -
-

wind_sensor.gaussian_params.std_dev

-
    -
  • Description: The standard deviation parameters in kmph for the gaussian generator. There are two standard deviations -specified within an array: one for the x component, and one for the y component. Only used if -wind_sensor.generator_type is gaussian.
  • -
  • Datatype: double array, length 2
  • -
  • Range: (0.0, MAX_DOUBLE)
      -
    • If a standard deviation of zero is desired, then consider using the constant generator instead.
    • -
    -
  • -
-

wind_sensor.generator_type

-
    -
  • Description: Determines the type of random number generator that will be used to generate wind sensor data.
  • -
  • Datatype: string
  • -
  • Acceptable Values: gaussian, constant
  • -
-

wingsail.actuation_request_period_sec

-
    -
  • Description: How often the sail action server requests a wingsail actuation.
  • -
  • Datatype: double
  • -
  • Range: (0.0, MAX_DOUBLE)
  • -
-

data_collection_node

-

file_name

-
    -
  • Description: The name of the file in which the data is saved, excluding the file extension.
  • -
  • Datatype: string
  • -
  • Acceptable Values: Any valid file name.
  • -
-

qos_depth

-
    -
  • Description: The maximum number of subscription messages to queue for further processing.
  • -
  • Datatype: int
  • -
  • Range: [1, MAX_INT)
  • -
-

topics

-
    -
  • Description: Specifies the topics to subscribe to. It should adhere to the format ['topic_name_1', 'topic_type_1', ...].
  • -
  • Datatype: string array with an even length
  • -
  • Acceptable Values: Each pair within the array must consist of a valid topic name as the first string and the -corresponding correct type as the second string.
  • -
-

bag

-
    -
  • Description: Determines whether to save recorded data as a ROS bag.
  • -
  • Datatype: boolean
  • -
  • Acceptable Values: true, false
  • -
-

json

-
    -
  • Description: Determines whether to save recorded data as a JSON file.
  • -
  • Datatype: boolean
  • -
  • Acceptable Values: true, false
  • -
-

write_period_sec

-
    -
  • Description: The interval (in seconds) for writing queued data to the JSON file.
  • -
  • Datatype: double
  • -
  • Range: (0.0, MAX_DOUBLE)
  • -
- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/current/sailbot_workspace/setup/index.html b/pr-248/current/sailbot_workspace/setup/index.html deleted file mode 100644 index 47a640766..000000000 --- a/pr-248/current/sailbot_workspace/setup/index.html +++ /dev/null @@ -1,2809 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Setup - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - -

Setup Instructions

-

Sailbot Workspace can be run on Windows, Linux, or macOS, but is the easiest to set up and performs the best on -Ubuntu and its derivatives. -The workspace may not perform well on Windows computers with 8GB of memory or less; -in this case, please check out our recommendations in the Performance Issues -section.

-

1. Setup prerequisites

-

Docker

-

Docker is a platform that uses OS-level virtualization1 to develop, ship, and -run applications.2 We use it to separate our applications from our infrastructure2 so that we can update and -version control our infrastructure for every use case (software members, CI, deployment) in one place: this repository.

-

Docker Engine is a software used to run Docker. -However, it can only be installed on Linux. -Docker Desktop is a software used to run Docker in a VM,3 -allowing it to be installed on Windows and macOS in addition to Linux.

-
-
-
-
    -
  1. -

    Set up prerequisites, WSL and Ubuntu:

    -
      -
    1. -

      In PowerShell, run wsl --install Ubuntu, then exit, wsl --update, and wsl --set-default Ubuntu

      -
      -Ubuntu is already installed? -

      If Ubuntu is already installed, check that it is the right WSL version:

      -
        -
      1. Check the WSL versions of Linux distributions with wsl -l -v
      2. -
      3. If Ubuntu's VERSION is 1, upgrade it to WSL 2 with wsl --set-version Ubuntu 2
      4. -
      -
      -
    2. -
    3. -

      Open the Ubuntu app to set up or verify its configuration:

      -
        -
      1. If you are opening Ubuntu for the first time, a setup process will run; -follow the prompts to finish setting it up
      2. -
      3. -

        Run whoami to verify that it returns your Ubuntu username

        -
        -whoami returns root -

        If whoami returns root:

        -
          -
        1. Create a non-root user with sudo privileges
        2. -
        3. Change the default Ubuntu user to this newly-created user: run ubuntu config --default-user <username> -in PowerShell, replacing <username> with the name of the newly-created user
        4. -
        5. Run whoami after closing and reopening Ubuntu, verifying that it returns your Ubuntu username
        6. -
        -
        -
      4. -
      -
    4. -
    -
  2. -
  3. -

    Install Docker Desktop -with the WSL 2 backend

    -
    -Docker Desktop - Unexpected WSL Error -

    image

    -

    If the above error shows when trying to start Docker Desktop on your laptop:

    -
      -
    1. For windows users navigate to C:\Users\user_name and delete the .Docker folder
    2. -
    3. Restart Docker Desktop
    4. -
    -
    -
    -Docker Desktop can't start up and WSL hangs when restarting -

    If Ubuntu can't start up and WSL hangs when restarting:

    -
      -
    1. Open command prompt as administrator and run the command netsh winsock reset
    2. -
    3. Uninstall and reinstall Docker Desktop
    4. -
    5. Restart your computer
    6. -
    -

    More potential solutions can be found here: -Link

    -
    -
  4. -
-
-
-

Install Docker Desktop for your computer's CPU.

-
-
-
    -
  1. Install Docker Engine
      -
    • As of February 2023, Sailbot Workspace (more specifically its use of VS Code Dev Containers) isn't compatible - with Docker Desktop for Linux; if you have Docker Desktop installed, uninstall it and install Docker Engine instead.
    • -
    -
  2. -
  3. Manage Docker as a non-root user
  4. -
  5. Configure Docker to start on boot
  6. -
-
-
-
-

VS Code

-

Visual Studio Code is a powerful and customizable code editor for -Windows, Linux, and macOS. We strongly recommend that you use this editor to develop our software so that you can -use all the features of Sailbot Workspace.

-
    -
  1. Install VS Code
  2. -
  3. Install the Remote Development Extension Pack
  4. -
-

Git

-

Git is a free and open source distributed version control system designed to handle everything from -small to very large projects with speed and efficiency.4

-
    -
  1. Check if Git is installed with git --version (on Windows, run command in PowerShell) -
  2. -
  3. Configure your name and email: Git config file setup - (on Windows, run commands in Ubuntu)
  4. -
  5. -

    Login to GitHub

    -
    -
    -
    -
      -
    1. -

      Run the git config command for your Git version in Git Credential Manager setup (run command in Ubuntu)

      -
      -

      Which Git to check

      -

      Git is installed seperately in Windows and Ubuntu, so they could be at different versions. -We want to check the version of Git on Windows, not Ubuntu: -run git --version in PowerShell and not Ubuntu. -However, the git config command itself is run in Ubuntu.

      -
      -
    2. -
    -
    -
    -
      -
    1. Install the GitHub CLI: Installation
    2. -
    3. Run gh auth login and select the first option for all choices
    4. -
    -
    -
    -
    -
  6. -
  7. -

    Verify that you have successfully logged in to GitHub by cloning a private GitHub repository (run command in Ubuntu)

    -
      -
    1. If you are a part of the UBCSailbot Software GitHub team, - you shouldn't see any errors running git clone https://github.com/UBCSailbot/raye-ais.git
    2. -
    3. You can delete this repository with rm -rf raye-ais
    4. -
    -
  8. -
-

2. Setup X11 forwarding

-

X11 forwarding is a mechanism that enables Sailbot Workspace to run GUI applications.

-
-

You can skip this step since we currently aren't running any GUI applications

-
-
-Setup instructions for X11 forwarding -
    -
  1. Ensure that the versions of VS Code and its Dev Containers extension support X11 forwarding:
      -
    1. VS Code version >= 1.75
    2. -
    3. Dev Containers version >= 0.275.1
    4. -
    -
  2. -
  3. -

    Verify that echo $DISPLAY returns something like :0

    -
    -echo $DISPLAY doesn't return anything -

    If echo $DISPLAY doesn't return anything, set it to :0 on shell initialization:

    -
      -
    1. Find out what shell you are using with echo $SHELL
        -
      1. Most Linux distributions use Bash by default, whose rc file path is ~/.bashrc
      2. -
      3. macOS uses Zsh by default, whose rc file path is: ~/.zshrc
      4. -
      -
    2. -
    3. Run echo 'export DISPLAY=:0' >> <rc file path>, replacing <rc file path> -with the path to your shell's rc file
    4. -
    5. Run echo $DISPLAY after closing and reopening your terminal, verifying it returns something like :0
    6. -
    -
    -
  4. -
  5. -

    Install a X11 server

    -
    -
    -
    -

    WSL includes a X11 server.

    -
    -
    -
      -
    1. Set up XQuartz following this guide
    2. -
    3. Copy the default xinitrc to your home directory: cp /opt/X11/etc/X11/xinit/xinitrc ~/.xinitrc
    4. -
    5. Add xhost +localhost to ~/.xinitrc after its first line
    6. -
    -
    -
    -
    -
    -
    -

    As of February 2023, almost all Linux distributions include a X11 server, Xorg. -This may change in the future as Wayland matures.

    -
    -
    -
      -
    1. Install xhost: sudo pacman -S xorg-xhost
    2. -
    3. Copy the default xinitrc to your home directory: cp /etc/X11/xinit/xinitrc ~/.xinitrc
    4. -
    5. Add xhost +local:docker to ~/.xinitrc after its first line
    6. -
    -
    -
    -
    -
    -
    -
    -
  6. -
  7. -

    Verify that X11 forwarding works:

    -
      -
    1. -

      Install x11-apps

      -
      -
      -
      -

      In Ubuntu, sudo apt install x11-apps.

      -
      -
      -

      XQuartz includes x11-apps. Ensure that XQuartz is running.

      -
      -
      -

      Install x11-apps using your desired package manager.

      -
      -
      -
      -
    2. -
    3. -

      Verify that running xcalc opens a calculator and that you can use it

      -
    4. -
    -
  8. -
-
-

3. Clone Sailbot Workspace

-
-

Where to clone on Windows

-

Run the command below in the Ubuntu app to clone it in the Ubuntu file system, otherwise sailbot workspace will not work. -Windows has a native file system as well as file systems for each WSL distribution.

-
-
git clone https://github.com/UBCSailbot/sailbot_workspace.git
-
-

4. Open Sailbot Workspace in VS Code

-
    -
  1. -

    Install code command in PATH

    -
    -
    -
    -

    The code command is installed by default.

    -
    - -
    -

    The code command is installed by default.

    -
    -
    -
    -
  2. -
  3. -

    Open the sailbot_workspace/ directory in VS Code: run code <relative path to sailbot workspace>

    -
      -
    • For example, if you just cloned the repository, the command would be code sailbot_workspace
    • -
    -
  4. -
-

5. Open the workspace file

-

Click the popup to Open Workspace. If there isn't a popup:

-
    -
  1. Open the file sailbot.code-workspace in VS Code
  2. -
  3. Click Open Workspace
  4. -
-

6. Open Sailbot Workspace in a Dev Container

-
    -
  1. Ensure that Docker is running
  2. -
  3. Click the popup to Reopen in Container. If there isn't a popup, - run the Dev Containers: Reopen in Container VS Code command
  4. -
-

7. Run the setup task

-

The setup task clones the repositories defined in src/polaris.repos and updates dependencies of the ROS packages. -If you don't know how to run a VS Code task, see How to run VS Code commands, tasks, and launch configurations.

-
-Can't see the setup task -

If you can't see the setup task, run the Developer: Reload Window VS Code command. -This may occur when the workspace file is opened for the first time.

-
-

8. Run the Build All task

-

The Build All task builds all the ROS packages.

-

9. Reload the VS Code terminals and window

-

Delete all open terminals and run the Developer: Reload Window VS Code command -to detect the files that were generated from building.

-

10. Start the system

-

Run the entire system to verify everything is working using the following command in the VS Code terminal:

-
ros2 launch $ROS_WORKSPACE/src/global_launch/main_launch.py
-
-

Use Ctrl+C in the terminal to stop the system.

-

Setup Sailbot Workspace in a GitHub Codespace

-

A codespace is a development environment that's hosted in the cloud.5 -Since Sailbot Workspace is resource intensive, it has high hardware requirements and power consumption, -which aren't ideal for development on laptops. GitHub Codespaces provide a seamless experience to work on repositories -off-device, especially if they specify a Dev Container like Sailbot Workspace. Codespaces can run in VS Code -or even in a browser for times when you aren't on your programming computer.

-
    -
  1. Create a GitHub Codespace following the steps in the relevant GitHub Docs page: -create a codespace for a repository. -A couple things to note: -
  2. -
  3. Follow the local setup instructions starting from 5. Open the workspace file
  4. -
-

Once you have a codespace set up:

-
    -
  • Open it by following the steps in the relevant GitHub Docs page: -reopening a codespace
  • -
  • Close it by running the Codespaces: Stop Current Codespace VS Code command
  • -
-
-

Known limitations of running Sailbot Workspace in a GitHub Codespace

-
    -
  • Does not support X11 forwarding to run GUI applications
  • -
  • High-spec machines not available: as of March 2023, the highest-spec machine that is publically available - has a 4-core CPU and 8GB of RAM
  • -
-
- - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/current/sailbot_workspace/workflow/index.html b/pr-248/current/sailbot_workspace/workflow/index.html deleted file mode 100644 index 87c17ec73..000000000 --- a/pr-248/current/sailbot_workspace/workflow/index.html +++ /dev/null @@ -1,2630 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Workflow - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - -

Development Workflow

-

1. Open Sailbot Workspace

-

Once you have set up Sailbot Workspace, you can open it by opening a new VS Code window and selecting:

-
File > Open Recent > /workspaces/sailbot_workspace/.devcontainer/config/sailbot_workspace (Workspace) [Dev Container: Sailbot Workspace]
-
-
-Another way to open Sailbot Workspace on Windows -
    -
  1. Pin VS Code to the taskbar
  2. -
  3. Right-click VS Code in the taskbar and pin sailbot_workspace (Workspace) [Dev Container]
  4. -
-

Then you can open Sailbot Workspace by selecting it from the "Pinned" section of the VS Code taskbar icon's -right-click menu.

-
-

2. Update Sailbot Workspace

-

Sailbot Workspace is still in active development, check out its recent releases -and commit history. -If there are new features or bug fixes that you want to try, you will need to update your local version of Sailbot Workspace:

-
    -
  1. -

    Switch Sailbot Workspace to the main branch if you aren't in it already

    -
    -If you running Git commands in the CLI, make sure that you are in the correct repository -

    Sailbot Workspace contains other repositories in the src/ directory, so if you are in one of its subdirectories -you may be in the wrong repository.

    -

    To check which repository you are in, run git remote -v; if its output contains sailbot_workspace, -you are good to go. -If not, you can navigate the root directory of the Sailbot Workspace repository with cd $ROS_WORKSPACE, -or open a new terminal in its root directory with Ctrl+Shift+` then Enter.

    -
    -
      -
    • If you are unable to switch branches because you have uncommitted changes, stash them
    • -
    -
  2. -
  3. -

    Pull the latest changes

    -
      -
    • If you stashed your uncommitted changes, pop them
    • -
    -
  4. -
  5. -

    If prompted, rebuild the Dev Container

    -
    -When does the Dev Container need to be rebuilt? -

    To apply the modifications to its configuration files in .devcontainer/ that occurred since it was last built.

    -

    VS Code will prompt you to rebuild when devcontainer.json, Dockerfile, or docker-compose*.yml. -These file may be modified if you:

    -
      -
    • Pull the lastest changes of a branch
    • -
    • Switch branches
    • -
    • Update a file in .devcontainer/ yourself
    • -
    -

    However, there may be changes to the Dev Container that VS Code can't detect. -To rebuild it yourself, run the Dev Containers: Rebuild Container VS Code command.

    -
    -
  6. -
  7. -

    If you aren't working in any other branches, - run the setup task to switch the branches of all sub-repositories to their default specified in src/polaris.repos - and pull their latest changes

    -
  8. -
  9. If you want to run our docs, website, or other optional programs, see How to run optional programs
  10. -
-

3. Make your changes

-

We make changes to our software following our GitHub development workflow. -Of particular relevance is the Developing on Branches page.

-
-

Git interfaces

-

One way to interface with Git is through CLI commands. However, you may find it faster to use -VS Code's interface, -especially when working with multiple repositories.

-
-

Things to note when making changes:

-
    -
  • When C++ or Python files are saved, you may notice that some lines change. We use formatters to help fix lint errors; - not all lint errors can be fixed by formatters, so you may have to resolve some manually
  • -
  • When changing a package's source files, you likely should update its test files accordingly
  • -
-

4. Build your changes

-
-

Revamped in v1.2.0

-
-

In general, changes need to be built before they can be run. You can skip this step if you only modified Python source -or test files (in python_package/python_package/ or python_package/test, respectively), or are running a launch type -launch configuration.

-
    -
  1. Depending on which packages you modified, run the Build All or Build Package task
      -
    1. Unless you want to run clang-tidy, use the -q build argument (default) for quicker build times
    2. -
    -
  2. -
-

5. Verify your changes

-
-

Revamped in v1.2.0

-
-
-Running GUI applications on macOS -

If you want to run GUI applications on macOS, ensure that XQuartz is running.

-
-

Lint and Test

-

Run lint and test tasks to make sure you changes will pass our CI:

-
    -
  • ament lint
  • -
  • For C++ packages, clang-tidy
  • -
  • test
  • -
-

In addition to VS Code tasks, the Testing tab on the VS Code primary sidebar -contains individual tests. One can run specific unit tests by clicking the Run Test -icon beside the test name.

-

VS Code Test Tab

-

Run a Package

-

To verify that your changes do what you expect, you may want to run the package you modified. The run commands for each -package should be documented in their READMEs, but in general they can be run using a CLI or VS Code command:

-
-
-
-
    -
  • Launch files:
      -
    • ros2 launch <package> <launch file>
    • -
    • ros2 launch <path to launch file>
    • -
    -
  • -
  • Nodes:
      -
    • ros2 run <package> <executable>
    • -
    -
  • -
-
-CLI features -

There are many commands that can be autocompleted in the terminal. -Take advantage of this so that you run commands faster and memorize less syntax. -If there is only one possibility, pressing tab once will complete it. -If there is more than one possibility, pressing tab again will list them out.

-

Some tab completion use cases:

-
    -
  • -

    View available commands: lists all ros2 commands

    -
    $ ros2 <tab><tab>
    -action                          extension_points                multicast                       security
    -bag                             extensions                      node                            service
    -...
    -
    -
  • -
  • -

    Complete commands: runs ros2 launch local_pathfinding main_launch.py

    -
    $ ros2<tab>la<tab>loc<tab>m<tab>
    -
    -
  • -
  • -

    Navigate to directories: runs cd .devcontainer/config from the root directory of Sailbot Workspace

    -
    $ cd .d<tab>c<tab>
    -
    -
  • -
-

Furthermore, navigate past commands with Up and Down and search through them with Ctrl+R.

-
-
-
-
    -
  • Launch files: ROS: Run a ROS launch file (roslaunch)
  • -
  • Nodes: ROS: Run a ROS executable (rosrun)
  • -
-
-
-
-

For more information on launch file use in our system, see this page.

-

Run the System

-

To verify that you didn't break anything, you may want to run the entire system. See -Invoking Launch Files for more information -on running the system.

-

Debugging

-

Debug your changes if they aren't behaving how you expect by setting breakpoints and running one of our launch -configurations in the Run and Debug tab on the VS Code primary sidebar. The launch configuration types are:

-
    -
  • Launch: runs the desired launch file or executable
      -
    • For launch files, ROS: Launch
    • -
    • For C++ executables, C++ (GDB): Launch
    • -
    -
  • -
  • Attach: attaches to a running executable
      -
    • ROS: Attach
    • -
    -
  • -
-

Troubleshooting

-

If you are having some trouble running our software, here are some things you can try:

-
    -
  • Build from scratch
      -
    1. Run the clean task to delete C++ generated files
    2. -
    3. Run the purge task to delete ROS generated files
    4. -
    5. Run the Build All task to rebuild
    6. -
    -
  • -
  • Rebuild the Dev Container: run the Dev Containers: Rebuild Container VS Code command
  • -
  • Reload VS Code: run the Developer: Reload Window VS Code command
  • -
  • -

    Delete Docker files

    -
    -Running Docker CLI commands on Windows -

    On Windows, Docker CLI commands should be run in the Ubuntu terminal while Docker Desktop is running.

    -
    -
      -
    • Run docker system prune to remove all unused containers, networks, and dangling and unreferenced images
        -
      • Add --all to additionally remove unused images (don't have a container associated with them)
      • -
      • Add --volumes to additionally remove volumes (makes Bash history and ROS logs persist across containers)
      • -
      -
    • -
    • Run docker rmi -f $(docker images -aq) to remove all images
    • -
    -
  • -
-

Performance Issues

-

If you are not satisfied with the performance of Sailbot Workspace, here are some things you can try:

-
    -
  • Free up memory: close programs that you aren't using
  • -
  • Free up disk space: permanently delete large programs and files that you don't need anymore
  • -
  • Run Sailbot Workspace in a GitHub Codespace
      -
    • In a codespace with 8GB of RAM, building all packages from scratch with the -q argument takes about a minute. -If your computer takes longer than, or you want to free up memory and disk space, you can -setup Sailbot Workspace in a GitHub Codespace
    • -
    -
  • -
  • If you are running Sailbot Workspace on Windows, dual boot Ubuntu and run Sailbot Workspace there
      -
    • Sailbot Workspace performs worse on Windows than bare metal Linux because it uses Docker, which is not natively supported.
    • -
    • Here is a guide to dual boot the operating systems we recommend: How to Dual Boot Ubuntu 22.04 LTS and Windows 11
        -
      • We recommend allocating at least 50 GB to Ubuntu to leave some wiggle room for Docker
      • -
      • The process is similar for other Ubuntu and Windows versions, - but feel free to search for a guide specific to the combination you want to dual boot
      • -
      • Since Sailbot Workspace uses Docker, it should be able to run on any Linux distribution, not just Ubuntu. - However, we may not be able to provide support if you encounter any difficulties with this
      • -
      -
    • -
    -
  • -
- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/current/website/overview/index.html b/pr-248/current/website/overview/index.html deleted file mode 100644 index c5da4c152..000000000 --- a/pr-248/current/website/overview/index.html +++ /dev/null @@ -1,2369 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Overview - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -
-

Source code

-

The source code for Website can be found in the -website GitHub repository. -Its README has been copied below.

-
-

Website

-

In the website development timeline, we are currently evaluating the folllowing software stack: -Next.js website (this repository) and the MongoDB database. -The easiest way to evaluate these potential solutions for our purposes is in sailbot_workspace.

-

Database

-

MongoDB is a general purpose, document-based, distributed database built for modern application -developers and for the cloud era. If you want to learn more about MongoDB, visit their docs site: MongoDB Documentation.

-

Setup

-

Environment variables

-

This project uses environment variables to manage configuration-specific information. Please look at the file -.env.local and ensure the variables are defined below:

-
    -
  • MONGODB_URI: Your MongoDB connection string. Use mongodb://localhost:27017/<DB_NAME> to establish a connection - with the local database.
  • -
  • NEXT_PUBLIC_SERVER_HOST: The host URL of the website.
  • -
  • NEXT_PUBLIC_SERVER_PORT: The port number of the website.
  • -
  • NEXT_PUBLIC_POLLING_TIME_MS: The time interval for polling the database in milliseconds.
  • -
-

Package installation

-

The following command installs all required dependencies listed in the package.json file:

-
npm install
-
-

Once the installation is complete, you should see a node_modules directory in the project's root. -This directory contains all installed packages.

-

When installing a new package to the website, please follow the steps below:

-
    -
  1. -

    Access the terminal of the website container on Docker.

    -
  2. -
  3. -

    Run the command npm install <package-name>. - Replace <package-name> with the actual name of the package you want to add.

    -
  4. -
  5. -

    Should you encounter errors related to resolving peer dependencies, please re-run the command with - the header --legacy-peer-deps. Do not to use --force unless you're well aware of the potential consequences.

    -
  6. -
  7. -

    Review the package.json file to ensure the new package and its version have been added to the dependencies section.

    -
  8. -
  9. Confirm that package-lock.json has also been updated. - This file holds specific version information to ensure consistent installations across different environments.
  10. -
  11. Once the installation process is finished, please make sure to commit the files package.json and package-lock.json. - These files are essential for version controlling the dependencies that have been added.
  12. -
-

Run

-

Using Sailbot Workspace, -the website should be up and running on http://localhost:3005.

-

Otherwise, you execute the following commands to run it in development mode:

-
npm run dev
-
-

Linters

-

Before merging in new changes to the repository, please execute the following commands in order:

-
npm run format
-
-

This command runs Prettier to automatically format the code according to -the rules defined in the configuration file .prettierrc.

-
npm run lint
-
-

This command runs ESLint to analyze the code for potential errors -and enforce coding style based on the rules defined in the configuration file .eslintrc.

- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/index.html b/pr-248/index.html deleted file mode 100644 index 44a44ca4b..000000000 --- a/pr-248/index.html +++ /dev/null @@ -1,2242 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - - - - - - - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - -

UBCSailbot Software Team Docs

-

Welcome to the UBC Sailbot software team docs 👋

-

Looking to get started with running the Sailbot codebase? Start by setting up the Sailbot Workspace:

-

Getting Started

-

What information is on this website?

-

Information on our current project is contained on this website. In particular, information on each of our major software -projects are provided in detail.

-

Current Project Overview

-

References to the software tools that we use are also provided on this website. This includes basic information on these -tools, how we use these tools on UBC Sailbot, and external links to helpful references and tutorials.

-

Software Team References

-

Who is this website for?

-

The docs site is primarily for the members on the UBC Sailbot software team. However, curious members of the public and/or -those who are interested in contributing to our open source software would also benefit from this site.

-

Prospective Members

-

Are you a member of the UBC community? Are you interested in what we do at UBC Sailbot? We are always looking for motivated -students to help us tackle the challenge of autonomous sailing. Learn more below!

-

Software Team Posting

-

Apply to join UBC Sailbot

- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/javascripts/mathjax.js b/pr-248/javascripts/mathjax.js deleted file mode 100644 index 080801efb..000000000 --- a/pr-248/javascripts/mathjax.js +++ /dev/null @@ -1,16 +0,0 @@ -window.MathJax = { - tex: { - inlineMath: [["\\(", "\\)"]], - displayMath: [["\\[", "\\]"]], - processEscapes: true, - processEnvironments: true - }, - options: { - ignoreHtmlClass: ".*|", - processHtmlClass: "arithmatex" - } -}; - -document$.subscribe(() => { - MathJax.typesetPromise() -}) diff --git a/pr-248/javascripts/table_of_contents_themes.js b/pr-248/javascripts/table_of_contents_themes.js deleted file mode 100644 index 037d8ba45..000000000 --- a/pr-248/javascripts/table_of_contents_themes.js +++ /dev/null @@ -1,55 +0,0 @@ -const LIGHT_MODE = 0; -const DARK_MODE = 1; -const LIGHT_MODE_TABLE_OF_CONTENTS_LINK_COLOR = "#000000"; // Black -const DARK_MODE_TABLE_OF_CONTENTS_LINK_COLOR = "#ffffff"; // White -const SAILBOT_BLUE = "#2f97ec"; - -// Sets the theme given a mode -function set_table_of_content_link_color(mode) { - let linkColor = ""; - switch (mode) { - case LIGHT_MODE: - linkColor = LIGHT_MODE_TABLE_OF_CONTENTS_LINK_COLOR; - break; - case DARK_MODE: - linkColor = DARK_MODE_TABLE_OF_CONTENTS_LINK_COLOR; - break; - default: - console.log("Error determining website theme. Defaulting table of content link color to sailbot blue."); - linkColor = SAILBOT_BLUE; - break; - } - document.documentElement.style.setProperty("--md-table-of-contents-link-color", linkColor); -} - -// Returns the new theme index given the current theme -function toggle_table_of_contents_link_color(current_mode) { - switch (current_mode) { - case LIGHT_MODE: - return DARK_MODE; - case DARK_MODE: - return LIGHT_MODE; - default: - return -1; - } -} - -// An enslosure that keeps track of the mode and toggles the theme accordingly -const theme_enclosure = function(initial_mode) { - let current_mode = initial_mode; - return { - setLinkColor: () => {set_table_of_content_link_color(current_mode);}, - toggleLinkColor: () => {current_mode = toggle_table_of_contents_link_color(current_mode); set_table_of_content_link_color(current_mode);} - }; -}; - -// Set the theme upon the window loading -var initial_mode = __md_get("__palette").index; -table_of_contents_theme = theme_enclosure(initial_mode); -table_of_contents_theme.setLinkColor(); - -// Set the theme when the light/dark mode button is clicked -const buttons = document.querySelectorAll("form.md-header__option > label.md-header__button"); -buttons.forEach((button) => { - button.addEventListener('click', table_of_contents_theme.toggleLinkColor); -}); diff --git a/pr-248/overrides/main.html b/pr-248/overrides/main.html deleted file mode 100644 index d75754906..000000000 --- a/pr-248/overrides/main.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base.html" %} - -{% block outdated %} - You're not viewing the version in the main branch. - - Click here to go to main. - -{% endblock %} diff --git a/pr-248/reference/cpp/differences/index.html b/pr-248/reference/cpp/differences/index.html deleted file mode 100644 index aafd0926a..000000000 --- a/pr-248/reference/cpp/differences/index.html +++ /dev/null @@ -1,2547 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Differences - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -

Differences Between C and C++

-

For most use cases, you can think of C++ as a superset of C. While this is not technically true, more often than not -you are able to write standard C code for a C++ program without issues. However, doing so ignores a lot of the benefits -and reasons to use C++.

-

Classes and Structs

-

In C structs can only contain member variables, but in C++ structs are basically classes but with a default member -visibility of public instead of private.

-
-Example -

The following code blocks are equivalent.

-
struct foo {
-private:
-    int x;
-    void helper(void);
-public:
-    foo(int y);
-}
-
-
class foo {
-private:
-    int x;
-    void helper(void);
-public:
-    foo(int y);
-}
-
-
-

Namespaces

-

One problem that is prevalent in C concerns the scoping of names. For example, let there be two files A.h and B.h -and a program ighxy.c, and let them both contain a float x and int bar(void).

-

Our program cannot compile because the linker cannot distinguish which bar() function we want to use! One way to fix -this in a C program would be to rename them a_bar() and b_bar(). Although this fix seems trivial for this example, -applying it to a file that has potentially 100 functions can be a lot more difficult, especially if two files just -happen to share the same prefix for their functions!

-

C++ introduces namespaces to tackle this problem. With namespaces, we can deal with naming conflicts much more easily. -Though be aware that namespaces are not necessary everywhere. See the following code snippet to see how they work.

-
-Example -
-
-
-
A.h
float x;
-int bar(void);
-
-
B.h
float x;
-int bar(void);
-
-
ighxy.c
#include "A.h"
-#include "B.h"
-
-int main(void) {
-    int a = bar();
-    ...
-}
-/* Error, does not compile*/
-
-
-
-
A.h
namespace a {
-float x;
-int bar(void);
-}
-
-
B.h
namespace b {
-float x;
-int bar(void);
-}
-
-
ighxy.cpp
#include "A.h"
-#include "B.h"
-
-int main(void) {
-    int a = a::bar();
-    int b = b::bar();
-    float xa = a::x;
-    float xb = b::x;
-    /* No problem! */
-    ...
-}
-
-
-
-
-
-
-Warning -

You may come across something like:

-
example.cpp
using namespace std;
-namespace io = std::filesystem;
-
-int main(int argc, char* argv[]) {
-    bool isDirectory = io::is_directory(argv[1]);  // Equivalent to std::filesystem::is_directory(argv[1])
-    cout << isDirectory << endl;
-    return 0;
-}
-
-

There are two things going on here.

-

First, using namespace std makes all functions and types defined within the standard namespace and included via -#include directives visible to example.cpp. If you are familiar with Python, the Python equivalent of this would -be import std as *. However, it is considered bad practice to do this as it eliminates the point of using -namespaces.

-
-
-
-
    class string {
-        // Insert implementation here
-    }
-
-    int main(void) {
-        string ourString = "Our own string implementation";
-        std::string stdString = "Standard Library string implementation";
-        ...
-    }
-
-
-
-
    using namespace std;
-
-    // ERROR - multiple definitions of type string
-    class string {
-
-    }
-
-

The compiler cannot infer which implementation we want.

-
-
-
-

Secondly, namespace io = std::filesystem is basically an alias for the std::filesystem namespace. This practice -is acceptable for long namespace identifiers, but be careful as it can still run into namespace conflicts if your -alias is the same as another namespace or alias.

-
-

Constant Expressions

-

In C, if we want to declare a constant or a function/expression that we want to be evaluated at compile time, we need -to use #define statements. One of the problems with #define statements is that they perform a simple copy paste -wherever they're used. For example:

-
-
-
-
#define PI 3.14F
-#define AREA_OF_CIRCLE(radius) ((PI) * (radius) * (radius))
-
-int main(void) {
-    float area = AREA_OF_CIRCLE(2.5F);
-    ...
-}
-
-
-
-
int main(void) {
-    float area = ((3.14F) * (2.5F) * (2.5F));
-    ...
-}
-
-
-
-
-
-

Note

-

AREA_OF_CIRCLE is a macro with arguments. If you are confused by it, this resource -has a detailed explanation on how they work.

-
-

Because of this copy-pasting, you need to be very careful with syntax, sometimes necessitating an ugly -do {} while(0) wrapper. -Moreover, symbols declared with #define are always globally visible, ignoring namespaces!

-

In C++, the use of constant expressions are preferred.

-
constexpr float pi = 3.14F;
-constexpr float area_of_circle(float radius) {
-    return pi * radius * radius;
-}
-
-

Constant expressions do not get copy pasted, and are instead placed in program memory just like a normal variable -or function. They also respect namespaces and function scopes, meaning the following code compiles.

-
Constant Expression Scoping
void foo(void) {
-    constexpr float rand = 123.456;
-    ...
-}
-
-void bar (void) {
-    constexpr float rand = 789.123;
-    ...
-}
-
-

Lambdas

-

Lambdas are primarily useful when you need to register a callback function one time and don't feel it's necessary to -write out a full function. They are in no way required though, so do not worry about learning them. However, it's -necessary to know that they exist such that you don't get confused when reading code. For more information, go here -for Microsoft's explanation.

-

Misc

-

Arrays

-

Using the C++ implementation of arrays -is preferred over C arrays. It is simply easier and safer to work with than a standard C array without any performance -costs.

-
-Example -

Passing an array to a function an iterating over it

-
-
-
-

#include "stdio.h"
-
-void print_contents(int *arr, int size) {
-    for (int i = 0; i < size; i++) {
-        printf("%d\n", *arr);
-    }
-}
-
-int main(void) {
-    int arr[5] = {0, 1, 2, 3, 4};
-    foo(arr, 5);
-    return 0;
-}
-
-We can't even guarantee that the integer pointer arr is an array!

-
-
-

C++ 20 makes passing arrays around a lot simpler. Do not worry about understanding the code shown below. It uses -some fairly advanced concepts and exists to illustrate how different such a simple operation can be.

-
#include <iostream>
-#include <array>
-#include <span>
-
-void print_contents(std::span<int> container) {
-    for (const auto &e : container) {
-        std::cout << e << std::endl;
-    }
-}
-
-int main(void) {
-    std::array<int, 5> arr = {0, 1, 2, 3, 4};
-    foo(arr);
-    return 0;
-}
-
-

The advantages of the C++ version are:

-
    -
  • Size is implicitly part of the object
  • -
  • We guarantee that foo takes a container, but it does not care if it's an array or, say, a vector, which is - preferable in this scenario where we simply iterate through the container's existing elements
  • -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/reference/cpp/start/index.html b/pr-248/reference/cpp/start/index.html deleted file mode 100644 index b2f339bb7..000000000 --- a/pr-248/reference/cpp/start/index.html +++ /dev/null @@ -1,2246 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Getting Started - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -

Getting Started

-

UBC Sailbot's Network Systems team uses C++ for its software. If you know already know C, then you already know the -bare minimum to write C++. This is a good starting point, but the additional features C++ provides allow for safer -programming practices.

-

For C/C++ Beginners

-

If you just need to know how C++ is different from C, then see the Differences Between C and C++. -You should also look at it if you go through and finish this section.

-

If you are new to C and C++, then this the best place to start. The tutorials provided in this section will help you -learn the fundamentals of the language. Do not feel pressured to do all the tutorials! Just get comfortable with the -syntax and the mechanisms of the language.

-
-

Note

-

The hardest part about this will likely be pointers and dynamic memory, so pay close attention to tutorials -concerning them! Additionally, dynamic memory requires the usage of pointers, but pointers do not require dynamic -memory!

-
-
-

Tip

-

Dynamic memory is much more prone to error than statically allocated memory, so try to use static allocation -whenever possible

-
- - - - - - - - - - - - - - - - - - - - - -
ResourceDescription
w3schools TutorialA structured tutorial that goes through basic concepts in C++. It's good to do up to the section on Classes.
YouTube TutorialIf you prefer video tutorial, then this is a comprehensive 4 hour video covering similar concepts to the one above. It is 4 hours long though.
Dynamic Memory OverviewA page going over how dynamic memory works in C++.
-

Feel free to add other resources other than the ones listed above if you find any that you like!

- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/reference/cpp/tools/index.html b/pr-248/reference/cpp/tools/index.html deleted file mode 100644 index d63890c43..000000000 --- a/pr-248/reference/cpp/tools/index.html +++ /dev/null @@ -1,2463 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Tools - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -

Tools

-

A lot goes into making a well structured C++ project, much more than any one team should have to do.

-

CMake

-

CMake is a powerfull build automation tool that makes compiling code for large projects with a lot of interoperating -files a lot easier. Steps 1-3 of the official tutorial -are great for understanding the basics.

-

GDB

-

The GNU Project Debugger is the most commonly debugger for the C -language family. -VSCode also has a degree of integration with GDB that allows an easy to use GUI. This GDB cheat sheet -has all the GDB comands you will need to know. Be aware the VSCode has GUI buttons for some of these commands that are -easier to use.

- - -

GoogleTest

-

GoogleTest is the C++ unit testing framework we will be using. -The GoogleTest Primer is a good place to start.

-
-Example -
-
-
-
cached_fib.h
#include <vector>
-class CachedFib {
-public:
-    void CachedFib(int n);
-    int  getFib(int n);
-private:
-    std::vector<int> cache;
-}
-
-
cached_fib.cpp
#include <iostream>
-#include <vector>
-#include "cached_fib.h"
-
-void CachedFib::CachedFib(int n) {
-    cache.push_back(0);
-    cache.push_back(1);
-    for (int i = 2; i < n; i++) {
-        cache.push_back(cache[i - 1] + cache[i - 2]);
-    }
-}
-
-int CachedFib::getFib(int n) {
-    if (cache.size() < n) {
-        for (int i = cache.size(); i < n; i++) {
-            cache.push_back(cache[i-1] + cache[i-2]);
-        }
-    }
-    std::cout << cache[n - 1] << std::endl;
-}
-
-
-
-
test_cached_fib.cpp
#include "cached_fib.h"
-#include "gtest/gtest.h"
-
-CachedFib::testFib;
-
-class TestFib : public ::testing::Test {
-protected:
-    void Setup override {
-        // Every time a test is started, testFib is reinitialized with a constructor parameter of 5
-        testFib = CachedFib(5);
-    }
-}
-
-TEST_F(TestFib, TestBasic) {
-    ASSERT_EQ(getFib(5), 3) << "5th fibonacci number must be 3!";
-}
-
-// more tests
-
-
-
-
-
- - -

Google Protocol Buffer

-

Google Protocol Buffer (Protobuf) is a portable data serialization -method. We use it over other methods like JSON and XML because it produces smaller binaries, an important consideration -when sending data across an ocean. Unfortunately, there does not seem to be a easy to follow tutorial for using them, -but here are the C++ basics. The page -is quite dense and can be hard to follow, so do not worry if you do not understand it.

-

Clang

-

In its most basic form, Clang is a compiler for the C language family. -Clang has multiple -benefits like easier portability compared to, for example, GCC. Clang is actually "half" the compiler, the other half -being LLVM. Without going into unnecessary detail, Clang compiles C++ code to a generic language before LLVM compiles -it to machine specific language.

-

Clangd

-

Clangd is the Clang language server. It provides a much more powerful -intellisense than the default one used in VSCode's C/C++ extension.

-

Clang-Tidy

-

Clang-Tidy is a linting tool, who's main purpose is to catch potential -programming errors caused by bad programming style/practices using just static analysis.

-

Clang Format

-

An autoformatting tool that makes enforcing style guidelines much easier. When se tup, it corrects formatting as soon -as you hit save.

-

llvm-cov

-

We will use llvm-cov to evaluate our test coverage. -When used with -genhtml, we can generate HTML reports that that -show our line, function, and branch coverage line-by-line.

- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/reference/github/workflow/branches/index.html b/pr-248/reference/github/workflow/branches/index.html deleted file mode 100644 index 0d298ceaf..000000000 --- a/pr-248/reference/github/workflow/branches/index.html +++ /dev/null @@ -1,2373 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Developing on Branches - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
- -
- - - -
-
- - - - - - - - - - - - - - - - - - - - -

Developing on Branches

-

We use branching to work on issues without modifying the main line. This ensures that the main line only -contains functional code and handles merge conflicts that arise -when multiple people are developing at the same time. For a quick rundown on branching in git, -consult the official git documentation.

-

Creating a branch

-

When starting a new issue, you will want to create a new branch for it:

-
-

Caution

-

When creating branches locally, it uses your local copy to create the new branch. Remember to do a git pull -if you intend on using the latest changes from the remote branch you are creating from.

-
-
Creating a new branch from main
# Switch to main
-git switch main
-
-# Update your local copy
-git pull
-
-# Clone a new branch from main
-git switch -c <branch_name>
-
-

IMPORTANT: When creating a new branch for an issue, you must create the branch from main.

-

Branch naming convention

-

When working on a new issue, you will want to create a branch to work on it. We have the following branch -naming convention:

-
user/<github_username>/<issue_number>-<issue_description>
-
-
-

Example

-

If Jill (GitHub Username: jill99) is going to take on an issue titled "Fix bug on pathfinding software" -and the issue number is 39, then the branch named can be named something like user/jill99/39-fix-pathfinding-bug.

-
-

If the branch that you are creating is not tied to an issue, then you do not need to put an issue number. -A descriptive title will suffice.

-

Tracking and committing changes

-

All files where new changes have been made must first be "staged" in order to make commits:

-
git add <FILES>
-
-

Files that are staged will be part of your next commit. Once you are confident in your changes and you are ready -to finalize them, then you should commit your changes:

-
git commit -m "<commit_message>"
-
-

Be sure to add a commit message that is descriptive of the changes that you made. It is encouraged that you make commits -often so you can keep track of your changes more easily and avoid overwhelmingly large commits when you look back on your -version history.

-

When you are ready to move your local changes to a remote branch, you want to push to the correct branch -and potentially set the upstream if it does not yet exist:

-
git push -u origin <current_branch_name>
-
-

Merging branches

-

There may be times where you want to merge two branches together, whether you diverged on some ideas and finally -want to synthesize them, or you just want to update your issue's branch with the main branch. In any case, merging -branches will be inevitable as part of the development process, so it is essential to understand how to merge branches.

-
-
-
-
# Checkout to destination branch
-git checkout <dest_branch>
-
-# Merge with local copy of other branch
-git merge <other_branch>
-
-
-
-
# Checkout to destination branch
-git checkout <dest_branch>
-
-# Fetch from remote
-git fetch
-
-# Merge remote copy of other branch
-git merge origin/<other_branch>
-
-
-
-
-
-

Info

-

Merging a remote branch into its local counterpart using the method above is essentially -the same operation as git pull.

-
-

Once the merge operation is complete, your destination branch should have updates both from itself and the other -branch that you merge. If you do a git log, you will also see a new commit that indicates that the merge happened.

-

Resolving merge conflicts

-

Merging two branches is not always easy since the commit history for both branches could look quite different, and -therefore conflicting changes can easily be made. If you run into a scenario like this, you may get something like this:

-

image

-

Upon inspecting bar.txt, we see the following:

-

image

-

Resolving merge conflicts is not always a trivial task, but there are many ways to resolve them which include:

- -
-

Tip

-

If you cannot resolve a merge conflict on your own, reach out to your lead for help!

-
- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/reference/github/workflow/issues/index.html b/pr-248/reference/github/workflow/issues/index.html deleted file mode 100644 index d4a6bf22b..000000000 --- a/pr-248/reference/github/workflow/issues/index.html +++ /dev/null @@ -1,2366 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Creating Issues - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - -

Creating Issues

-

GitHub issues lets us plan and track our work on GitHub.

-

Getting started with issue templates

-

An issue is associated with a specific repository. To open the issues page for a given repository, click on -the issues tab in the repository navigation bar.

-

image

-

You will see a list of current issues (if any) for the repository. To create a new issue, click on the -New issue button in the upper right corner.

-

image

-

When creating a new issue, you will see a few issue templates. Since issues can be created for a variety of reasons, -issues may therefore be structured differently and contain different kinds of information. Issue templates were -introduced to give us a quick and structured way to writing issues.

-

image

-
-

Note

-

GitHub issues are written using GitHub-flavoured markdown. To add a little spice to your issues, refer -to the official GitHub documentation -for some quick tips and tricks on how to write awesome markdown!

-
-

Click on the Get started button to open the issue template. For this example, let's go with the New Feature -issue template. Upon opening the issue template, you should see a page like the one below:

-

image

-

At this point, you should give a succinct title and describe the issue in the textbox. You will also see some templated -sections to fill out. Try to give only the necessary details to make a clear and concise issue. If you are unsure on how -to construct your issue, take a look at current or past issues and ask the software leads for further guidance if necessary.

-

Finally, feel free to make suggestions -on new templates or changing current templates!

-
-

Tip

-

We understand that some issues may need extra sections to describe the issue further, or some of -the templated sections might not be relevant at all! Add or remove sections as necessary to get your -point across. The goal of the issue templates is to provide guidance, not police your documentation -methodologies!

-
-

Adding issues to a project

-

We use projects to plan and track the status of our issues and pull requests. -To add an issue to an existing project, click on the gear icon in the Projects section and add it to your desired -project. You will almost always want to add your issue to the Software organization project.

-

image

-

To verify that your issue has been added to your desired project, go to the UBC Sailbot organization, go -to the Projects tab on the organization banner, and select the project that it is added to. When added -to a project, it should show up under the General tab (depending on the project, this might not always -be the case).

-

Adding issues to a milestone

-

We use milestones to track progress on groups of issues or pull requests that we want to complete by a certain date. -Since our projects span over many years, it is important to work incrementally with small, -yet achievable goals. If your issue should belong to a milestone, simply add it to a milestone by clicking -on the gear icon in the Milestone section and add it to your desired milestone.

-

image

-
-

Note

-

Unlike projects, milestones are strictly associated with a repository.

-
-

Labelling issues

-

GitHub allows us to label our issues so that we can categorize them. It helps us identify at first glance what -kind of a problem that an issue aims to solve and which issues are more important. To add a label to your issue, -click on the gear icon in the Labels section and add your desired label(s).

-

image

-

The issue templates will already have labels assigned to them, but you should add or remove labels as you see fit -to make them as relevant as possible.

-
-

Note

-

Each repository might have different labels available, so be sure to check out all of the labels -at least once in the repository that you are working in. Feel free to suggest additional labels -as well!

-
-

Adding assignees

-

Every issue should be assigned to at least one person to work on it. If you are not sure who should be assigned -the issue initially, then don't worry about it for now since you can assign someone to the issue later on. To -assign someone an issue, click on the gear icon in the Assignees section and add the desired people.

-

image

-

Submit the issue

-

Once you are finished writing your issue, click on the Submit new issue button. You should now see your issue -in the issues list and in the UBC Sailbot software project.

- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/reference/github/workflow/overview/index.html b/pr-248/reference/github/workflow/overview/index.html deleted file mode 100644 index 6632adf89..000000000 --- a/pr-248/reference/github/workflow/overview/index.html +++ /dev/null @@ -1,2272 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Overview - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -

Development Workflow Overview

-
graph LR
-    B[Problem Conception] --> C{Small Fix?};
-    C --> |Yes| E[Development];
-    C --> |No| D[Issue Creation];
-    D --> E;
-    E --> F[Pull Request];
-    F --> G{Approved?};
-    G --> |No| E;
-    G --> |Yes| H[Merge PR into Main];
-

A good development workflow is essential to maintain a robust codebase and stay organized. The above -diagram is a high level overview of how our development process works, and parts of this process -are explained in subsequent sections.

-

Version control: Git

-

We use git to help us keep track of the version history of our codebase. Git is a free and open source -distributed version control system, and it is commonly used by many developers to keep track of changes -to their code over time. As a member of the software team on UBC Sailbot, it is absolutely necessary that -you know git. If you are unfamiliar with git, here are a few resources to help you get started:

- - - - - - - - - - - - - - - - - - - - - -
ResourceDescription
Beginners TutorialA 30 minute video on git for beginners. Good if you want to learn git quickly and nail all the fundamentals.
Pro Git bookA textbook on using git. Good if you are a completionist and want to deep dive into how git works (and if you have some time on your hands).
Common Git CommandsA condensed summary of some common git commands. Good to refer to once you are familiar with the fundamentals of git.
-

Remote server: GitHub

-

We use GitHub as our remote server where we store our codebase. In addition to using it for storage, we also -leverage many of GitHub's features to make for a smoother development process. Some examples of features -that we use are:

- - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/reference/github/workflow/pr/index.html b/pr-248/reference/github/workflow/pr/index.html deleted file mode 100644 index 5418c8628..000000000 --- a/pr-248/reference/github/workflow/pr/index.html +++ /dev/null @@ -1,2328 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Pull Requests - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -

Pull Requests

-

Pull requests are used to verify code functionality and quality of a development branch before merging into the main branch, -accomplished through CI and code reviews.

-
-

Note

-

Pull requests are much like issues where we can do many of the same things. This goes for creating -comments in markdown, assigning reviewers, adding labels, adding projects, or adding milestones. -Sometimes we skip writing an issue when the change is relatively small.

-
-

Creating a pull request

-

To create a pull request in a repository, to go the Pull requests tab and then click New pull request:

-

image

-

On the next screen, you need to select the base branch that you are merging into, and the branch that you -are comparing. For the most part, the base branch will be the main branch, and the branch that you are comparing -will be the issue branch.

-

image

-

Once you have decided on your base and compare branches, click on Create pull request. You should see -the page below (looking in the dropdown menu, you can open the pull request as a draft to avoid notifying -reviewers until you are ready):

-

image

-

Notice how this is remarkably similar to the page of an issue. To link a pull request to an issue, simply add -<KEYWORD> #<ISSUE NUMBER> to the initial comment in the pull request. A list of valid keywords can be found -here.

-
-

Example

-

"This issue resolves #49. Please review my pull request!"

-
-

Observe that the right-hand side banner contains the following:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
ReviewersAssign reviewers to review your pull request. Always try to assign at least one reviewer.
AssigneesAssign the people who worked on the issue.
LabelsAssign labels to categorize pull requests.
ProjectsAssign a pull request to a project.
MilestoneAssign a pull request to a milestone.
-
-

Attention

-

If you linked the pull request to an issue, you should not add the pull request to a project or a milestone to -avoid duplicate cards.

-
-

Merging into main

-

Once the pull request and code reviews are complete, it is time to merge the changes in the pull request into the main -branch! However, this can only be done when the following conditions are met:

-
    -
  1. All CI checks pass (look for a green checkmark beside your latest commit on GitHub).
  2. -
  3. All reviewers have reviewed the PR and approved the PR.
  4. -
  5. There are no unresolved comments and suggestions from the reviewers.
  6. -
  7. There are no merge conflicts with the main branch.
  8. -
-

If all of these conditions are met, confirm that the merge is good to go by clicking Squash and merge:

-

image

-

Reviewing a pull request

-

A common activity that you will participate in is reviewing pull requests to give your feedback on other's code. -You will be notified when you have been requested to review a pull request and should promptly review it as -soon as time permits.

-

image

-

In particular, you will most likely be doing the following in a pull request:

-
    -
  • Asking Questions: Clarify your understanding about something that you are not sure about.
  • -
  • Providing Suggestions: Give some ideas about how to improve the current implementation and provide feedback to -your peers. This is a good opportunity to share your knowledge with others.
  • -
  • Verify Implementations: Identify potential bugs in the implementation and raise your concerns with the person -who developed the solution. This will reduce the likelihood of bugs and significantly bring down the number of issues -in the future.
  • -
  • Documentation: Record why certain changes were made, especially if this diverges from the proposed solution -in the linked issue (if any).
  • -
- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/reference/markdown/index.html b/pr-248/reference/markdown/index.html deleted file mode 100644 index cb6f32bde..000000000 --- a/pr-248/reference/markdown/index.html +++ /dev/null @@ -1,2422 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Markdown - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -

Markdown

-

Markdown is a lightweight markup language that you can use to add formatting elements to plaintext text documents.1 -You can do anything with Markdown, from creating websites to PDF documents, all in a clean format that is easy to -learn. Many of your favorite services use Markdown, so it would -be useful to pick it up to write technical documentation.

-

Markdown is not standardized across services. Many services that support Markdown have their -own "flavour" of Markdown. Be sure to know the Markdown features of the service -you are using so that your Markdown renders properly.

-

Getting Started

-

We recommend markdownguide.org to be your first point of reference if\ -you are learning Markdown for the first time. It covers topics like what Markdown is, its syntax, advanced tips, and the -different services that support Markdown. Flavours of Markdown specific to a service build on top of these basics.

-

Sailbot and Markdown

-

We write Markdown for GitHub and Material for MkDocs. The following sections -detail how Markdown is used in these services.

-

GitHub

-

We use Markdown in GitHub for technical documentation and collaboration. This includes:

-
    -
  • README.md files
  • -
  • Issues
  • -
  • Pull Requests
  • -
-

Almost all places where text is written in GitHub support Markdown. GitHub also allows you to preview -your Markdown before you submit any comments.

-
-
-
-

image

-
-
-

image

-
-
-
-

The image above shows an example of a "write" and a "preview" tab for writing a comment on an issue. It might look -different depending on where you are writing, but there usually exists a preview option!

-
-

GitHub-Flavoured Markdown

-

GitHub uses its own "flavour" of Markdown. Certain features, like using HTML, are excluded for security reasons. -Visit the official GitHub Markdown guide -for more information on the available features.

-
-

Material for MkDocs

-

We use Markdown in Material for MkDocs to create this website! Since it is written in Markdown, no frontend -experience is required to contribute to our docs.

-

Material for MkDocs supports powerful features purpose-built to take technical documentation to the next level. -Feel free to browse this site to see how we use these features, exploring their syntax in the -source code. Since GitHub renders Markdown files automatically -you will need to click the "Raw" button to view their contents.

-
-

Material-Flavoured Markdown

-

Material for MkDocs' flavour of Markdown extends upon vanilla Markdown, adding features such as admonitions -(like this note) and content tabs. Refer to the -official Material for MkDocs reference page -for more information on the available features.

-
-

Rendering Markdown

-

You have a few choices to render Markdown on your computer. -Be advised that if you are using an extended version of Markdown, you will -need to consult the documentation from the service provider to render their flavour of Markdown properly. The following -resources are good for rendering Markdown:

-
-
-
- -
-
-
    -
  • Markdown Preview GitHub Styling: -VS Code extension that renders GitHub-flavoured markdown.
  • -
  • Create a draft issue on GitHub and preview the markdown to see how it renders.
  • -
-
-
- -
-
-
-

Other resources exist to render Markdown like browser extensions that render Markdown as HTML and GitHub repositories -that contain source code to render your Markdown. Feel free -to browse around for the solution that suits your needs.

-

Linting

-

We lint our Markdown files to reduce errors and increase readability. In particular, we use two tools:

-
    -
  1. -

    markdownlint is -used to enforce a style guide. Its configuration file for this repository is .markdownlint.json. -If you use VS Code, there is a markdownlint extension.

    -
  2. -
  3. -

    markdown-link-check is -used to check for broken links. Its configuration file for this repository is .markdown-link-check.json.

    -
  4. -
- - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/reference/python/conventions/index.html b/pr-248/reference/python/conventions/index.html deleted file mode 100644 index 195618277..000000000 --- a/pr-248/reference/python/conventions/index.html +++ /dev/null @@ -1,2553 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Conventions - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -

Conventions

-

At UBC Sailbot, we follow standards in how we code to maintain a clean and comprehensible codebase. -This page addresses what conventions we use specifically when programming in Python and the tools to help us maintain -these conventions.

-

Style guide

-

Linting

-

To ensure that the codebase stays clean, we use flake8, which is a -tool for style guide enforcement mostly based off pep8. To automate -most of this process, we use autopep8, which is a tool that resolves -most style issues. However, there will be some issues that must be resolved by you!

-

Refer to this guide on how to write readable code in python with the -pep8 style guide.

-
-

Note

-

Our CI automatically checks that your code follows the pep8 standard. If it does not, your pull requests will -be blocked from being merged until those issues are resolved!

-
-

Type hinting

-

Even though Python is a dynamically typed language, newer versions support type hinting. Type hinting catches -errors, documents code, improves IDEs and linters, and helps build and maintain a clean software architecture.1 -Expanding on how it catches errors, a static type checker such as mypy -can be used.

-

There is some syntax to get familiar in order to use type checking. We recommend the following resources:

- -

Below are a few examples of using type hinting:

-
-Return the sum of a sequence -
from typing import Sequence, Union
-
-
-Number = Union[int, float]
-
-
-def sumseq(seq : Sequence[Number]) -> Number:
-    return sum(seq)
-
-
-
-Function with optional parameters and default values -
from typing import Optional
-
-
-def printArgs(a : str, b : str="World", c : Optional[str]=None) -> None:
-    print(f"Value of a: {a}")
-    print(f"Value of b: {b}")
-    if c is not None:
-        print(f"Value of c: {c}")
-
-
-
-Function with custom class -
class MyClass:
-    def __init__(self) -> None:
-        pass
-
-
-def foo(a : MyClass) -> None:
-    print(a)
-
-
-
-Forward referencing a class -
-
-
-
from __future__ import annotations
-
-
-def foo(a : MyClass) -> None:
-    print(a)
-
-
-class MyClass:
-    def __init__(self) -> None:
-        pass
-
-
-
-
def foo(a : 'MyClass') -> None:
-    print(a)
-
-
-class MyClass:
-    def __init__(self) -> None:
-        pass
-
-
-
-
-
-
-Function that never returns -
from typing import NoReturn
-
-
-def bar() -> NoReturn:
-    while True:
-        print("Hello World!")
-
-
-

Documentation

-

Code is written once and read a thousand times, so it is important to provide good documentation for current -and future members of the software team. The major things that we document in our code are:

-
    -
  1. Classes and Objects:
      -
    • What does it represent? What is it used for?
    • -
    • What are its member variables? What are they used for?
    • -
    -
  2. -
  3. Functions:
      -
    • What are the inputs and outputs?
    • -
    • What is the overall behavior and purpose of the function?
    • -
    -
  4. -
  5. Code:
      -
    • Is a line of code obscure and/or not clear? Add an inline comment to clear things up.
    • -
    • Break down a large process.
    • -
    -
  6. -
-

Ideally, the third point should be avoided as much as possible since we would want our code to be -self explanatory. It should be done only when absolutely necessary.

-

Generating docstrings

-

We use a vscode extension called autoDocstring -which autogenerates docstrings that we use to document our code. To install this extension, go to the Extensions tab in -vscode and search autoDocstring in the marketplace.

-

To generate docstrings, type """ at the beginning of the function that you want to document and the template -will be generated for you! If you use type hinting, this extention will autofill some of the -documentation for you!

-
-

Note

-

The autoDocstring extension only works for functions. It does not work for classes and objects, so documenting these -will have to be done manually. Be sure to follow the same format used by functions.

-
-

Example on documentation

-

It's hard to imagine what good documentation looks like. We provide a few examples below of documenting code using the -autoDocstring extension. The extension uses Google style docstrings -by default.

-
-Documentation example on a function -
from typing import List
-def inner_product(v1 : List[float], v2 : List[float]) -> float:
-    """
-    Computes the inner product between two 1D real vectors. Input vectors should have the
-    same dimensions.
-
-    Args:
-        v1 (List[float]): The first vector of real numbers.
-        v2 (List[float]): The second vector of real numbers.
-
-    Returns:
-        float : The inner product between v1 and v2
-    """
-    assert (len(v1) == len(v2)), "Input lists must have same length"
-
-    # Iterate through elementwise pairs
-    summation = 0
-    for e1, e2 in zip(v1, v2):
-        summation += (e1 * e2)
-    return float(summation)
-
-
-
-Documentation example with a stack -
from typing import Any
-class Stack:
-
-    """
-    This class represents a stack, which is an abstract data type that serves as a collection of
-    elements. The stack is a LIFO datastructure defined by two main operations: Push and Pop.
-
-    Attributes:
-        __stack (List[Any]): A list containing the elements on the stack.
-    """
-
-    def __init__(self):
-        """
-        Initializes the Stack object.
-        """
-        self.__stack = []
-
-    def push(self, element : Any) -> Any:
-        """
-        Pushes an element to the top of the stack.
-
-        Args:
-            element (Any): The element to be pushed on to the stack.
-        """
-        self.__stack.append(element)
-
-    def pop(self) -> Any:
-        """
-        Removes the element at the top of the stack and returns it. If the stack is empty,
-        then None is returned.
-
-        Returns:
-            Any, NoneType: The element at the top of the stack.
-        """
-        if self.is_empty():
-            return None
-        else:
-            return self.__stack.pop()
-
-    def is_empty(self) -> bool:
-        """
-        Determines whether the stack is empty or not.
-
-        Returns:
-            bool: Returns True if the stack is empty, and False otherwise.
-        """
-        empty = (len(self.__stack) == 0)
-        return empty
-
-    def __len__(self) -> int:
-        """
-        Gets the number of elements on the stack.
-
-        Returns:
-            int: The number of elements on the stack.
-        """
-        length = len(self.__stack)
-        return length
-
-
-

For more examples, see Example Google Style Python Docstrings.

- - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/reference/python/start/index.html b/pr-248/reference/python/start/index.html deleted file mode 100644 index 82a92d5ca..000000000 --- a/pr-248/reference/python/start/index.html +++ /dev/null @@ -1,2241 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Getting Started - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -

Getting Started

-

We use Python 3 to write the majority of our software at UBC Sailbot. Pathfinding and Controls mainly use Python 3, -so it is critical that you are familiar with the language if you are on one of these sub-teams.

-

Python tutorials

-

We understand that not everyone who joins Sailbot has Python in their toolkit, nor do we expect it either! Whether you -are learning Python for the first time or you just want to brush up, we have provided some resources below. You may not -learn absolutely everything from the resources below, but it is a good starting point. You will mostly learn through doing, -as you would with most technical skills!

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ResourceDescription
The Python TutorialThe official python tutorial. Good if you have some time on your hands and you are a completionist. Sections 1 - 5 and 9 are the most relevant.
w3schools TutorialGood if you want a more brief introduction to Python. It breaks down a lot of concepts into sections. Everything up to Python Classes/Objects is relevant.
YouTube TutorialIf you like video tutorials, then we recommend this tutorial. This video is about 5 hours long, but it pretty much covers everything that you'll need to know for Python and there are some hands on projects.
Shorter YouTube TutorialA shorter alternative YouTube tutorial condensed into 1 hour. It covers less material but still covers many of the essentials.
CodingBat PracticeGood resource to put your Python skills to practice on some simple coding problems. Note that this resource does not teach you python.
-

Feel free to add other resources other than the ones listed above if you find any that you like!

- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/reference/python/virtual-environments/index.html b/pr-248/reference/python/virtual-environments/index.html deleted file mode 100644 index 134bbe8d8..000000000 --- a/pr-248/reference/python/virtual-environments/index.html +++ /dev/null @@ -1,2648 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Virtual Environments - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - -

Virtual Environments

-

The Python virtual environment is a tool for dependency management and project isolation. They solve many -common issues, including:

-
    -
  • -

    Dependency Resolution: A project might want a package with version A while another project might want -a package with version B. With a virtual environment, you can separate which packages that you want to use -for a given project.

    -
  • -
  • -

    Project Isolation: The environment for your project is self-contained and reproducible by capturing all -dependencies in a configuration file.

    -
  • -
  • -

    Housekeeping: Virtual environments allow you to keep your global workspace tidy.

    -
  • -
-

There are two main methods of creating virtual environments: virtualenv -and Anaconda. Each have their own benefits and drawbacks. Here are some differences -between the two:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
VirtualenvAnaconda
Environment files are local.Environment files are available globally.
Must activate environment by giving the path.Can activate the environment without knowing the path, but only the name.
Can only use pip to install packages.Can either use pip or built-in conda package manager.
Installation is very simple.Installation takes more effort.
Can only install python packages.In addition to packages, you can download many data science tools.
-

We recommend virtualenv over Anaconda because of its simplicity. However, feel free to appeal to your preferences.

-

Installation

-
-
-
-

If you already have python and the pip package manager installed, just execute the following:

-
Using pip to install virtualenv
pip install virtualenv
-
-
-
-

Go to the official Anaconda website and follow the installation -instructions for your operating system.

-
-
-
-

Using virtual environments

-

The name of a virtual environment is configurable. For the purposes of this site, we will use env as the environment -name unless specified otherwise.

-

Creating a virtual environment

-
-
-
-

Since virtualenv creates the environment directory in a specific location, make sure that you -are in the located in the project that you want to work on.

-
Create virtual environment with virtualenv
# Go to desired location
-cd <PATH TO DIRECTORY>
-
-# Create the environment with the name env
-python3 -m venv env
-
-

Verify that your environment is created by examining your current directory and look for the directory -that matches the name of your virtual environment.

-
-
-

Since the environment will be available globally, there is no need to go to a specific location to create -it.

-
Create virtual environment with Anaconda
# Create environment with name env and python version
-conda env create -n env python=<PYTHON VERSION NUM>
-
-

If you don't specify a python version, the default is the version you used when you downloaded and installed Anaconda. -Verify that your environment is created by executing conda env list.

-
-
-
-

Activating the virtual environment

-

To use the virtual environment, you must activate it.

-
-
-
-
-
-
-
Activation for Windows
env\Scripts\activate
-
-
-
-
Activation for macOS
source env/bin/activate
-
-
-
-
Activation for Linux
source env/bin/activate
-
-
-
-
-
-
-
Activation for Anaconda
conda activate env
-
-
-
-
-

After activating your virtual environment, you might see (env) on your terminal before or after -your current line. Now you are in your virtual environment!

-

Installing dependencies

-

Any dependencies that you install while your virtual environment is activated are only available in your virtual -environment. If you deactivate your environment and try to use those dependencies, you will find that you will get -errors because they will not be found unless you install those dependencies in the other environment!

-
-
-
-

Use the pip package manager to install python dependencies. Before installing any Python dependencies, it is good -practice to upgrade pip:

-
Upgrade pip
pip install --upgrade pip
-
-

Now, install any Python dependencies pip:

-
Install dependency with pip
pip install <PACKAGE>
-
-
-
-
-
-
-

Use the pip package manager to install python dependencies.

-
Install dependency with pip
# Install pip using conda
-conda install pip
-
-# Install python packages using pip
-pip install <PACKAGE>
-
-
-
-

Use the built-in conda package manager to install python dependencies.

-
Install dependency with conda
conda install -c <CHANNEL> <PACKAGE>
-
-

Sometimes, installing a package like this simply won't work because you are not installing -from the correct channel. -You usually will have to google the command to use in order to install your package correctly because -it usually comes from a specific channel that you don't know about. Some common channels to try are:

-
    -
  • conda-forge
  • -
  • anaconda
  • -
  • bioconda
  • -
  • r
  • -
-
-
-
-
-
-
-

Deactivating the virtual environment

-

When you are finished using your virtual environment, you will need to deactivate it.

-
-
-
-
Deactivate virtualenv environment
deactivate
-
-
-
-
Deactivate anaconda environment
conda deactivate
-
-
-
-
-

Reproducing your virtual environment

-

When you want to share your code with others, it is important for others to be able to reproduce the environment -that you worked in. We discuss two topics in this section: exporting your environment and reproducing the environment.

-

Exporting your virtual environment

-

In order to reproduce your virtual environment, you need to export some information about your environment. -Be sure to follow the instructions below while your environment is activated.

-
-
-
-

You will create a requirements.txt file, which essentially lists all of your python dependencies in one -file:

-
Creating requirements file
pip freeze > requirements.txt
-
-

The pip freeze command prints all of your pip dependencies, and > requirements.txt redirects the output -to a text file.

-
-
-

Anaconda uses configuration files to recreate an environment.

-
-
-
-

Execute the following command to create a file called environment.yml:

-
Create config file
conda env export > environment.yml
-
-

Then, open the environment.yml file and delete the line with prefix:.

-
-
-

Execute the following command to create a file called environment.yml:

-
Create config file
conda env export | grep -v "^prefix: " > environment.yml
-
-
-
-

Execute the following command to create a file called environment.yml:

-
Create config file
conda env export | grep -v "^prefix: " > environment.yml
-
-
-
-
-
-
-
-

Reproducing the environment

-

You can reproduce your virtual environment when given the information about it. The steps above tell you how -to extract the information, and now we will use that information to recreate the virtual environment. -Remember to deactivate the current environment before making a new environment.

-
-
-
-

We use the requirements.txt file that we generated earlier to recreate the environment.

-
Recreate virtualenv environment
# Create the new environment
-python -m venv <NEW ENV NAME>
-
-# Activate the environment
-source <NEW ENV NAME>/bin/activate
-
-# Install dependencies
-pip install -r <PATH TO requirements.txt file>
-
-
-
-

We use the environment.yml file that we generated earlier to recreate the environment.

-
Recreate the conda environment
# Create the new environment with the dependencies
-conda env create -f <PATH TO environment.yml> -n <ENV NAME>
-
-
-
-
-

Official references

-

In this section, we summarized what virtual environments are, why they are used, and how to use them. We did not -cover all of the functions of virtual environments, but feel free to consult the official references to learn -about virtual environments more in depth.

- - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/reference/ros/index.html b/pr-248/reference/ros/index.html deleted file mode 100644 index 079a8f467..000000000 --- a/pr-248/reference/ros/index.html +++ /dev/null @@ -1,2382 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Robot Operating System - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -

Robot Operating System

-

Robot Operating System (ROS) is a set of software libraries and tools for building robot applications.1 -It provides functionality for hardware abstraction, device drivers, communication between processes over -multiple machines, tools for testing and visualization, and much more.2

-

We use ROS because it is open-source, language-agnostic, and built with cross-collaboration in mind. -It enables our sub-teams to work independently on well-defined components of our software system -without having to worry about the hardware it runs on or the implementation of other components.

-

The official ROS 2 documentation contains everything you need -to get started using ROS. From it we have hand-picked the resources that are most relevant to our current and expected -future usage of ROS assuming that you use our preconfigured workspace. -To run our software on your device without our workspace, you would have to install ROS -and the dependencies that are in our Docker images -yourself.

-

Workspace Configuration

-

To get our workspace configuration running on your computer:

-
    -
  1. Set it up by following the setup instructions
  2. -
  3. Uncomment the ROS 2 tutorials section in .devcontainer/Dockerfile, - then run the "Dev Containers: Rebuild Container" VS Code command, to install the tutorials' dependencies
  4. -
  5. Uncomment the ROS 2 tutorials section in src/polaris.repos, - then run the "setup" VS Code task, to clone the repositories used in the tutorials
  6. -
-

Our workspace configuration contains easier methods of accomplishing some of the tutorial steps, or eliminates the need -for them altogether.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Tutorial stepSailbot Workspace configuration
Install a packageAll packages used in the tutorials are already installed (step 2 above)
Clone a sample repo (ros_tutorials)ros_tutorials is already cloned (step 3 above)
Resolve dependenciesRun the "install dependencies" VS Code task
Build the workspaceRun the "Build" VS Code task, AKA Ctrl+Shift+B
Source the overlayRun the srcnew terminal command
Create a package with a nodeRun the "new ament_(python|cmake) package with a node" VS Code task
-

Tutorials

-

We encourage all software members to work through the ROS tutorials -that are listed below in order. For tutorials that have both C++ and Python versions, -NET members should do the C++ version while CTRL and PATH members should do the Python version.

-
    -
  • Beginner: CLI tools
      -
    • Introducing turtlesim and rqt
    • -
    • Understanding nodes
    • -
    • Understanding topics
    • -
    • Understanding services
    • -
    • Understanding parameters
    • -
    • Understanding actions
    • -
    • Using rqt_console to view logs
    • -
    • Recording and playing back data
    • -
    -
  • -
  • Beginner: Client libraries
      -
    • Creating a workspace
    • -
    • Creating a package
    • -
    • Writing a simple publisher and subscriber (C++ or Python)
    • -
    • Writing a simple service and client (C++ or Python)
    • -
    • Using parameters in a class (C++ or Python)
    • -
    • Using ros2doctor to identify issues
    • -
    -
  • -
  • Intermediate
      -
    • Launch
    • -
    • Testing
    • -
    -
  • -
  • Demos
      -
    • Logging
    • -
    -
  • -
-

Concepts

-

We encourage all software members to read the following documentation on key ROS concepts:

-
    -
  • About logging and logger configuration
  • -
  • About ROS 2 interfaces
  • -
  • About parameters in ROS 2
  • -
-

ROS 1 Bridge

-

There are two major versions of ROS, aptly named ROS 1 and ROS 2. Our previous project, Raye, -uses ROS 1 because it was the only version available during her design process. Our new project will -use ROS 2, a complete re-design of the framework that tackles the shortcomings of ROS 1 to bring it up -to industry needs and standards.3 If you are curious about the changes made in ROS 2 compared to 1, -this article is a worthwhile read.

-

ROS 2 includes the ROS 1 Bridge, a collection of packages that can be installed alongside ROS 1 to help migrate code -from ROS 1 to ROS 2. As we will be reusing parts of Raye's codebase, it is essential to know how to use these packages. -Until we are completely done with Raye, our preconfigured workspace will have ROS 1, ROS 1 Bridge, and ROS 2 installed.

-

We encourage all software members work through the ROS 1 Bridge README. -For PATH members, the Migrating launch files from ROS 1 to ROS 2 page -will be a helpful reference when we do so.

- - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/reference/sailing/ais_terms/index.html b/pr-248/reference/sailing/ais_terms/index.html deleted file mode 100644 index 4e7ad363a..000000000 --- a/pr-248/reference/sailing/ais_terms/index.html +++ /dev/null @@ -1,2285 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - AIS Terms - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -

AIS Terms

-

This section explains the most unfamiliar fields that we receive from the AIS.

-

MMSI a.k.a ID

-

A 9-digit, unique identification number for the ship.

-

COG: Course over Ground

-

The direction the boat is travelling, relative to the sea floor. This is the direction of the rate of change -of the Track Made Good.

-

This is measured with the navigational angle convention, where 0° is towards the North, and angles increase in the -clockwise direction. If we make the slight simplification of neglecting the effect of the wind, then

-
    -
  • If the boatspeed is positive and there is no current, the boat's Course over Ground will be the same as the Heading.
  • -
  • If the boatspeed is zero and there is positive current, the boat's Course over Ground will be the same direction as the -current is flowing.
  • -
-

SOG: Speed over Ground

-

The speed the boat is travelling at, relative to the sea floor. This is the magnitude of the rate of change -of the Track Made Good.

-

\(\begin{align*} -\text{SoG} &= \left|\frac{d}{dt} \overrightarrow{(\text{Track Made Good})} \right|\\ -\end{align*}\)

-

If we make the slight simplification of neglecting the effect of the wind, then

-
    -
  • If the boatspeed is positive and there is no current, the boat's Speed over Ground will be the same as the speed of water -hitting your hand, if you were sitting on the boat and put your hand in the water.
  • -
  • If the boatspeed is zero and there is positive current, the boat's Speed over Ground will be the same speed as the -current.
  • -
-

RoT: Rate of Turn

-

The angular velocity of the boat (how fast it's turning), measured in degrees per minute.

- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/reference/sailing/boat_parts/index.html b/pr-248/reference/sailing/boat_parts/index.html deleted file mode 100644 index 05ff2530c..000000000 --- a/pr-248/reference/sailing/boat_parts/index.html +++ /dev/null @@ -1,2382 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Parts of a Sailboat - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -

Parts of a Sailboat

-

This page names some important parts of a sailboat, and explains what the part is for. -Read the descriptions of the parts below, and refer to the image to see where the part fits in.

-

image

-

Hull

-

The Hull is the "boat" part of the boat, which displaces water to create buoyancy. The following parts of the boat -are attached to the hull:

-
    -
  • Keel: The keel has a large mass on the end, which keeps the sailboat upright. The fin-like shape of the -keel provides lateral resistance to prevent the boat from slipping sideways through the water.
  • -
  • Rudder: Raye has two rudders for redundancy. The rudders can angle side to side to steer the boat. -To steer the boat effectively, the rudders need enough water flowing over them to create a pressure difference when they -angle sideways. Controls sends commands to the rudder to steer the boat.
  • -
-

It is also helpful to know the names of the following "regions" of the hull:

-
    -
  • Bow: The front of the boat.
  • -
  • Stern: The back of the boat.
      -
    • Aft means "backwards towards the stern".
    • -
    -
  • -
  • Starboard: The side of the boat which is on the right, for someone standing on the boat facing the bow.
  • -
  • Port: The side of the boat which is on the left, for someone standing on the boat facing the bow.
      -
    • To remember which is which between starboard and port, remember that "port" and "left" both have 4 letters.
    • -
    -
  • -
-

The image below shows a birds-eye view of the outline of a hull of a sailboat, -where the "regions" of the hull are labeled.

-

image

-

Jib

-

The Jib is the sail located near the bow, and is the smaller of the two sails.

-
    -
  • Jib Sheet: In general, sheets are ropes that pull a sail in to the boat, and the jib sheet does this for the jib. -On Raye, the jib sheet connects to the back bottom corner of the jib, through a pulley near the bottom of the mast to -the Jib Winch. Most sailboats have two jib sheets, one on either side, but Raye is designed differently for autonomy.
  • -
  • The Jib Winch is a motor-driven device that tightens or pulls in the jib by pulling on the jib sheet. -Controls sends commands to the winches.
  • -
  • The jib halyard: In general, a halyard is a rope that pulls a sail up. The jib halyard pulls up the jib. -It connects to the top of the jib, runs through a pulley near the top of the mast, and is tied off -near the bottom of the mast.
  • -
-

Mast

-

The Mast is the long vertical pole which connects to hull. It holds up the sails and some instruments.

-

The following instruments are at the top of the mast:

-
    -
  • One of the 3 Wind Sensors. The top of the mast is a good location to measure undisturbed wind. -Pathfinding and Controls both use data from the wind sensors.
  • -
  • The AIS antenna. AIS ("Autonomous Identification System") is a system by which ships -communicate their location, speed, and other information to surrounding ships via radio signals. -Pathfinding uses AIS data to avoid other ships.
  • -
-

The mast is held upright by three lines:

-
    -
  • The forestay connects the mast from the top of the jib to the bow, and runs parallel to the front edge of the jib.
  • -
  • The two shrouds connect the mast from the top of the jib to the outside edges of the hull slightly aft of the mast. -There is one shroud on the startboard side and one on the port side.
  • -
-

Main Sail

-

The Main Sail is the larger of the two sails, and is located aft of the mast. -Most of the boat's propulsion comes from the main sail.

-
    -
  • The Boom is the horizontal pole that holds the bottom corner of the main sail out from the mast.
  • -
  • Main Sheet is the rope that pulls the main sail in towards the center of the boat. It connects from the back end of -the boom, through a pulley on the stern, to the Main Winch.
  • -
  • The Main Winch is a motor-driven device that pulls in the main sail by pulling on the main sheet. -Controls sends commands to the main winch.
  • -
  • The main halyard is the line used to hoist the main sail.
  • -
-

Conclusion

-

Hopefully this section helped you gain familiarity with some common sailing terms. -It likely feels like this section contains a lot of new information. It's unrealistic to remember it all perfectly, -but make an effort to remember the terms which are Bolded and Italicized.

-

Keywords on this Page

-
    -
  • Hull
  • -
  • Keel
  • -
  • Rudder
  • -
  • Bow
  • -
  • Stern
  • -
  • Starboard
  • -
  • Port
  • -
  • Jib
  • -
  • Jib winch
  • -
  • Mast
  • -
  • Wind Sensor
  • -
  • AIS Antenna
  • -
  • Main Sail
  • -
  • Main Winch
  • -
- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/reference/sailing/miscellaneous/index.html b/pr-248/reference/sailing/miscellaneous/index.html deleted file mode 100644 index 8ccd954df..000000000 --- a/pr-248/reference/sailing/miscellaneous/index.html +++ /dev/null @@ -1,2446 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - Miscellaneous - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - -

Miscellaneous Sailing Knowledge

-

This section covers some other useful information.

-

Wind Direction Convention

-

Generally speaking, there are two ways to use an angle to describe the wind direction.

-
    -
  1. The angle tells you which way the wind is blowing towards. For example, 0° means the wind is blowing from North -to South.
  2. -
  3. The angle tells you which way the wind is coming from. For example, 0° means the wind is blowing from South to North.
  4. -
-

In sailing, we normally talk about "where the wind is coming from". Somehow this ends up being more intuitive when -talking about maneuvers or sail angle adjustments.

-

However, when describing the wind as a vector, it can make more sense for the vector to represent the actual -speed and direction the air is flowing. Make sure to document which convention you are using in your work when -its applicable, and don't be afraid to ask someone to clarify which convention they are using in their work.

- -

Heading

-

In navigation generally (outside of Sailbot), the Heading is the direction the bow of the boat is pointing -towards. Headings are typically (but not always at Sailbot) measured relative to true North in the clockwise direction.

-

Bearing

-

A Bearing is used to describe one point in relation to another: the Bearing of point "A" from point "B" -is the direction you would would look towards if you wanted to see point "A" while standing at point "B". A Range -is the distance between points "A" and "B", so that a Bearing and Range together can locate point "A" relative to point -"B" in polar co-ordinates. There are two main ways of measuring bearings:

-
    -
  • A True Bearing is a bearing where the angle convention is as follows: 0° is towards the North, -angles increase in the clockwise direction, and angles are typically bounded within [0°, 360°)]
  • -
  • A Relative Bearing is a bearing where the angle convention is as follows: 0° is straight forwards relative to the -boat, and angle measurements increase in the clockwise direction. Angles may be bounded in [-180°, 180°) or [0°, 360°)
  • -
-

In the example below, the boat "B" has a Heading (H) of 30°. The True Bearing (\(B_t\)) of the Lighthouse "A" -from the boat is 90°. The Relative Bearing (\(B_r\)) of the lighthouse from the boat is 60°.

-

image

-

Track Made Good

-

Boats do not necessarily travel in the same direction as their Heading, due to the effects of ocean current and -wind. The path the boat has traveled relative to the sea floor is called the Track Made Good. This is the -same as if you measured motion compared to land or with a GPS.

-

image

-

Heading and Bearing in Raye Project

-

In Sailbot's Raye project, Heading and Bearing are used to refer to different conventions for describing -which way the boat is pointing. -The following 3 pieces of information are needed to unambiguously define an angle measuring convention:

-
    -
  • What does 0° mean? If 0° is North, is it towards the North or away from the North?
  • -
  • Do the angle measurements increase in the clockwise or counter-clockwise direction?
  • -
  • What range should the angles be bounded to? This part is often unimportant if the angles are only used in -trigonometry functions.
  • -
-

Some common examples of angle measuring conventions which we use are:

-
    -
  • 0° means towards the East, angles increase in the counter-clockwise direction, and angles are bounded in -[-180°, 180°). This is effectively the main angle convention used in most math courses.
  • -
  • 0° means towards the North, angles increase in the clockwise direction, and angles are bounded in [0°, 360°). This -angle convention is more commonly used by navigators.
  • -
-

The specific angle conventions which we call Heading and Bearing can be ambiguous, and may be subject to change, -so they are deliberately omitted here. Refer to the applicable source code to determine what the angle conventions are.

-

True, Apparent, and Boat Wind

-
    -
  • True Wind is the wind vector (speed and direction) which you would measure while standing on land (or motionless at -sea with unchanging GPS co-ordinates). In sailbot code, this may be referred to as Global Wind. When people -refer to "the wind", they normally mean True Wind.
  • -
  • Boat Wind is the wind vector which you would measure while standing on a moving boat when the True Wind speed is 0. -This means that boat wind always blows straight onto the bow of the boat, and the magnitude of the boat wind is equal to -the speed of the boat.
  • -
  • Apparent Wind is the vector sum of the True Wind and the Boat Wind. This is the wind that you would measure while -standing on a moving boat more generally, even if there is non-zero wind. The apparent wind is also what our wind -sensors measure, and what our sails feel. In Sailbot code, Apparent Wind may be referred to as Measured Wind.
  • -
-

In the example below, suppose the wind is blowing from the North at 4 m/s, and suppose the boat is moving towards -the East at 3 m/s.

-
    -
  • The True Wind everywhere is blowing at 4 m/s from the North
  • -
  • The Boat Wind onboard the boat is blowing from the East at 3 m/s
  • -
  • The Apparent Wind onboard the boat is has a magnitude of \(\sqrt{3^2 + 4^2} = 5 \text{ m/s}\), -and is coming from a true bearing of \(\arctan{(\frac{3}{4})} = 36.9°\).
  • -
-

image

-

Tack

-

In the Types of Turn -page, we discussed how a Tack is a type of turn. Weirdly, the word "tack" actually has two -more distinct meanings in sailing. The word "Tack" can refer to:

-
    -
  • the type of turn, as covered before.
  • -
  • Starboard Tack vs Port Tack: The tack is basically the side of the boat which is further upwind. More thoroughly, -the tack is the opposite side to the sail. This means that boats change tack when the sail switches sides.
      -
    • In the diagram below, -the 3 boats on the left of the diagram are on Starboard Tack, and the 3 boats on the right side are on Port Tack.
    • -
    • The tack of a boat in Irons is undefined.
    • -
    • The boat in the diagram on a run is on Port Tack. If the boat continued straight but the sail switched sides into -the position shown by the dashed line, the boat would be on Starboard Tack.
    • -
    -
  • -
-

image

-
    -
  • Finally, the Tack can refer to particular region of the main sail. This is not important for software members.
  • -
-

Keywords on this Page

-
    -
  • Heading
  • -
  • Bearing
  • -
  • Track Made Good
  • -
  • Global Wind (aka True Wind)
  • -
  • Measured Wind (aka Apparent Wind)
  • -
  • Tack
  • -
- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/reference/sailing/overview/index.html b/pr-248/reference/sailing/overview/index.html deleted file mode 100644 index 45369b237..000000000 --- a/pr-248/reference/sailing/overview/index.html +++ /dev/null @@ -1,2154 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Overview - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -

Sailing Knowledge Section Overview

-

In order to make high-quality contributions to Sailbot's Software teams, it is extremely helpful to have some -understanding of sailing. This section introduces important parts of a sailboat, explains the 4 types of turns, -discusses upwind and downwind sailing, and covers some other helpful knowledge.

-

In this section, terms which are Bolded and Italicized are the most important terms to know. -These terms are listed at the bottom of each page. -Terms that are only Italicized are other helpful sailing terms. -Words that are bolded are meant to be emphasized, but are not necessarily considered important vocabulary.

- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/reference/sailing/points_of_sail/index.html b/pr-248/reference/sailing/points_of_sail/index.html deleted file mode 100644 index 2e21a2984..000000000 --- a/pr-248/reference/sailing/points_of_sail/index.html +++ /dev/null @@ -1,2292 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Points of Sail - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -

Points of Sail

-

In sailing, we sometimes talk about different angles that we can sail on with respect to the wind. -Ranges of angles which are close together have special names. These ranges are called points of sail. -The discussion below coveres the most important points of sail for software members to understand.

-

Notice how for higher points of sail (points of sail closer to straight into the wind), the sail is pulled tightly -in to the boat. If the boat is on a lower point of sail, the sails should be let further out of the boat. For any -point of sail, there is an optimum angle that the sail should be adjusted to. If the sails are adjusted too far in -or too far out, the boat will not go as fast as it could if the sails were adjusted correctly.

-

image

-

Irons

-

The range of angles where the boat is roughly pointing straight into the wind are called Irons, or the -No-Go Zone. -If the boat is pointing in these directions, the sails will be flapping regardless of how the sheets are adjusted. -When the sails are flapping, they are not catching the wind in a way that can propell the boat forwards. -When the boat looses propulsion, water stops flowing over the rudder, and the boat loses steering. -This is why we want our sailbots to avoid being stuck in irons.

-

Upwind Sailing

-

If we want to sail to a destination that is not on too high or low of an angle upwind or downwind from our starting -position, we can just point our boat in that direction, adjust our sails, and go there.

-

However, sometimes we want to sail to a destination that is straight upwind of our starting position. -To get there, we will need to do upwind sailing. -Since we can't point our boat directly into the wind, we need to sail on an angle on the edge of irons. -We will need to tack back and forth every now and then if we want to go directly upwind. -The point of sail on the edge of Irons is called Close Hauled.

-

Downwind Sailing

-

Raye also avoids sailing straight downwind. This means that to reach a goal downwind of the starting position, -we need to gybe back and forth in a zig-zag pattern. The point of sail straight -downwind is called a run, and the next point of sail higher than a run is called a broad reach.

-

image

-

Keywords on this Page

-
    -
  • Irons (aka No-Go Zone)
  • -
  • Upwind Sailing
  • -
  • Close Hauled
  • -
  • Downwind Sailing
  • -
- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/reference/sailing/turning/index.html b/pr-248/reference/sailing/turning/index.html deleted file mode 100644 index 236fdbca1..000000000 --- a/pr-248/reference/sailing/turning/index.html +++ /dev/null @@ -1,2394 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Types of Turns - UBCSailbot Software Team Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
- - - - - - - -
- -
- - - - -
-
- - - -
-
-
- - - - - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - -

Types of Turns

-

In sailing, there are 4 distinct types of turns. Read the descriptions below, and observe how they fit into the diagrams.

-

Note that any of these types of turn can be done in either the clockwise or counter-clockwise directions.

-

Classifying Types Of Turns Summary

-

The following flowchart summarizes how to distinguish between different types of turns. Note:

-
    -
  • to point higher means to steer your boat to point in a direction closer to straight into the wind
  • -
  • to point lower means to steer your boat to point in a direction closer towards to straight downwind
  • -
-
graph LR
-    B[Classify a Turn] --> C{Does the sail change<br/>sides during the turn?};
-    C --> |Yes| E{Which end of<br/>the boat is upwind<br/>during the turn?};
-    C --> |No| D{Does the<br/>boat point higher<br/>or lower at the end<br/>of the turn?};
-    D --> |Higher| F[Heading Up];
-    D --> |Lower| G[Bearing Off];
-    E --> |Bow| H[Tack];
-    E --> |Stern| I[Gybe];
-

The diagrams in this section show outlines of the hull of a boat and its main sail going through turns. -As is common in these types of diagrams, assume that the wind is blowing down from the top of the screen unless -there is an arrow that indicates otherwise.

-

image

-

Heading Up

-

When the boat makes any turn as follows, it is called Heading Up:

-
    -
  • At the end of the turn, the boat is pointing higher.
  • -
  • Throughout the turn, the sails stay on the same side of the boat. In other words, the sails do not cross between -the starboard and port sides.
  • -
-

Unlike some of the other turns listed here, heading up can be a large turn or a small course adjustment of just a few -degrees.

-

The image below shows a boat heading up. Notice how the sail stays on the starboard side of the boat.

-

image

-

Bearing Off

-

When the boat makes any turn as follows, it is called Bearing Off:

-
    -
  • At the end of the turn, the boat is pointing lower.
  • -
  • Throughout the turn, the sails stays on the same side of the boat (port or starboard).
  • -
-

Like heading up, bearing off can be a small course adjustment.

-

image

-

Tacking

-

When the boat makes any turn as follows, it is called a Tack or Tacking:

-
    -
  • The sails change sides.
  • -
  • Through the turn, the wind hits the bow of the boat before the stern. You can also say that the bow is upwind or -windward of the stern.
  • -
-

Notice how at some point throughout this turn, the boat will be pointing straight into the wind. -While the boat points nearly straight into the wind, the sails don't generate any forward propulsion. -This means that a tack must be a large (at least ~90°) turn all at once, so that the boat's momentum carries it through -the range of angles where it does not get propulsion.

-

image

-

Gybing

-

When the boat makes any turn as follows, it is called a Gybe or Gybing.

-
    -
  • The sails change sides.
  • -
  • Through the turn, the wind hits the stern of the boat before the bow. You can also say that the bow of the boat is -downwind or leeward of the stern.
  • -
-

When sailing on most angles relative to the wind, the sail is always blown to the downwind side of the boat. -However, sailing nearly straight downwind, both sides of the boat are equally "downwind" relative to eachother. -This means that the sail can be on either side of the boat.

-

The sail propells the boat throughout a gybe, so it is possible to conduct the turn more gradually than a tack. -However, because the sail can be on either side, the sails can switch sides in an uncontrolled way as the boat moves in -the waves. For this reason, Raye avoids sailing on angles close to straight downwind, and gybes by doing a quick ~60° -turn.

-

Note that "gybe" is the spelling used in Canadian and British english, whereas in American english it is spelled "Jibe"

-

image

-

Combinations of Turns

-

Of course, it is possible to do two or more of these types of turns in one continuous motion. -What two types of turns does the boat do in the image below?

-

image

-

Answer: In the turn shown by the first arrow, the sail stays on the port side of the boat while it steers to point further -downwind. This means that the first part of the maneuver is bearing off. In the next part of the maneuver, the sail -changes sides and the stern of the boat is upwind of the bow. This part of the maneuver is a gybe.

-

Keywords on this Page

-
    -
  • Higher (in relation to pointing)
  • -
  • Lower (in relation to pointing)
  • -
  • Heading Up
  • -
  • Bearing Off
  • -
  • Tack
  • -
  • Gybe (aka Jibe)
  • -
- - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pr-248/requirements.txt b/pr-248/requirements.txt deleted file mode 100644 index b8f560561..000000000 --- a/pr-248/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -cairosvg -mkdocs-git-revision-date-localized-plugin==1.* -mkdocs-material==9.* -mike -pillow diff --git a/pr-248/search/search_index.json b/pr-248/search/search_index.json deleted file mode 100644 index 13f1b8dca..000000000 --- a/pr-248/search/search_index.json +++ /dev/null @@ -1 +0,0 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-,:!=\\[\\]()\"/]+|(?!\\b)(?=[A-Z][a-z])|\\.(?!\\d)|&[lg]t;","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"UBCSailbot Software Team Docs","text":"

Welcome to the UBC Sailbot software team docs

Looking to get started with running the Sailbot codebase? Start by setting up the Sailbot Workspace:

Getting Started

"},{"location":"#what-information-is-on-this-website","title":"What information is on this website?","text":"

Information on our current project is contained on this website. In particular, information on each of our major software projects are provided in detail.

Current Project Overview

References to the software tools that we use are also provided on this website. This includes basic information on these tools, how we use these tools on UBC Sailbot, and external links to helpful references and tutorials.

Software Team References

"},{"location":"#who-is-this-website-for","title":"Who is this website for?","text":"

The docs site is primarily for the members on the UBC Sailbot software team. However, curious members of the public and/or those who are interested in contributing to our open source software would also benefit from this site.

"},{"location":"#prospective-members","title":"Prospective Members","text":"

Are you a member of the UBC community? Are you interested in what we do at UBC Sailbot? We are always looking for motivated students to help us tackle the challenge of autonomous sailing. Learn more below!

Software Team Posting

Apply to join UBC Sailbot

"},{"location":"current/overview/","title":"Current Project Overview","text":"

Our current project, Polaris, is an autonomous research vessel capable of collecting oceanic and atmospheric data. With our expertise in autonomous sailing, the goal is to monitor the health of our oceans while collaborating with stakeholders and researchers involved in climate science and oceanography.

The software team is responsible for designing, implementing, and testing the software system of our autonomous sailboats. We work on both low-level and high-level integration, from interfacing with sensors to planning sea routes with pathfinding algorithms.

"},{"location":"current/overview/#dataflow","title":"Dataflow","text":"

The software architecture for our next autonomous sailboat is split across two computers: the on board computer on board and the remote server off board. The following paragraphs will follow the flow of data between the software components (bolded) on each computer.

On the remote server, global pathfinding uses the A* pathfinding algorithm to create a sailing path, a list of global waypoints from the current position to destination. Global sailing paths are sent via the Remote Transceiver to the Local Transceiver on the on board computer.

On the on board computer, the CAN Transceiver receives GPS and wind data from their respective sensors. This raw data is filtered before being used in the other software components. Local Pathfinding uses GPS and wind data, as well as the global path and AIS data from the AIS Receiver, to create a local path, a list of local waypoints from the current position to the next global waypoint. The Controller uses wind data and the relative bearing to the local path to adjust the rudder and sails accordingly. The state of the boat and research data we collect is sent via the Local Transceiver to the Remote Transceiver on the remote server.

Back on the remote server, the Website presents the boat state and research data for monitoring and analysis purposes. The Remote Transceiver additionally includes manual overrides such as resetting the boat state and modifying the global path.

As for the communication mediums, the computers communicate via satellite, and components on the on board computer communicate through the Robot Operating System framework, or ROS for short.

For software development purposes, all software components will be able to run and communicate with each other locally. To accomplish this, we will:

  1. Create a development environment that has all software component dependencies: Sailbot Workspace.
  2. Develop accurate simulations of the environment and hardware: Simulator, Mock AIS, Mock Global Pathfinding.
  3. Add configuration options to select between real and simulated hardware as well as running remote server components remotely or locally.
"},{"location":"current/overview/#diagrams","title":"Diagrams","text":"

In these diagrams, the bubbles represent components of our software system, and the direction of arrows connecting the bubbles represent the flow of data between them. The color of the bubbles denote the sub-team leading their development:

  • Purple: Controls
  • Green: Network Systems
  • Blue: Pathfinding
  • Red: Website
  • White: Not a part of the Software Team's codebase

Components that are used in both the production and development environments are darker, while ones that are only used in one are lighter.

Interacting with the diagram

  • To switch between the production and development environment diagrams, hover over the image to make the toolbar visible and navigate with the arrows on the left side
"},{"location":"current/boat_simulator/overview/","title":"Overview","text":"

Source code

The source code for Boat Simulator can be found in the boat_simulator GitHub repository. Its README has been copied below.

"},{"location":"current/boat_simulator/overview/#ubc-sailbot-boat-simulator","title":"UBC Sailbot Boat Simulator","text":"

UBC Sailbot's boat simulator for the new project. This repository contains a ROS package boat_simulator. This README contains only setup and run instructions. Further information on the boat simulator can be found on the software team's docs website.

"},{"location":"current/boat_simulator/overview/#setup","title":"Setup","text":"

The boat simulator is meant to be ran inside the Sailbot Workspace development environment. Follow the setup instructions for the Sailbot Workspace here to get started and build all the necessary ROS packages.

"},{"location":"current/boat_simulator/overview/#run","title":"Run","text":"

The launch/ folder contains a ROS 2 launch file responsible for starting up the boat simulator. To run the boat simulator standalone, execute the launch file after building the boat_simulator package:

ros2 launch boat_simulator main_launch.py [OPTIONS]...\n

To see a list of options for simulator configuration, add the -s flag at the end of the above command.

"},{"location":"current/boat_simulator/overview/#test","title":"Test","text":"

Run the test task in the Sailbot Workspace. See here on how to run vscode tasks.

"},{"location":"current/controller/overview/","title":"Overview","text":"

Source code

The source code for Controller can be found in the controller GitHub repository. Its README has been copied below.

"},{"location":"current/controller/overview/#controller","title":"Controller","text":"

UBC Sailbot's controller for the new project. This repository contains a ROS package controller. This README contains only setup and run instructions. Further information on the controller can be found on the software team's docs website.

"},{"location":"current/controller/overview/#setup","title":"Setup","text":"

The controller is meant to be ran inside the Sailbot Workspace development environment. Follow the setup instructions for the Sailbot Workspace here to get started and build all the necessary ROS packages.

"},{"location":"current/controller/overview/#run","title":"Run","text":"

The launch/ folder contains a ROS 2 launch file responsible for starting up the controller. To run the controller standalone, execute the launch file after building the controller package:

ros2 launch controller main_launch.py [OPTIONS]...\n

To see a list of options for configuration, add the -s flag at the end of the above command.

"},{"location":"current/controller/overview/#test","title":"Test","text":"

Run the test task in the Sailbot Workspace. See here on how to run vscode tasks.

"},{"location":"current/custom_interfaces/overview/","title":"Overview","text":"

Source code

The source code for Custom Interfaces can be found in the custom_interfaces GitHub repository. Its README has been copied below.

"},{"location":"current/custom_interfaces/overview/#custom-interfaces","title":"Custom Interfaces","text":"

UBC Sailbot's custom interfaces ROS package. To add custom_interfaces to another ROS package, follow the instructions here.

The terminology that we use in this document are the following:

  • External Interface: An interface used to communicate data between nodes and ROS packages.
  • Internal Interface: An interface used to standardize conventions across external interfaces. Standards are documented in the .msg or .srv file associated with that interface.
"},{"location":"current/custom_interfaces/overview/#project-wide-interfaces","title":"Project-wide Interfaces","text":"

ROS messages and services used across many ROS packages in the project.

"},{"location":"current/custom_interfaces/overview/#project-wide-external-interfaces","title":"Project-wide External Interfaces","text":""},{"location":"current/custom_interfaces/overview/#project-wide-internal-interfaces","title":"Project-wide Internal Interfaces","text":"Interface Used In HelperAISShip AISShips HelperBattery Batteries HelperDimension HelperAISShip HelperGenericSensor GenericSensors HelperHeading DesiredHeading, GPS, HelperAISShip HelperLatLon GPS, HelperAISShip, Path HelperROT HelperAISShip HelperSpeed GPS, HelperAISShip, WindSensor"},{"location":"current/custom_interfaces/overview/#boat-simulator-interfaces","title":"Boat Simulator Interfaces","text":"

ROS messages and services used in our boat simulator.

"},{"location":"current/custom_interfaces/overview/#boat-simulator-external-interfaces","title":"Boat Simulator External Interfaces","text":"Topic Type Publisher Subscriber(s) mock_kinematics SimWorldState Simulator Physics Engine Simulator Visualizer"},{"location":"current/custom_interfaces/overview/#boat-simulator-actions","title":"Boat Simulator Actions","text":"Action Client Node Server Node SimRudderActuation Simulator Physics Engine Simulator Low Level Controller SimSailTrimTabActuation Simulator Physics Engine Simulator Low Level Controller"},{"location":"current/custom_interfaces/overview/#resources","title":"Resources","text":""},{"location":"current/custom_interfaces/overview/#common-interfaces","title":"Common Interfaces","text":"

The ROS2 common_interfaces repository defines a set of packages which contain common interface files. Since we are using the Humble version of ROS2, see the humble branch. These interfaces can be used in this repository or as a reference for ideas and best practices.

Package Possible Usage diagnostic_msgs Could be used for website sensors geometry_msgs Simulator, Local Pathfinding sensor_msgs Reference for CAN Transceiver std_msgs Reference std_srvs Reference visualization_msgs Reference

For more detail on the usefulness of each package, see this issue comment. If you are interested in creating your own custom message or service, see the ROS Humble documentation.

"},{"location":"current/docs/overview/","title":"Docs","text":"

UBCSailbot software team's documentation site. It is meant to be developed in Sailbot Workspace in conjunction with our other software, but doesn't have to be. There are instructions for both cases below.

"},{"location":"current/docs/overview/#setup","title":"Setup","text":""},{"location":"current/docs/overview/#setup-in-sailbot-workspace","title":"Setup in Sailbot Workspace","text":"
  1. Uncomment docker-compose.docs.yml in .devcontainer/devcontainer.json
  2. Rebuild the Dev Container

Refer to How to work with containerized applications for more details.

"},{"location":"current/docs/overview/#setup-by-itself","title":"Setup By Itself","text":"
  1. Clone repository

    git clone https://github.com/UBCSailbot/docs.git\n
  2. Manually install social plugin OS dependencies

  3. Install Python dependencies

    pip install --upgrade pip pip install -Ur docs/requirements.txt

    • Can do this in a Python virtual environment
"},{"location":"current/docs/overview/#run","title":"Run","text":""},{"location":"current/docs/overview/#run-in-sailbot-workspace","title":"Run in Sailbot Workspace","text":"

After setup, the Docs site should be running on port 8000.

Refer to How to work with containerized applications for more details.

"},{"location":"current/docs/overview/#run-by-itself-using-vs-code","title":"Run By Itself using VS Code","text":"
  1. CTRL+P to open Quick Open
  2. Run a launch configuration
    • \"debug Run Application\" runs mkdocs serve
    • \"debug Launch Application\" runs mkdocs serve and opens the application in a new Microsoft Edge window
"},{"location":"current/docs/overview/#run-by-itself-using-cli","title":"Run By Itself using CLI","text":"
mkdocs serve\n
"},{"location":"current/docs/overview/#update-dependencies","title":"Update Dependencies","text":"

This site is built using the latest versions of dependencies in docs/requirements.txt at the time of the most recent commit to the main branch. To see exactly how the site will look when deployed, ensure your local dependencies are up to date.

"},{"location":"current/docs/overview/#update-dependencies-in-sailbot-workspace","title":"Update Dependencies in Sailbot Workspace","text":"

Rebuild the Dev Container.

"},{"location":"current/docs/overview/#update-dependencies-by-itself","title":"Update Dependencies By Itself","text":"
pip install -Ur docs/requirements.txt\n
"},{"location":"current/local_pathfinding/overview/","title":"Overview","text":"

Source code

The source code for Local Pathfinding can be found in the local_pathfinding GitHub repository. Its README has been copied below.

"},{"location":"current/local_pathfinding/overview/#local-pathfinding","title":"Local Pathfinding","text":"

UBC Sailbot's local pathfinding ROS package

"},{"location":"current/local_pathfinding/overview/#run","title":"Run","text":"

Using main launch file: ros2 launch local_pathfinding main_launch.py

"},{"location":"current/local_pathfinding/overview/#launch-parameters","title":"Launch Parameters","text":"

Launch arguments are added to the run command in the format <name>:=<value>.

name description value log_level Logging level A severity level (case insensitive)"},{"location":"current/network_systems/overview/","title":"Overview","text":"

Source code

The source code for Network Systems can be found in the network_systems GitHub repository. Its README has been copied below.

"},{"location":"current/network_systems/overview/#network-systems","title":"Network Systems","text":"

This repository contains the source code for all of UBC Sailbot's Network Systems programs. It is made to work as part of Sailbot Workspace, and is not meant to be built as an independent project.

"},{"location":"current/network_systems/overview/#setup","title":"Setup","text":"

For comprehensive setup instructions, follow our setup guide.

"},{"location":"current/network_systems/overview/#building","title":"Building","text":"

Option A: With sailbot_workspace open, invoke the VSCode build or debug task.

Option B: Run /workspaces/sailbot_workspace/build.sh

"},{"location":"current/network_systems/overview/#running","title":"Running","text":""},{"location":"current/network_systems/overview/#ros-launch","title":"ROS Launch","text":"

Instructions found here.

For example:

ros2 launch network_systems main_launch.py\n

This is the best option if multiple modules need to be run at once. Launch configurations are found under the config folder. These configurations define which modules to enable/disable and what parameters to use.

"},{"location":"current/network_systems/overview/#ros-run","title":"ROS Run","text":"

If you just want to run a single module, then this is a direct and easy way to do it.

For example:

ros2 run network_systems example --ros-args -p enabled:=true\n
"},{"location":"current/network_systems/overview/#binary","title":"Binary","text":"

Not recommended as you cannot pass ROS parameters, so modules may not work by default. Binaries for each module found under projects can be found under /workspaces/sailbot_workspace/build/network_systems/projects/{module_name}/{module_name}.

For example:

/workspaces/sailbot_workspace/build/network_systems/projects/example/example\n
"},{"location":"current/network_systems/overview/#testing","title":"Testing","text":"

Unit tests specific to Network Systems is done using GoogleTest. Unit tests are defined per module. For example, under projects/example/test/.

"},{"location":"current/network_systems/overview/#run-all-tests","title":"Run All Tests","text":"

Option A: With sailbot_workspace open, invoke the VSCode test task.

Option B: Under the sailbot_workspace directory, run /workspaces/sailbot_workspace/test.sh

Both options will run all of UBC Sailbot's tests, including those from other projects. More often than not, this is unnecessary.

"},{"location":"current/network_systems/overview/#run-and-debug-specific-tests","title":"Run and Debug Specific Tests","text":"

This is the preferred way to run and debug tests. When you open a test source file like the example's, there will be green arrows next to each TEST_F macro. Clicking a double green arrow runs a test suite, while clicking single green arrow runs one unit test. Right clicking either arrow will open a prompt with a debug test option. When running a test via the debug option, we can set breakpoints and step through our code line by line to resolve issues.

This convenient testing frontend is thank's to the TestMate extension.

Warning: Large failing tests can crash VSCode. If this happens, either lower the size of the tests (ex. reduce the number of iterations) or run the test binary directly.

"},{"location":"current/network_systems/overview/#run-test-binaries","title":"Run Test Binaries","text":"

Test binaries for each module found under projects can be found under /workspaces/sailbot_workspace/build/network_systems/projects/{module_name}/test_{module_name}.

For example:

/workspaces/sailbot_workspace/build/network_systems/projects/example/test_example\n
"},{"location":"current/notebooks/overview/","title":"Overview","text":"

Source code

The source code for Notebooks can be found in the notebooks GitHub repository. Its README has been copied below.

"},{"location":"current/notebooks/overview/#notebooks","title":"Notebooks","text":"

UBC Sailbot's Jupyter notebooks for researching and exporing implementations.

"},{"location":"current/notebooks/overview/#standards","title":"Standards","text":"
  1. In addition to the dependencies installed in Sailbot Workspace, notebooks may have additional dependencies that are installed in the first code block
  2. Implementations in notebooks should be complete: do not import functions from other UBC Sailbot repositories
  3. Notebooks should be organized into directories named like the UBC Sailbot repositories they correspond to
"},{"location":"current/sailbot_workspace/deployment/","title":"Deployment","text":"

Source code

The source code for deployment can be found in the sailbot_workspace GitHub repository. Its README has been copied below.

"},{"location":"current/sailbot_workspace/deployment/#deployment","title":"Deployment","text":"

Deploying our software to our autonomous sailboat's main computer.

"},{"location":"current/sailbot_workspace/deployment/#scripts","title":"Scripts","text":""},{"location":"current/sailbot_workspace/deployment/#start_containersh","title":"start_container.sh","text":"

Runs the base image. A new container is created every time this is run. The default container name is sailbot_deployment_container. Container names are unique, so if you want to use multiple deployment containers (e.g., from different branches) you will have to update the variable CONTAINER_NAME in the script.

Usage:

  • Runs the base image used by the Dev Container by default: ./start_container.sh
  • Run a specific version of the base image by specifying its ID: ./start_container.sh <IMAGE_ID>
"},{"location":"current/sailbot_workspace/deployment/#setup_bootsh","title":"setup_boot.sh","text":"

Configures programs and scripts that need to run when the main computer boots. Only needs to be run once unless the script is updated. Does not need to be rerun if any scripts or programs it targets are updated, with the exception of renaming or moving the file.

Usage:

  • Must be run as root
  • sudo ./setup_boot.sh
"},{"location":"current/sailbot_workspace/deployment/#deployment-container-commands","title":"Deployment container commands","text":"
  • Exit out of a container: exit
  • Start an existing container: docker start -ia <container name>
  • Delete an existing container: docker rm <container name>
  • Find the container ID of a container: docker ps -a
"},{"location":"current/sailbot_workspace/deployment/#deploy-software","title":"Deploy software","text":"

These commands are run in the in the root directory of this repository

  1. Run the setup script: ./setup.sh
  2. Run the build script with the quick build flag: ./build.sh -q
"},{"location":"current/sailbot_workspace/deployment/#develop-software","title":"Develop software","text":"

The deployment container isn't intended for development, but if you discover a bug and want to quickly push a fix:

  1. Fix the issue in a terminal outside the deployment container
  2. Run the software to verify your fix in a terminal inside the deployment container
  3. Commit and push your fix in a terminal outside the deployment container
"},{"location":"current/sailbot_workspace/docker_images/","title":"Docker Images","text":"

A table detailing the Docker images used to create the Dev Container can be found below. Click on an image to learn more about its features and how to update it.

Image Parent Image Source Code Why it is Rebuilt Where it is Built pre-base Ubuntu 22.04 base-dev.Dockerfile To install ROS or OMPL Personal computer base pre-base base-dev.Dockerfile To install core dependencies Workflow dispatch local-base base base-dev.Dockerfile To install core dev dependencies Workflow dispatch dev local-base base-dev.Dockerfile To install dev dependencies Workflow dispatch Dev Container dev Dockerfile To configure the Dev Container VS Code docs mkdocs-material docs.Dockerfile To install and run docs site VS Code (optional) website javascript-node website.Dockerfile To install and run website VS Code (optional)"},{"location":"current/sailbot_workspace/how_to/","title":"How-To's","text":""},{"location":"current/sailbot_workspace/how_to/#run-vs-code-commands-tasks-and-launch-configurations","title":"Run VS Code commands, tasks, and launch configurations","text":"

MacOS keyboard shortcuts

For keyboard shortcuts on MacOS, substitute Ctrl with Cmd.

VS Code commands can be run in the Command Palette. Open the Command Palette from the View menu or with Ctrl+Shift+P.

Tasks can be run using the Tasks: Run Task VS Code command. Build tasks can be run with Ctrl+Shift+B.

Launch configurations can be run from the Run and Debug view.

You can also run VS Code commands, tasks, launch configurations, and much more by typing their prefixes into an empty Command Palette. Open an empty Command Palette with Ctrl+P or by clicking the box in the center of the title bar. See the list below for some prefixes and their functions. For prefixes that are words, you will have to append a space to them to bring up their functions.

  • Nothing: files
  • >: VS Code commands
  • task: tasks
  • debug: launch configurations
  • ?: list all prefixes and their functions
"},{"location":"current/sailbot_workspace/how_to/#work-with-containerized-applications","title":"Work with containerized applications","text":"

New in v1.1.0

We have containerized the following applications for a variety of reasons:

  • MongoDB database
  • Docs site
  • Website
"},{"location":"current/sailbot_workspace/how_to/#running-containerized-applications","title":"Running containerized applications","text":"

In the first section of dockerComposeFile of .devcontainer/devcontainer.json, there is a list of files: each file contains the configuration for one or more applications.

The ones that are commented out are not run. To run them:

  1. Uncomment the Docker Compose file(s) that the application(s) you desire to run are defined in
    • Programs that are defined in the uncommented Docker Compose files will be started and stopped with Sailbot Workspace
  2. Run the Dev Containers: Rebuild Container VS Code command to restart Sailbot Workspace

To stop running them:

  1. Comment out the corresponding Docker Compose file
  2. Stop the application's container: see Managing containerized applications
"},{"location":"current/sailbot_workspace/how_to/#viewing-mongodb-data","title":"Viewing MongoDB data","text":"

Connect the MongoDB VS Code extension to the running database: Create a Connection for Deployment

  • Use the default methods: \"Paste Connection String\" and \"Open from Overview Page\"
  • Our database's connection string is mongodb://localhost:27017
  • See the MongoDB VS Code extension docs for how to use it to navigate or explore the database
"},{"location":"current/sailbot_workspace/how_to/#opening-docs-or-website","title":"Opening Docs or Website","text":"

Docs runs on port 8000 and Website 3005. You can see them in your browser at localhost:<port>. To open them using VS Code:

  1. Run the Ports: Focus on Ports View VS Code command
  2. Open the site by hovering over its local address and clicking either \"Open in Browser\" or \"Preview in Editor\"
    • The local address of Docs is the line with a port of 8000
    • The local address of Website is the line with a port of 3005

Turn off auto saving

Changes made to their files are loaded when they are saved, so if Auto Save is on, turn it off so that the Docs/Website servers aren't continuously reloading. Auto Save is on by default in GitHub Codespaces

"},{"location":"current/sailbot_workspace/how_to/#managing-containerized-applications","title":"Managing containerized applications","text":"

Each application runs in a Docker container. Containers can be managed using Docker Desktop or CLI commands:

  • View Sailbot Workspace containers

    Docker Desktop CLI Commands
    1. Select \"Containers\" in the top right
    2. Expand \"sailbot_workspace_devcontainer\"
      • The \"Status\" column shows whether a container is running or not
    docker ps -a\n
    • Sailbot Workspace containers should be named something like sailbot_workspace_devcontainer-<application>-<number>
    • The STATUS column shows whether a container is running or not
  • View a container's logs, the output of the container (including errors that caused it to stop)

    Docker Desktop CLI Commands
    1. Click on a container
    2. Navigate to the \"Logs\" view if not already on it
    docker logs <container>\n
  • Start a container that is not running

    Docker Desktop CLI Commands
    1. Click start
    docker start <container>\n
  • Stop a container that is running

    Docker Desktop CLI Commands
    1. Click stop
    docker stop <container>\n
"},{"location":"current/sailbot_workspace/how_to/#manage-software-packages","title":"Manage software packages","text":"

Why can't I just install the dependencies myself in the command line interface with pip or apt?

Although this will temporarily work, installing apt and/or Python dependencies directly in sailbot workspace using the commandline interface will not persist between container instances. The dependencies will need to be manually installed every single time you create a new instance of sailbot workspace, which is not feasible when we start to use many dependencies at once.

Of course, one could also install dependencies inside the sailbot workspace Docker images to allow such dependencies to persist across container instances. However, putting dependencies inside package.xml distinguishes between what dependencies are needed for ROS packages and what dependencies are needed for infrastructure purposes.

"},{"location":"current/sailbot_workspace/how_to/#add-apt-or-python-dependencies-to-ros-packages","title":"Add apt or python dependencies to ROS packages","text":"

If running your ROS packages requires external dependencies from an apt repository or python package, one of the following tags should be added to the package.xml file in the root directory of the ROS package:

<depend>ROSDEP_KEY</depend>\n<build_depend>ROSDEP_KEY</build_depend>\n<build_export_depend>ROSDEP_KEY</build_export_depend>\n<exec_depend>ROSDEP_KEY</exec_depend>\n<test_depend>ROSDEP_KEY</test_depend>\n
  • Learn what each tag is used for here.

  • Replace ROSDEP_KEY with the rosdep key for the dependency, which can be found online.

    • Use the key associated with ubuntu since sailbot workspace uses Ubuntu, or debian which Ubuntu is based on
    • Do not include the square brackets in package.xml
    Apt Dependencies Python Dependencies
    • Rosdep keys for apt repositories can be found here
    • Rosdep keys for python packages can be found here
    • Since we use Python 3, look for the packages that start with python3- (python- is usually for Python 2)
  • If there isn't rosdep key for the dependency, you can add your own to custom-rosdep.yaml in the root directory of the ROS package

After completing these steps, run the setup task and the desired dependencies should be installed. ROS uses a dependency management utility, rosdep, to handle the installation of dependencies. In addition to runtime dependencies, rosdep also handles dependencies for build time, dependencies for testing, sharing dependencies between ROS packages, and more. See the ROS documentation on rosdep to learn more.

"},{"location":"current/sailbot_workspace/how_to/#add-dependencies-to-a-docker-image","title":"Add dependencies to a Docker image","text":"

There are a couple cases where you would want to add dependencies to a Docker image instead of ROS package:

  1. The dependency is not used to build/run/test a ROS package
  2. There is no apt or pip package for your dependency so you have to build from source

To verify your changes, you can add them to .devcontainer/Dockerfile then run the Dev Containers: Rebuild Container VS Code command. Once verified, migrate the changes to one of the upstream images: base, local-base, dev, or pre-base.

"},{"location":"current/sailbot_workspace/how_to/#enable-github-copilot-in-sailbot-workspace","title":"Enable GitHub Copilot in Sailbot Workspace","text":"

GitHub Copilot is an AI paired programming tool that can help you accelerate your development by providing suggestions for whole lines or entire functions inside your editor.1 To enable GitHub Copilot:

  1. Apply to GitHub Global Campus as a student to use GitHub Copilot and get other student benefits for free. It may take a few days for your student status to be verified. In the meantime, you can still continue with the next steps. However, you will need to use the GitHub Copilot free trial until your account is verified.

  2. Sign up for GitHub Copilot for your personal account. If it offers a free trial, then take it. You should see a page telling you that you can use GitHub Copilot for free (if you have a verified student account).

  3. Uncomment the github.copilot extension in .devcontainer/devcontainer.json and run the Dev Containers: Rebuild Container VS Code command

  4. Sign into your GitHub account in VS Code. The GitHub Copilot extension should automatically prompt you to sign into your account if you are not already.

    VS Code is not prompting me to sign into my account

    You may already be signed in into your GitHub account. You can check by clicking on the Accounts icon in the bottom-left corner in VS Code and verify that you see your GitHub account.

    If you do not see your account, you can get the sign in prompt by trying:

    • Reloading the VS Code window: Ctrl+Shift+P and select Developer: Reload Window
    • Rebuilding the devcontainer: Ctrl+Shift+P and select Dev Containers: Rebuild Container
    • If using a Mac, use Cmd instead of Ctrl
  5. If all the previous steps were done correctly, you should see the GitHub Copilot icon in the bottom-right corner of VS Code without any error messages. For more information on how to use Copilot and a tutorial, refer to:

    • The GitHub Copilot Getting Started Guide
    • Configuring GitHub Copilot in your Environment
"},{"location":"current/sailbot_workspace/how_to/#use-your-dotfiles","title":"Use your dotfiles","text":"

Dotfiles are configuration files for various programs.2

More about dotfiles
  • They are called dotfiles because their filenames start with a dot (.)
  • On Linux and MacOS, files and directories that begin with a dot are hidden by default
  • To list dotfiles using the ls command, specify the -a argument: ls -a

Dotfiles that are commonly modified include:

  • Bash: ~/.bashrc
  • Git: ~/.gitconfig
  • Vim: ~/.vimrc

To use your dotfiles:

  1. Ensure that the base, local-base, or dev image installs the programs that the dotfiles correspond to
  2. Copy the dotfiles to the .devcontainer/config/ directory. If a dotfile is located in a child directory, you will have to created it. For example, if a dotfile's path is ~/.config/ex_dotfile, you will need to copy it to .devcontainer/config/.config/ex_dotfile

    Special cases

    • ~/.gitconfig: there is no need copy your Git dotfile, as Dev Containers do this automatically
    • ~/.bashrc: don't copy your Bash dotfile, as it would override the one created in the dev image. Instead, add your bash configuration .aliases.bash or .functions.bash in the config directory, as these are sourced by the created Bash dotfile.
  3. Run the Dev Containers: Rebuild Container VS Code command

"},{"location":"current/sailbot_workspace/how_to/#run-rayes-software","title":"Run Raye's software","text":"

Raye was our previous project. Her software can be run in the raye branch:

  1. Switch to the raye branch: git switch raye
  2. Rebuild the Dev Container: run the Dev Containers: Rebuild Container VS Code command
  3. If you want to run Raye's local pathfinding visualizer, complete step 2 of the setup instructions

raye branch disclaimers

  1. Since raye (and Raye's codebase in general) is not in active development, it may not be 100% functional or contain all the features in main
  2. raye is more memory intensive than main because the parent image of its Dev Container is much larger; this may lead to worse performance
"},{"location":"current/sailbot_workspace/how_to/#build-rayes-ros-packages","title":"Build Raye's ROS packages","text":"

To build Raye's ROS packages, run the following commands:

roscd\ncatkin_make\n
"},{"location":"current/sailbot_workspace/how_to/#run-packages-from-different-workspaces","title":"Run packages from different workspaces","text":"

The raye branch has two ROS workspaces: one for Raye and one for the new project. To run ROS packages, you will have to source the overlay of the workspace that it is in:

New ProjectRaye
srcnew\n
srcraye\n

Then you can run launch files or package-specific executables in that workspace with:

New ProjectRaye

ros2 launch ... or ros2 run ..., respectively.

roslaunch ... or rosrun ..., respectively.

"},{"location":"current/sailbot_workspace/how_to/#rayes-known-issues","title":"Raye's known issues","text":"

Run commands for Raye packages are very slow

On non-Ubuntu-based Linux operating systems, Run commands for Raye packages may take a long time to start-up. This is because the system has trouble resolving the local hostname.

To resolve this bug, run the commands below in the Dev Container:

echo 'export ROS_HOSTNAME=localhost' >> ~/.bashrc\necho 'export ROS_MASTER_URI=http://localhost:11311' >> ~/.bashrc\n
  1. GitHub Copilot Quickstart Guide \u21a9

  2. Dotfiles \u2013 What is a Dotfile and How to Create it in Mac and Linux \u21a9

"},{"location":"current/sailbot_workspace/launch_files/","title":"ROS Launch Files in Sailbot Workspace","text":"

ROS 2 Launch files allow us to programatically start up and configure multiple ROS nodes.1 Within Sailbot Workspace, ROS launch files are used to start up our ROS packages with ease. Additionally, we take advantage of the hierarchical properties of launch files by defining a global entry point that invokes the launch files of all ROS packages in the system.

"},{"location":"current/sailbot_workspace/launch_files/#launch-file-architecture","title":"Launch File Architecture","text":"

There are two launch processes that we utilize: namely the Package Launch Process and the Global launch process.

"},{"location":"current/sailbot_workspace/launch_files/#the-package-launch-process","title":"The Package Launch Process","text":"

The package launch process is intended to start up a specific ROS package by directly using the package launch file. The process is as follows:

  1. The package launch file is invoked with the user passing arguments via the CLI and specifying a configuration file.
  2. Global argument declarations and environment variables are loaded into the launch process.
  3. Local arguments, specific to the package, are declared.
  4. Both global and local arguments are parsed based on the argument declarations and are set for use upon start up.
  5. The ROS nodes belonging to the package begin execution, utilizing the ROS parameters from the configuration file.
When launching individual packages, be aware of dependencies between ROS packages

Some packages rely on the data produced by other packages in the system. This may cause only partial functionality of the ROS node(s) that are running inside the launched package. Therefore, it may be necessary to launch multiple packages manually to get the desired functionality.

"},{"location":"current/sailbot_workspace/launch_files/#the-global-launch-process","title":"The Global Launch Process","text":"

The global launch process is intended to start up the entire system (both the development and production environments). This process invokes the package launch files for each ROS package used in the system through a global launch file. The process is as follows:

  1. The global launch file is invoked with the user passing arguments via the CLI and specifying a configuration file.
  2. Environment variables common to all ROS packages are declared. In addition, the global arguments common across all ROS packages are declared.
  3. For each package launch file:
    • The CLI arguments, global argument declarations, and environment variables are passed into the package launch file.
    • Local arguments, specific to the package, are declared. Both the global and local arguments are parsed based on the argument declarations and are set for use upon start up.
    • The ROS nodes belonging to the package begin execution, utilizing the ROS parameters from the configuration file.
"},{"location":"current/sailbot_workspace/launch_files/#invoking-launch-files","title":"Invoking Launch Files","text":"Stopping the execution of a launch file

Entering Ctrl+C in the terminal where the launch file was invoked will stop all associated ROS packages from running.

Use Cmd+C for Mac OS

"},{"location":"current/sailbot_workspace/launch_files/#package-launch","title":"Package Launch","text":"

At the bare minimum, the following packages need to be built with the Build or Build All VS Code task before launching:

  • custom_interfaces
  • The package you want to launch

Packages only need to be rebuilt either when the workspace is first set up, or if any changes are made to the ROS package. Once built, the package launch file can be invoked either in the CLI or using a VS Code command:

CLI VS Code

Either the package and launch file name, or the path to the launch file can be used:

  • Method 1: ros2 launch <package> <launch file>. This method can only be used when a launch file is part of a built ROS package.
  • Method 2: ros2 launch <path to launch file>. This method can be used regardless if a launch file is in a ROS package or not.

Launch via CLI Examples

Let's launch local pathfinding using both CLI methods:

Method 1

ros2 launch local_pathfinding main_launch.py\n

Method 2

ros2 launch $ROS_WORKSPACE/src/local_pathfinding/launch/main_launch.py\n

Run the following VS Code command from the Run and Debug tab: ROS: Launch (workspace)

There will be a prompt to select which launch file to run. Select the desired launch file.

"},{"location":"current/sailbot_workspace/launch_files/#global-launch","title":"Global Launch","text":"

Before running the system, be sure to run the Build All VS Code task to build all ROS packages. If the ROS launch debug configuration is being used, then this step is not necessary as the Build All task is ran automatically before launch.

CLI VS Code

Run the entire system with the following CLI command:

ros2 launch $ROS_WORKSPACE/src/global_launch/main_launch.py\n

Run the following VS Code command from the Run and Debug tab: ROS: Launch (workspace)

There will be a prompt to select which launch file to run. Select the desired launch file.

Remember to that you need to potentially reload the window if the nodes are not being detected by VS Code. This usually happens when somebody build for the first time. Also, note that the global launch file is not part of a ROS package, so the path to the global launch file always must be provided. This is not always the case when a launch file is contained within a ROS package.

"},{"location":"current/sailbot_workspace/launch_files/#using-cli-arguments","title":"Using CLI Arguments","text":"

Invoking the launch files as is will provide the system with the default CLI arguments. As an example, the following command will launch local pathfinding while setting the log level to \"debug\":

ros2 launch local_pathfinding main_launch.py log_level:=debug\n

It can also be ran with the VS Code command named ROS: Launch.

Passing arguments takes the form of <arg name>:=<arg value>. To list the arguments that a launch file takes, simply add the -s flag at the end of the launch command.

Example using the -s flag in a launch command

Let's add the -s flag after the global launch command to see the list of arguments:

ros2 launch $ROS_WORKSPACE/src/global_launch/main_launch.py -s\n

The following output is observed in the terminal (as of September 2023):

Arguments (pass arguments as '<name>:=<value>'):\n\n'config':\n    Path to ROS parameter config file. Controls ROS parameters passed into ROS nodes\n    (default: '/workspaces/sailbot_workspace/src/global_launch/config/globals.yaml')\n\n'log_level':\n    Logging severity level. A logger will only process log messages with severity levels at or higher than the\n    specified severity. Valid choices are: ['debug', 'info', 'warn', 'error', 'fatal']\n    (default: 'info')\n\n'mode':\n    System mode. Decides whether the system is ran with development or production interfaces. Valid choices are:\n    ['production', 'development']\n    (default: 'development')\n
Example using multiple CLI arguments
ros2 launch local_pathfinding main_launch.py log_level:=debug mode:=production\n
Example passing local launch arguments to the global launch file

As long as an argument is valid inside one of the package launch files, it may be passed to the global launch file without generating any errors. This is valid even though the argument doesn't show up in the argument list for the global launch file. For example, the following will run:

ros2 launch $ROS_WORKSPACE/src/global_launch/main_launch.py enable_sim_multithreading:=true\n

Compare the argument list between the global launch file and the package launch file for the boat_simulator package. It will be observed that the argument enable_sim_multithreading shows up in the boat_simulator package argument list, but not for the global launch file.

"},{"location":"current/sailbot_workspace/launch_files/#ros-parameter-config-file","title":"ROS Parameter Config File","text":"

All launch files in Sailbot Workspace accept a configuration file, which controls the ROS parameters that the ROS nodes in the system have access to. This makes our system highly configurable and customizable during development and testing. See more about ROS parameters.

  1. ROS Launch File Documentation \u21a9

"},{"location":"current/sailbot_workspace/overview/","title":"Overview","text":"

Source code

The source code for Sailbot Workspace can be found in the sailbot_workspace GitHub repository. Its README has been copied below.

"},{"location":"current/sailbot_workspace/overview/#sailbot-workspace","title":"Sailbot Workspace","text":"

This repository will get you set up to develop UBCSailbot's software on VS Code. It is based on athackst's vscode_ros2_workspace.

"},{"location":"current/sailbot_workspace/overview/#features","title":"Features","text":"

An overview of Sailbot Workspace's features can be found below. See our docs site for how to use these features.

"},{"location":"current/sailbot_workspace/overview/#style","title":"Style","text":"

C++ and Python linters and formatters are integrated into Sailbot Workspace:

  • ament_flake8
  • ament_lint_cmake
  • ament_xmllint
  • black
  • clang-tidy
  • isort

The ament linters are configured to be consistent with the ROS style guide.

"},{"location":"current/sailbot_workspace/overview/#dev-container","title":"Dev Container","text":"

Dev Containers enable us to use a Docker container as a fully-featured development environment containing all our configuration and dependencies. Our Dev Container configuration can be found in .devcontainer/.

"},{"location":"current/sailbot_workspace/overview/#multi-root-workspace","title":"Multi-Root Workspace","text":"

Workspaces are VS Code instances that contain one or more folders. Our workspace configuration file can be found at sailbot.code-workspace.

Our software spans many repositories: software team repositories. Multi-root workspaces make it easy to work with multiple repositories at the same time. Our roots are defined in the folders section of our workspace file.

"},{"location":"current/sailbot_workspace/overview/#debugging","title":"Debugging","text":"

Launch configurations have been created to debug our software. They are defined in the launch section of our workspace file.

"},{"location":"current/sailbot_workspace/overview/#tasks","title":"Tasks","text":"

Tasks provide an alternative to memorizing the multitude of CLI commands we use to setup, build, lint, test, and run our software. They are defined in tasks section of our workspace file.

"},{"location":"current/sailbot_workspace/overview/#continuous-integration","title":"Continuous Integration","text":"

Actions were used to build our Docker containers and lint and test our code the same way it is done locally in Sailbot Workspace on GitHub. We use a reusable workflow to create a single source of truth for our tests across all our repositories. Our CI can be found in .github/workflows/.

"},{"location":"current/sailbot_workspace/overview/#customization","title":"Customization","text":"

This repository supports user-specific configuration files. To set this up, see How to use your dotfiles.

"},{"location":"current/sailbot_workspace/overview/#run-rayes-software","title":"Run Raye's Software","text":"

Raye was our previous project. Her software can be run in the raye branch following the instructions in How to run Raye's software. The initial differences between the main and raye branches are summarized in this PR.

"},{"location":"current/sailbot_workspace/overview/#documentation","title":"Documentation","text":"

Further documentation, including setup and run instructions, can be found on our Docs website.

"},{"location":"current/sailbot_workspace/parameters/","title":"Parameters","text":"

Source code

The README for ROS parameterization can be found in the sailbot_workspace GitHub repository. Its README has been copied below.

"},{"location":"current/sailbot_workspace/parameters/#sailbot-ros-parameter-configuration","title":"Sailbot ROS Parameter Configuration","text":"

The description of each parameter contained in globals.yaml are described in this README. Descriptions of parameters for each node are included. These parameters can be changed dynamically as well via the command line interface. To learn more, see the ROS 2 documentation on ROS 2 Parameters.

Each parameter is specified in the following format:

  • Description: The description of the parameter.
  • Datatype: The datatype. If it happens to be an array, the datatype of the elements should be specified and the length of the array.
  • Range/Acceptable Values: Ranges of integers and floating point values are specified with interval notation. Namely, [] denotes inclusive boundaries, while () denotes non-inclusive boundaries. For strings, the acceptable values are listed.

Additional information may be included when necessary.

[!IMPORTANT] This document should be updated when any changes occur to the ROS parameters specified in globals.yaml.

"},{"location":"current/sailbot_workspace/parameters/#global-parameters","title":"Global Parameters","text":"

ROS parameters common across all ROS nodes in the network.

pub_period_sec

  • Description: The period at which the publishers publish.
  • Datatype: double
  • Range: (0.0, MAX_DOUBLE)
"},{"location":"current/sailbot_workspace/parameters/#local-pathfinding-parameters","title":"Local Pathfinding Parameters","text":"

ROS parameters specific to the nodes in the local_pathfinding package.

"},{"location":"current/sailbot_workspace/parameters/#mgp_main","title":"mgp_main","text":"

global_path_filepath

  • Description: The absolute filepath to a global path csv file.
  • Datatype: string
  • Acceptable Values: Any valid filepath to a properly formatted csv file.

interval_spacing

  • Description: The upper bound on spacing between each point in the global path in km.
  • Datatype: double
  • Range: (0.0, MAX_DOUBLE)

write

  • Description: Whether or not to write a generated global path to a new csv file.
  • Datatype: boolean
  • Acceptable Values: true, false

gps_threshold

  • Description: A new path will be generated if the GPS position changed by more thangps_threshold*interval_spacing.
  • Datatype: double
  • Acceptable Values: (1.0, MAX_DOUBLE)

force

  • Description: Force the mock global path callback to update the global path when set to true.
  • Datatype: boolean
  • Acceptable Values: true, false
"},{"location":"current/sailbot_workspace/parameters/#navigate_main","title":"navigate_main","text":"

path_planner

  • Description: The path planner to use. Planners are from OMPL Library.
  • Datatype: string
  • Acceptable Values: \"bitstar\", \"bfmtstar\", \"fmtstar\", \"informedrrtstar\", \"lazylbtrrt\", \"lazyprmstar\", \"lbtrrt\", \"prmstar\", \"rrtconnect\", \"rrtsharp\", \"rrtstar\", \"rrtxstatic\", \"sorrtstar\"
"},{"location":"current/sailbot_workspace/parameters/#boat-simulator-parameters","title":"Boat Simulator Parameters","text":"

ROS parameters specific to the nodes in the boat simulator.

"},{"location":"current/sailbot_workspace/parameters/#low_level_control_node","title":"low_level_control_node","text":"

info_log_throttle_period_sec

  • Description: Limits the info logs to avoid overwhelming the terminal.
  • Datatype: double
  • Range: (0.0, MAX_DOUBLE)

logging_throttle_period_sec

  • Description: Controls the message logging throttle period.
  • Datatype: double
  • Range: (0.0, MAX_DOUBLE)

qos_depth

  • Description: The maximum number of subscription messages to queue for further processing.
  • Datatype: int
  • Range: [1, MAX_INT)

rudder.actuation_execution_period_sec

  • Description: The period at which the main loop in the rudder action server executes in seconds.
  • Datatype: double
  • Range: (0.0, MAX_DOUBLE)

rudder.disable_actuation

  • Description: Controls whether or not rudder actuation is disabled. If true, the rudder angle is fixed to some value. Otherwise, the PID mechanism is used to control the rudder angle.
  • Datatype: boolean
  • Acceptable Values: true, false

rudder.fixed_angle_deg

  • Description: The angle to fix the rudder in degrees. Only used if rudder.disable_actuation is true.
  • Datatype: double
  • Range: [-45.0, 45.0]

rudder.pid.buffer_size

  • Description: The buffer size of PID that stores previously computed errors over time.
  • Datatype: int
  • Range: [1, MAX_INT)

rudder.pid.kd

  • Description: The PID Derivative constant for the rudder. Only used if rudder.disable_actuation is false.
  • Datatype: double
  • Range: [0.0, MAX_DOUBLE)

rudder.pid.ki

  • Description: The PID Integral constant for the rudder. Only used if rudder.disable_actuation is false.
  • Datatype: double
  • Range: [0.0, MAX_DOUBLE)

rudder.pid.kp

  • Description: The PID Proportionality constant for the rudder. Only used if rudder.disable_actuation is false.
  • Datatype: double
  • Range: [0.0, MAX_DOUBLE)

wingsail.actuation_execution_period_sec

  • Description: The period at which the main loop in the sail action server executes in seconds.
  • Datatype: double
  • Range: (0.0, MAX_DOUBLE)

wingsail.actuation_speed_deg_per_sec

  • Description: The speed at which the wingsail trim tab actuates in degrees per second.
  • Datatype: double
  • Range: (0.0, MAX_DOUBLE)

wingsail.disable_actuation

  • Description: Controls whether or not wingsail trim tab actuation is disabled. If true, the trim tab is fixed to some value. Otherwise, the trim tab angle is determined by the wingsail controller.
  • Datatype: boolean
  • Acceptable Values: true, false

wingsail.fixed_angle_degree

  • Description: Fixed the wingsail trim tab to some angle in degrees. Only used if wingsail.disable_actuation is true.
  • Datatype: double
  • Range: [-180.0, 180.0)
"},{"location":"current/sailbot_workspace/parameters/#physics_engine_node","title":"physics_engine_node","text":"

action_send_goal_timeout_sec

  • Description: How long the action clients wait for the action server to respond to a request before timing out in seconds.
  • Datatype: double
  • Range: (0.0, MAX_DOUBLE)

info_log_throttle_period_sec

  • Description: Limits the info logs to avoid overwhelming the terminal.
  • Datatype: double
  • Range: (0.0, MAX_DOUBLE)

logging_throttle_period_sec

  • Description: Controls the message logging throttle period.
  • Datatype: double
  • Range: (0.0, MAX_DOUBLE)

qos_depth

  • Description: The maximum number of subscription messages to queue for further processing.
  • Datatype: int
  • Range: [1, MAX_INT)

rudder.actuation_request_period_sec

  • Description: How often the rudder action client requests a rudder actuation in seconds.
  • Datatype: double
  • Range: (0.0, MAX_DOUBLE)

wind_sensor.constant_params.value

  • Description: Specifies the constant vector returned by the constant generator that represents the wind velocity in kmph. Namely, the same value is fixed in the wind sensors. The value is an array containing the x and y components of the velocity. Only used if wind_sensor.generator_type is constant.
  • Datatype: double array, length 2
  • Range: (MIN_DOUBLE, MAX_DOUBLE)

wind_sensor.gaussian_params.corr_xy

  • Description: The correlation coefficient between x and y components of the wind velocity. Only used if wind_sensor.generator_type is gaussian.
  • Datatype: double
  • Range: [-1.0, 1.0]

wind_sensor.gaussian_params.mean

  • Description: The mean wind velocity parameter in kmph for the gaussian generator. The mean is an array containing the x and y components of the velocity. Only used if wind_sensor.generator_type is gaussian.
  • Datatype: double array, length 2
  • Range: (MIN_DOUBLE, MAX_DOUBLE)

wind_sensor.gaussian_params.std_dev

  • Description: The standard deviation parameters in kmph for the gaussian generator. There are two standard deviations specified within an array: one for the x component, and one for the y component. Only used if wind_sensor.generator_type is gaussian.
  • Datatype: double array, length 2
  • Range: (0.0, MAX_DOUBLE)
    • If a standard deviation of zero is desired, then consider using the constant generator instead.

wind_sensor.generator_type

  • Description: Determines the type of random number generator that will be used to generate wind sensor data.
  • Datatype: string
  • Acceptable Values: gaussian, constant

wingsail.actuation_request_period_sec

  • Description: How often the sail action server requests a wingsail actuation.
  • Datatype: double
  • Range: (0.0, MAX_DOUBLE)
"},{"location":"current/sailbot_workspace/parameters/#data_collection_node","title":"data_collection_node","text":"

file_name

  • Description: The name of the file in which the data is saved, excluding the file extension.
  • Datatype: string
  • Acceptable Values: Any valid file name.

qos_depth

  • Description: The maximum number of subscription messages to queue for further processing.
  • Datatype: int
  • Range: [1, MAX_INT)

topics

  • Description: Specifies the topics to subscribe to. It should adhere to the format ['topic_name_1', 'topic_type_1', ...].
  • Datatype: string array with an even length
  • Acceptable Values: Each pair within the array must consist of a valid topic name as the first string and the corresponding correct type as the second string.

bag

  • Description: Determines whether to save recorded data as a ROS bag.
  • Datatype: boolean
  • Acceptable Values: true, false

json

  • Description: Determines whether to save recorded data as a JSON file.
  • Datatype: boolean
  • Acceptable Values: true, false

write_period_sec

  • Description: The interval (in seconds) for writing queued data to the JSON file.
  • Datatype: double
  • Range: (0.0, MAX_DOUBLE)
"},{"location":"current/sailbot_workspace/setup/","title":"Setup Instructions","text":"

Sailbot Workspace can be run on Windows, Linux, or macOS, but is the easiest to set up and performs the best on Ubuntu and its derivatives. The workspace may not perform well on Windows computers with 8GB of memory or less; in this case, please check out our recommendations in the Performance Issues section.

"},{"location":"current/sailbot_workspace/setup/#1-setup-prerequisites","title":"1. Setup prerequisites","text":""},{"location":"current/sailbot_workspace/setup/#docker","title":"Docker","text":"

Docker is a platform that uses OS-level virtualization1 to develop, ship, and run applications.2 We use it to separate our applications from our infrastructure2 so that we can update and version control our infrastructure for every use case (software members, CI, deployment) in one place: this repository.

Docker Engine is a software used to run Docker. However, it can only be installed on Linux. Docker Desktop is a software used to run Docker in a VM,3 allowing it to be installed on Windows and macOS in addition to Linux.

Windows macOS Linux
  1. Set up prerequisites, WSL and Ubuntu:

    1. In PowerShell, run wsl --install Ubuntu, then exit, wsl --update, and wsl --set-default Ubuntu

      Ubuntu is already installed?

      If Ubuntu is already installed, check that it is the right WSL version:

      1. Check the WSL versions of Linux distributions with wsl -l -v
      2. If Ubuntu's VERSION is 1, upgrade it to WSL 2 with wsl --set-version Ubuntu 2
    2. Open the Ubuntu app to set up or verify its configuration:

      1. If you are opening Ubuntu for the first time, a setup process will run; follow the prompts to finish setting it up
      2. Run whoami to verify that it returns your Ubuntu username

        whoami returns root

        If whoami returns root:

        1. Create a non-root user with sudo privileges
        2. Change the default Ubuntu user to this newly-created user: run ubuntu config --default-user <username> in PowerShell, replacing <username> with the name of the newly-created user
        3. Run whoami after closing and reopening Ubuntu, verifying that it returns your Ubuntu username
  2. Install Docker Desktop with the WSL 2 backend

    Docker Desktop - Unexpected WSL Error

    If the above error shows when trying to start Docker Desktop on your laptop:

    1. For windows users navigate to C:\\Users\\user_name and delete the .Docker folder
    2. Restart Docker Desktop
    Docker Desktop can't start up and WSL hangs when restarting

    If Ubuntu can't start up and WSL hangs when restarting:

    1. Open command prompt as administrator and run the command netsh winsock reset
    2. Uninstall and reinstall Docker Desktop
    3. Restart your computer

    More potential solutions can be found here: Link

Install Docker Desktop for your computer's CPU.

  1. Install Docker Engine
    • As of February 2023, Sailbot Workspace (more specifically its use of VS Code Dev Containers) isn't compatible with Docker Desktop for Linux; if you have Docker Desktop installed, uninstall it and install Docker Engine instead.
  2. Manage Docker as a non-root user
  3. Configure Docker to start on boot
"},{"location":"current/sailbot_workspace/setup/#vs-code","title":"VS Code","text":"

Visual Studio Code is a powerful and customizable code editor for Windows, Linux, and macOS. We strongly recommend that you use this editor to develop our software so that you can use all the features of Sailbot Workspace.

  1. Install VS Code
  2. Install the Remote Development Extension Pack
"},{"location":"current/sailbot_workspace/setup/#git","title":"Git","text":"

Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency.4

  1. Check if Git is installed with git --version (on Windows, run command in PowerShell)
    • If not installed, download and install it from Git Downloads
  2. Configure your name and email: Git config file setup (on Windows, run commands in Ubuntu)
  3. Login to GitHub

    Windows macOS / Linux
    1. Run the git config command for your Git version in Git Credential Manager setup (run command in Ubuntu)

      Which Git to check

      Git is installed seperately in Windows and Ubuntu, so they could be at different versions. We want to check the version of Git on Windows, not Ubuntu: run git --version in PowerShell and not Ubuntu. However, the git config command itself is run in Ubuntu.

    1. Install the GitHub CLI: Installation
    2. Run gh auth login and select the first option for all choices
  4. Verify that you have successfully logged in to GitHub by cloning a private GitHub repository (run command in Ubuntu)

    1. If you are a part of the UBCSailbot Software GitHub team, you shouldn't see any errors running git clone https://github.com/UBCSailbot/raye-ais.git
    2. You can delete this repository with rm -rf raye-ais
"},{"location":"current/sailbot_workspace/setup/#2-setup-x11-forwarding","title":"2. Setup X11 forwarding","text":"

X11 forwarding is a mechanism that enables Sailbot Workspace to run GUI applications.

You can skip this step since we currently aren't running any GUI applications

Setup instructions for X11 forwarding
  1. Ensure that the versions of VS Code and its Dev Containers extension support X11 forwarding:
    1. VS Code version >= 1.75
    2. Dev Containers version >= 0.275.1
  2. Verify that echo $DISPLAY returns something like :0

    echo $DISPLAY doesn't return anything

    If echo $DISPLAY doesn't return anything, set it to :0 on shell initialization:

    1. Find out what shell you are using with echo $SHELL
      1. Most Linux distributions use Bash by default, whose rc file path is ~/.bashrc
      2. macOS uses Zsh by default, whose rc file path is: ~/.zshrc
    2. Run echo 'export DISPLAY=:0' >> <rc file path>, replacing <rc file path> with the path to your shell's rc file
    3. Run echo $DISPLAY after closing and reopening your terminal, verifying it returns something like :0
  3. Install a X11 server

    Windows macOS Linux

    WSL includes a X11 server.

    1. Set up XQuartz following this guide
    2. Copy the default xinitrc to your home directory: cp /opt/X11/etc/X11/xinit/xinitrc ~/.xinitrc
    3. Add xhost +localhost to ~/.xinitrc after its first line
    General Arch Linux

    As of February 2023, almost all Linux distributions include a X11 server, Xorg. This may change in the future as Wayland matures.

    1. Install xhost: sudo pacman -S xorg-xhost
    2. Copy the default xinitrc to your home directory: cp /etc/X11/xinit/xinitrc ~/.xinitrc
    3. Add xhost +local:docker to ~/.xinitrc after its first line
  4. Verify that X11 forwarding works:

    1. Install x11-apps

      Windows macOS Linux

      In Ubuntu, sudo apt install x11-apps.

      XQuartz includes x11-apps. Ensure that XQuartz is running.

      Install x11-apps using your desired package manager.

    2. Verify that running xcalc opens a calculator and that you can use it

"},{"location":"current/sailbot_workspace/setup/#3-clone-sailbot-workspace","title":"3. Clone Sailbot Workspace","text":"

Where to clone on Windows

Run the command below in the Ubuntu app to clone it in the Ubuntu file system, otherwise sailbot workspace will not work. Windows has a native file system as well as file systems for each WSL distribution.

git clone https://github.com/UBCSailbot/sailbot_workspace.git\n
"},{"location":"current/sailbot_workspace/setup/#4-open-sailbot-workspace-in-vs-code","title":"4. Open Sailbot Workspace in VS Code","text":"
  1. Install code command in PATH

    Windows macOS Linux

    The code command is installed by default.

    See launching from the command line.

    The code command is installed by default.

  2. Open the sailbot_workspace/ directory in VS Code: run code <relative path to sailbot workspace>

    • For example, if you just cloned the repository, the command would be code sailbot_workspace
"},{"location":"current/sailbot_workspace/setup/#5-open-the-workspace-file","title":"5. Open the workspace file","text":"

Click the popup to Open Workspace. If there isn't a popup:

  1. Open the file sailbot.code-workspace in VS Code
  2. Click Open Workspace
"},{"location":"current/sailbot_workspace/setup/#6-open-sailbot-workspace-in-a-dev-container","title":"6. Open Sailbot Workspace in a Dev Container","text":"
  1. Ensure that Docker is running
  2. Click the popup to Reopen in Container. If there isn't a popup, run the Dev Containers: Reopen in Container VS Code command
"},{"location":"current/sailbot_workspace/setup/#7-run-the-setup-task","title":"7. Run the setup task","text":"

The setup task clones the repositories defined in src/polaris.repos and updates dependencies of the ROS packages. If you don't know how to run a VS Code task, see How to run VS Code commands, tasks, and launch configurations.

Can't see the setup task

If you can't see the setup task, run the Developer: Reload Window VS Code command. This may occur when the workspace file is opened for the first time.

"},{"location":"current/sailbot_workspace/setup/#8-run-the-build-all-task","title":"8. Run the Build All task","text":"

The Build All task builds all the ROS packages.

"},{"location":"current/sailbot_workspace/setup/#9-reload-the-vs-code-terminals-and-window","title":"9. Reload the VS Code terminals and window","text":"

Delete all open terminals and run the Developer: Reload Window VS Code command to detect the files that were generated from building.

"},{"location":"current/sailbot_workspace/setup/#10-start-the-system","title":"10. Start the system","text":"

Run the entire system to verify everything is working using the following command in the VS Code terminal:

ros2 launch $ROS_WORKSPACE/src/global_launch/main_launch.py\n

Use Ctrl+C in the terminal to stop the system.

"},{"location":"current/sailbot_workspace/setup/#setup-sailbot-workspace-in-a-github-codespace","title":"Setup Sailbot Workspace in a GitHub Codespace","text":"

A codespace is a development environment that's hosted in the cloud.5 Since Sailbot Workspace is resource intensive, it has high hardware requirements and power consumption, which aren't ideal for development on laptops. GitHub Codespaces provide a seamless experience to work on repositories off-device, especially if they specify a Dev Container like Sailbot Workspace. Codespaces can run in VS Code or even in a browser for times when you aren't on your programming computer.

  1. Create a GitHub Codespace following the steps in the relevant GitHub Docs page: create a codespace for a repository. A couple things to note:
    • For the best Sailbot Workspace development experience, select the high-spec machine available
    • There are usage limits if you don't want to pay: monthly included storage and core hours for personal accounts
      • Upgrade to a Pro account for increased usage limits (this is free for students): apply to GitHub Global Campus as a student
      • Stop your codespace as soon as you are done using it: stopping a codespace
      • Delete codespaces that you do not plan to use anymore: deleting a codespace
  2. Follow the local setup instructions starting from 5. Open the workspace file

Once you have a codespace set up:

  • Open it by following the steps in the relevant GitHub Docs page: reopening a codespace
  • Close it by running the Codespaces: Stop Current Codespace VS Code command

Known limitations of running Sailbot Workspace in a GitHub Codespace

  • Does not support X11 forwarding to run GUI applications
  • High-spec machines not available: as of March 2023, the highest-spec machine that is publically available has a 4-core CPU and 8GB of RAM
  1. Wikipedia Docker page \u21a9

  2. Get Docker \u21a9\u21a9

  3. What is the difference between Docker Desktop for Linux and Docker Engine \u21a9

  4. Git SCM \u21a9

  5. GitHub Codespaces overview \u21a9

"},{"location":"current/sailbot_workspace/workflow/","title":"Development Workflow","text":""},{"location":"current/sailbot_workspace/workflow/#1-open-sailbot-workspace","title":"1. Open Sailbot Workspace","text":"

Once you have set up Sailbot Workspace, you can open it by opening a new VS Code window and selecting:

File > Open Recent > /workspaces/sailbot_workspace/.devcontainer/config/sailbot_workspace (Workspace) [Dev Container: Sailbot Workspace]\n
Another way to open Sailbot Workspace on Windows
  1. Pin VS Code to the taskbar
  2. Right-click VS Code in the taskbar and pin sailbot_workspace (Workspace) [Dev Container]

Then you can open Sailbot Workspace by selecting it from the \"Pinned\" section of the VS Code taskbar icon's right-click menu.

"},{"location":"current/sailbot_workspace/workflow/#2-update-sailbot-workspace","title":"2. Update Sailbot Workspace","text":"

Sailbot Workspace is still in active development, check out its recent releases and commit history. If there are new features or bug fixes that you want to try, you will need to update your local version of Sailbot Workspace:

  1. Switch Sailbot Workspace to the main branch if you aren't in it already

    If you running Git commands in the CLI, make sure that you are in the correct repository

    Sailbot Workspace contains other repositories in the src/ directory, so if you are in one of its subdirectories you may be in the wrong repository.

    To check which repository you are in, run git remote -v; if its output contains sailbot_workspace, you are good to go. If not, you can navigate the root directory of the Sailbot Workspace repository with cd $ROS_WORKSPACE, or open a new terminal in its root directory with Ctrl+Shift+` then Enter.

    • If you are unable to switch branches because you have uncommitted changes, stash them
  2. Pull the latest changes

    • If you stashed your uncommitted changes, pop them
  3. If prompted, rebuild the Dev Container

    When does the Dev Container need to be rebuilt?

    To apply the modifications to its configuration files in .devcontainer/ that occurred since it was last built.

    VS Code will prompt you to rebuild when devcontainer.json, Dockerfile, or docker-compose*.yml. These file may be modified if you:

    • Pull the lastest changes of a branch
    • Switch branches
    • Update a file in .devcontainer/ yourself

    However, there may be changes to the Dev Container that VS Code can't detect. To rebuild it yourself, run the Dev Containers: Rebuild Container VS Code command.

  4. If you aren't working in any other branches, run the setup task to switch the branches of all sub-repositories to their default specified in src/polaris.repos and pull their latest changes

  5. If you want to run our docs, website, or other optional programs, see How to run optional programs
"},{"location":"current/sailbot_workspace/workflow/#3-make-your-changes","title":"3. Make your changes","text":"

We make changes to our software following our GitHub development workflow. Of particular relevance is the Developing on Branches page.

Git interfaces

One way to interface with Git is through CLI commands. However, you may find it faster to use VS Code's interface, especially when working with multiple repositories.

Things to note when making changes:

  • When C++ or Python files are saved, you may notice that some lines change. We use formatters to help fix lint errors; not all lint errors can be fixed by formatters, so you may have to resolve some manually
  • When changing a package's source files, you likely should update its test files accordingly
"},{"location":"current/sailbot_workspace/workflow/#4-build-your-changes","title":"4. Build your changes","text":"

Revamped in v1.2.0

In general, changes need to be built before they can be run. You can skip this step if you only modified Python source or test files (in python_package/python_package/ or python_package/test, respectively), or are running a launch type launch configuration.

  1. Depending on which packages you modified, run the Build All or Build Package task
    1. Unless you want to run clang-tidy, use the -q build argument (default) for quicker build times
"},{"location":"current/sailbot_workspace/workflow/#5-verify-your-changes","title":"5. Verify your changes","text":"

Revamped in v1.2.0

Running GUI applications on macOS

If you want to run GUI applications on macOS, ensure that XQuartz is running.

"},{"location":"current/sailbot_workspace/workflow/#lint-and-test","title":"Lint and Test","text":"

Run lint and test tasks to make sure you changes will pass our CI:

  • ament lint
  • For C++ packages, clang-tidy
  • test

In addition to VS Code tasks, the Testing tab on the VS Code primary sidebar contains individual tests. One can run specific unit tests by clicking the Run Test icon beside the test name.

"},{"location":"current/sailbot_workspace/workflow/#run-a-package","title":"Run a Package","text":"

To verify that your changes do what you expect, you may want to run the package you modified. The run commands for each package should be documented in their READMEs, but in general they can be run using a CLI or VS Code command:

CLI VS Code
  • Launch files:
    • ros2 launch <package> <launch file>
    • ros2 launch <path to launch file>
  • Nodes:
    • ros2 run <package> <executable>
CLI features

There are many commands that can be autocompleted in the terminal. Take advantage of this so that you run commands faster and memorize less syntax. If there is only one possibility, pressing tab once will complete it. If there is more than one possibility, pressing tab again will list them out.

Some tab completion use cases:

  • View available commands: lists all ros2 commands

    $ ros2 <tab><tab>\naction                          extension_points                multicast                       security\nbag                             extensions                      node                            service\n...\n
  • Complete commands: runs ros2 launch local_pathfinding main_launch.py

    $ ros2<tab>la<tab>loc<tab>m<tab>\n
  • Navigate to directories: runs cd .devcontainer/config from the root directory of Sailbot Workspace

    $ cd .d<tab>c<tab>\n

Furthermore, navigate past commands with Up and Down and search through them with Ctrl+R.

  • Launch files: ROS: Run a ROS launch file (roslaunch)
  • Nodes: ROS: Run a ROS executable (rosrun)

For more information on launch file use in our system, see this page.

"},{"location":"current/sailbot_workspace/workflow/#run-the-system","title":"Run the System","text":"

To verify that you didn't break anything, you may want to run the entire system. See Invoking Launch Files for more information on running the system.

"},{"location":"current/sailbot_workspace/workflow/#debugging","title":"Debugging","text":"

Debug your changes if they aren't behaving how you expect by setting breakpoints and running one of our launch configurations in the Run and Debug tab on the VS Code primary sidebar. The launch configuration types are:

  • Launch: runs the desired launch file or executable
    • For launch files, ROS: Launch
    • For C++ executables, C++ (GDB): Launch
  • Attach: attaches to a running executable
    • ROS: Attach
"},{"location":"current/sailbot_workspace/workflow/#troubleshooting","title":"Troubleshooting","text":"

If you are having some trouble running our software, here are some things you can try:

  • Build from scratch
    1. Run the clean task to delete C++ generated files
    2. Run the purge task to delete ROS generated files
    3. Run the Build All task to rebuild
  • Rebuild the Dev Container: run the Dev Containers: Rebuild Container VS Code command
  • Reload VS Code: run the Developer: Reload Window VS Code command
  • Delete Docker files

    Running Docker CLI commands on Windows

    On Windows, Docker CLI commands should be run in the Ubuntu terminal while Docker Desktop is running.

    • Run docker system prune to remove all unused containers, networks, and dangling and unreferenced images
      • Add --all to additionally remove unused images (don't have a container associated with them)
      • Add --volumes to additionally remove volumes (makes Bash history and ROS logs persist across containers)
    • Run docker rmi -f $(docker images -aq) to remove all images
"},{"location":"current/sailbot_workspace/workflow/#performance-issues","title":"Performance Issues","text":"

If you are not satisfied with the performance of Sailbot Workspace, here are some things you can try:

  • Free up memory: close programs that you aren't using
  • Free up disk space: permanently delete large programs and files that you don't need anymore
  • Run Sailbot Workspace in a GitHub Codespace
    • In a codespace with 8GB of RAM, building all packages from scratch with the -q argument takes about a minute. If your computer takes longer than, or you want to free up memory and disk space, you can setup Sailbot Workspace in a GitHub Codespace
  • If you are running Sailbot Workspace on Windows, dual boot Ubuntu and run Sailbot Workspace there
    • Sailbot Workspace performs worse on Windows than bare metal Linux because it uses Docker, which is not natively supported.
    • Here is a guide to dual boot the operating systems we recommend: How to Dual Boot Ubuntu 22.04 LTS and Windows 11
      • We recommend allocating at least 50 GB to Ubuntu to leave some wiggle room for Docker
      • The process is similar for other Ubuntu and Windows versions, but feel free to search for a guide specific to the combination you want to dual boot
      • Since Sailbot Workspace uses Docker, it should be able to run on any Linux distribution, not just Ubuntu. However, we may not be able to provide support if you encounter any difficulties with this
"},{"location":"current/website/overview/","title":"Overview","text":"

Source code

The source code for Website can be found in the website GitHub repository. Its README has been copied below.

"},{"location":"current/website/overview/#website","title":"Website","text":"

In the website development timeline, we are currently evaluating the folllowing software stack: Next.js website (this repository) and the MongoDB database. The easiest way to evaluate these potential solutions for our purposes is in sailbot_workspace.

"},{"location":"current/website/overview/#database","title":"Database","text":"

MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era. If you want to learn more about MongoDB, visit their docs site: MongoDB Documentation.

"},{"location":"current/website/overview/#setup","title":"Setup","text":""},{"location":"current/website/overview/#environment-variables","title":"Environment variables","text":"

This project uses environment variables to manage configuration-specific information. Please look at the file .env.local and ensure the variables are defined below:

  • MONGODB_URI: Your MongoDB connection string. Use mongodb://localhost:27017/<DB_NAME> to establish a connection with the local database.
  • NEXT_PUBLIC_SERVER_HOST: The host URL of the website.
  • NEXT_PUBLIC_SERVER_PORT: The port number of the website.
  • NEXT_PUBLIC_POLLING_TIME_MS: The time interval for polling the database in milliseconds.
"},{"location":"current/website/overview/#package-installation","title":"Package installation","text":"

The following command installs all required dependencies listed in the package.json file:

npm install\n

Once the installation is complete, you should see a node_modules directory in the project's root. This directory contains all installed packages.

When installing a new package to the website, please follow the steps below:

  1. Access the terminal of the website container on Docker.

  2. Run the command npm install <package-name>. Replace <package-name> with the actual name of the package you want to add.

  3. Should you encounter errors related to resolving peer dependencies, please re-run the command with the header --legacy-peer-deps. Do not to use --force unless you're well aware of the potential consequences.

  4. Review the package.json file to ensure the new package and its version have been added to the dependencies section.

  5. Confirm that package-lock.json has also been updated. This file holds specific version information to ensure consistent installations across different environments.
  6. Once the installation process is finished, please make sure to commit the files package.json and package-lock.json. These files are essential for version controlling the dependencies that have been added.
"},{"location":"current/website/overview/#run","title":"Run","text":"

Using Sailbot Workspace, the website should be up and running on http://localhost:3005.

Otherwise, you execute the following commands to run it in development mode:

npm run dev\n
"},{"location":"current/website/overview/#linters","title":"Linters","text":"

Before merging in new changes to the repository, please execute the following commands in order:

npm run format\n

This command runs Prettier to automatically format the code according to the rules defined in the configuration file .prettierrc.

npm run lint\n

This command runs ESLint to analyze the code for potential errors and enforce coding style based on the rules defined in the configuration file .eslintrc.

"},{"location":"reference/markdown/","title":"Markdown","text":"

Markdown is a lightweight markup language that you can use to add formatting elements to plaintext text documents.1 You can do anything with Markdown, from creating websites to PDF documents, all in a clean format that is easy to learn. Many of your favorite services use Markdown, so it would be useful to pick it up to write technical documentation.

Markdown is not standardized across services. Many services that support Markdown have their own \"flavour\" of Markdown. Be sure to know the Markdown features of the service you are using so that your Markdown renders properly.

"},{"location":"reference/markdown/#getting-started","title":"Getting Started","text":"

We recommend markdownguide.org to be your first point of reference if\\ you are learning Markdown for the first time. It covers topics like what Markdown is, its syntax, advanced tips, and the different services that support Markdown. Flavours of Markdown specific to a service build on top of these basics.

"},{"location":"reference/markdown/#sailbot-and-markdown","title":"Sailbot and Markdown","text":"

We write Markdown for GitHub and Material for MkDocs. The following sections detail how Markdown is used in these services.

"},{"location":"reference/markdown/#github","title":"GitHub","text":"

We use Markdown in GitHub for technical documentation and collaboration. This includes:

  • README.md files
  • Issues
  • Pull Requests

Almost all places where text is written in GitHub support Markdown. GitHub also allows you to preview your Markdown before you submit any comments.

Before RenderingAfter Rendering

The image above shows an example of a \"write\" and a \"preview\" tab for writing a comment on an issue. It might look different depending on where you are writing, but there usually exists a preview option!

GitHub-Flavoured Markdown

GitHub uses its own \"flavour\" of Markdown. Certain features, like using HTML, are excluded for security reasons. Visit the official GitHub Markdown guide for more information on the available features.

"},{"location":"reference/markdown/#material-for-mkdocs","title":"Material for MkDocs","text":"

We use Markdown in Material for MkDocs to create this website! Since it is written in Markdown, no frontend experience is required to contribute to our docs.

Material for MkDocs supports powerful features purpose-built to take technical documentation to the next level. Feel free to browse this site to see how we use these features, exploring their syntax in the source code. Since GitHub renders Markdown files automatically you will need to click the \"Raw\" button to view their contents.

Material-Flavoured Markdown

Material for MkDocs' flavour of Markdown extends upon vanilla Markdown, adding features such as admonitions (like this note) and content tabs. Refer to the official Material for MkDocs reference page for more information on the available features.

"},{"location":"reference/markdown/#rendering-markdown","title":"Rendering Markdown","text":"

You have a few choices to render Markdown on your computer. Be advised that if you are using an extended version of Markdown, you will need to consult the documentation from the service provider to render their flavour of Markdown properly. The following resources are good for rendering Markdown:

Vanilla Github Material for MkDocs
  • VS Code: Markdown rendering is supported out of the box.
  • Markdown Live Preview: An online rendering tool.
  • Markdown Preview GitHub Styling: VS Code extension that renders GitHub-flavoured markdown.
  • Create a draft issue on GitHub and preview the markdown to see how it renders.
  • UBC Sailbot Docs: To preview your changes when working on this site, refer to the run instructions in the README.md.
  • Material for MkDocs sites in general: If you ever decide to write your own documentation using Material for MkDocs, refer to the official \"Getting Started\" guide.

Other resources exist to render Markdown like browser extensions that render Markdown as HTML and GitHub repositories that contain source code to render your Markdown. Feel free to browse around for the solution that suits your needs.

"},{"location":"reference/markdown/#linting","title":"Linting","text":"

We lint our Markdown files to reduce errors and increase readability. In particular, we use two tools:

  1. markdownlint is used to enforce a style guide. Its configuration file for this repository is .markdownlint.json. If you use VS Code, there is a markdownlint extension.

  2. markdown-link-check is used to check for broken links. Its configuration file for this repository is .markdown-link-check.json.

  1. https://www.markdownguide.org/getting-started/ \u21a9

"},{"location":"reference/ros/","title":"Robot Operating System","text":"

Robot Operating System (ROS) is a set of software libraries and tools for building robot applications.1 It provides functionality for hardware abstraction, device drivers, communication between processes over multiple machines, tools for testing and visualization, and much more.2

We use ROS because it is open-source, language-agnostic, and built with cross-collaboration in mind. It enables our sub-teams to work independently on well-defined components of our software system without having to worry about the hardware it runs on or the implementation of other components.

The official ROS 2 documentation contains everything you need to get started using ROS. From it we have hand-picked the resources that are most relevant to our current and expected future usage of ROS assuming that you use our preconfigured workspace. To run our software on your device without our workspace, you would have to install ROS and the dependencies that are in our Docker images yourself.

"},{"location":"reference/ros/#workspace-configuration","title":"Workspace Configuration","text":"

To get our workspace configuration running on your computer:

  1. Set it up by following the setup instructions
  2. Uncomment the ROS 2 tutorials section in .devcontainer/Dockerfile, then run the \"Dev Containers: Rebuild Container\" VS Code command, to install the tutorials' dependencies
  3. Uncomment the ROS 2 tutorials section in src/polaris.repos, then run the \"setup\" VS Code task, to clone the repositories used in the tutorials

Our workspace configuration contains easier methods of accomplishing some of the tutorial steps, or eliminates the need for them altogether.

Tutorial step Sailbot Workspace configuration Install a package All packages used in the tutorials are already installed (step 2 above) Clone a sample repo (ros_tutorials) ros_tutorials is already cloned (step 3 above) Resolve dependencies Run the \"install dependencies\" VS Code task Build the workspace Run the \"Build\" VS Code task, AKA Ctrl+Shift+B Source the overlay Run the srcnew terminal command Create a package with a node Run the \"new ament_(python|cmake) package with a node\" VS Code task"},{"location":"reference/ros/#tutorials","title":"Tutorials","text":"

We encourage all software members to work through the ROS tutorials that are listed below in order. For tutorials that have both C++ and Python versions, NET members should do the C++ version while CTRL and PATH members should do the Python version.

  • Beginner: CLI tools
    • Introducing turtlesim and rqt
    • Understanding nodes
    • Understanding topics
    • Understanding services
    • Understanding parameters
    • Understanding actions
    • Using rqt_console to view logs
    • Recording and playing back data
  • Beginner: Client libraries
    • Creating a workspace
    • Creating a package
    • Writing a simple publisher and subscriber (C++ or Python)
    • Writing a simple service and client (C++ or Python)
    • Using parameters in a class (C++ or Python)
    • Using ros2doctor to identify issues
  • Intermediate
    • Launch
    • Testing
  • Demos
    • Logging
"},{"location":"reference/ros/#concepts","title":"Concepts","text":"

We encourage all software members to read the following documentation on key ROS concepts:

  • About logging and logger configuration
  • About ROS 2 interfaces
  • About parameters in ROS 2
"},{"location":"reference/ros/#ros-1-bridge","title":"ROS 1 Bridge","text":"

There are two major versions of ROS, aptly named ROS 1 and ROS 2. Our previous project, Raye, uses ROS 1 because it was the only version available during her design process. Our new project will use ROS 2, a complete re-design of the framework that tackles the shortcomings of ROS 1 to bring it up to industry needs and standards.3 If you are curious about the changes made in ROS 2 compared to 1, this article is a worthwhile read.

ROS 2 includes the ROS 1 Bridge, a collection of packages that can be installed alongside ROS 1 to help migrate code from ROS 1 to ROS 2. As we will be reusing parts of Raye's codebase, it is essential to know how to use these packages. Until we are completely done with Raye, our preconfigured workspace will have ROS 1, ROS 1 Bridge, and ROS 2 installed.

We encourage all software members work through the ROS 1 Bridge README. For PATH members, the Migrating launch files from ROS 1 to ROS 2 page will be a helpful reference when we do so.

  1. https://docs.ros.org/en/humble/index.html \u21a9

  2. https://www.toptal.com/robotics/introduction-to-robot-operating-system \u21a9

  3. https://ubuntu.com/robotics/what-is-ros \u21a9

"},{"location":"reference/cpp/differences/","title":"Differences Between C and C++","text":"

For most use cases, you can think of C++ as a superset of C. While this is not technically true, more often than not you are able to write standard C code for a C++ program without issues. However, doing so ignores a lot of the benefits and reasons to use C++.

"},{"location":"reference/cpp/differences/#classes-and-structs","title":"Classes and Structs","text":"

In C structs can only contain member variables, but in C++ structs are basically classes but with a default member visibility of public instead of private.

Example

The following code blocks are equivalent.

struct foo {\nprivate:\n    int x;\n    void helper(void);\npublic:\n    foo(int y);\n}\n
class foo {\nprivate:\n    int x;\n    void helper(void);\npublic:\n    foo(int y);\n}\n
"},{"location":"reference/cpp/differences/#namespaces","title":"Namespaces","text":"

One problem that is prevalent in C concerns the scoping of names. For example, let there be two files A.h and B.h and a program ighxy.c, and let them both contain a float x and int bar(void).

Our program cannot compile because the linker cannot distinguish which bar() function we want to use! One way to fix this in a C program would be to rename them a_bar() and b_bar(). Although this fix seems trivial for this example, applying it to a file that has potentially 100 functions can be a lot more difficult, especially if two files just happen to share the same prefix for their functions!

C++ introduces namespaces to tackle this problem. With namespaces, we can deal with naming conflicts much more easily. Though be aware that namespaces are not necessary everywhere. See the following code snippet to see how they work.

Example CC++ A.h
float x;\nint bar(void);\n
B.h
float x;\nint bar(void);\n
ighxy.c
#include \"A.h\"\n#include \"B.h\"\n\nint main(void) {\n    int a = bar();\n    ...\n}\n/* Error, does not compile*/\n
A.h
namespace a {\nfloat x;\nint bar(void);\n}\n
B.h
namespace b {\nfloat x;\nint bar(void);\n}\n
ighxy.cpp
#include \"A.h\"\n#include \"B.h\"\n\nint main(void) {\n    int a = a::bar();\n    int b = b::bar();\n    float xa = a::x;\n    float xb = b::x;\n    /* No problem! */\n    ...\n}\n
Warning

You may come across something like:

example.cpp
using namespace std;\nnamespace io = std::filesystem;\n\nint main(int argc, char* argv[]) {\n    bool isDirectory = io::is_directory(argv[1]);  // Equivalent to std::filesystem::is_directory(argv[1])\n    cout << isDirectory << endl;\n    return 0;\n}\n

There are two things going on here.

First, using namespace std makes all functions and types defined within the standard namespace and included via #include directives visible to example.cpp. If you are familiar with Python, the Python equivalent of this would be import std as *. However, it is considered bad practice to do this as it eliminates the point of using namespaces.

OKNot OK
    class string {\n        // Insert implementation here\n    }\n\n    int main(void) {\n        string ourString = \"Our own string implementation\";\n        std::string stdString = \"Standard Library string implementation\";\n        ...\n    }\n
    using namespace std;\n\n    // ERROR - multiple definitions of type string\n    class string {\n\n    }\n

The compiler cannot infer which implementation we want.

Secondly, namespace io = std::filesystem is basically an alias for the std::filesystem namespace. This practice is acceptable for long namespace identifiers, but be careful as it can still run into namespace conflicts if your alias is the same as another namespace or alias.

"},{"location":"reference/cpp/differences/#constant-expressions","title":"Constant Expressions","text":"

In C, if we want to declare a constant or a function/expression that we want to be evaluated at compile time, we need to use #define statements. One of the problems with #define statements is that they perform a simple copy paste wherever they're used. For example:

Before PrecompileAfter Precompile
#define PI 3.14F\n#define AREA_OF_CIRCLE(radius) ((PI) * (radius) * (radius))\n\nint main(void) {\n    float area = AREA_OF_CIRCLE(2.5F);\n    ...\n}\n
int main(void) {\n    float area = ((3.14F) * (2.5F) * (2.5F));\n    ...\n}\n

Note

AREA_OF_CIRCLE is a macro with arguments. If you are confused by it, this resource has a detailed explanation on how they work.

Because of this copy-pasting, you need to be very careful with syntax, sometimes necessitating an ugly do {} while(0) wrapper. Moreover, symbols declared with #define are always globally visible, ignoring namespaces!

In C++, the use of constant expressions are preferred.

constexpr float pi = 3.14F;\nconstexpr float area_of_circle(float radius) {\n    return pi * radius * radius;\n}\n

Constant expressions do not get copy pasted, and are instead placed in program memory just like a normal variable or function. They also respect namespaces and function scopes, meaning the following code compiles.

Constant Expression Scoping
void foo(void) {\n    constexpr float rand = 123.456;\n    ...\n}\n\nvoid bar (void) {\n    constexpr float rand = 789.123;\n    ...\n}\n
"},{"location":"reference/cpp/differences/#lambdas","title":"Lambdas","text":"

Lambdas are primarily useful when you need to register a callback function one time and don't feel it's necessary to write out a full function. They are in no way required though, so do not worry about learning them. However, it's necessary to know that they exist such that you don't get confused when reading code. For more information, go here for Microsoft's explanation.

"},{"location":"reference/cpp/differences/#misc","title":"Misc","text":""},{"location":"reference/cpp/differences/#arrays","title":"Arrays","text":"

Using the C++ implementation of arrays is preferred over C arrays. It is simply easier and safer to work with than a standard C array without any performance costs.

Example

Passing an array to a function an iterating over it

CC++

#include \"stdio.h\"\n\nvoid print_contents(int *arr, int size) {\n    for (int i = 0; i < size; i++) {\n        printf(\"%d\\n\", *arr);\n    }\n}\n\nint main(void) {\n    int arr[5] = {0, 1, 2, 3, 4};\n    foo(arr, 5);\n    return 0;\n}\n
We can't even guarantee that the integer pointer arr is an array!

C++ 20 makes passing arrays around a lot simpler. Do not worry about understanding the code shown below. It uses some fairly advanced concepts and exists to illustrate how different such a simple operation can be.

#include <iostream>\n#include <array>\n#include <span>\n\nvoid print_contents(std::span<int> container) {\n    for (const auto &e : container) {\n        std::cout << e << std::endl;\n    }\n}\n\nint main(void) {\n    std::array<int, 5> arr = {0, 1, 2, 3, 4};\n    foo(arr);\n    return 0;\n}\n

The advantages of the C++ version are:

  • Size is implicitly part of the object
  • We guarantee that foo takes a container, but it does not care if it's an array or, say, a vector, which is preferable in this scenario where we simply iterate through the container's existing elements
"},{"location":"reference/cpp/start/","title":"Getting Started","text":"

UBC Sailbot's Network Systems team uses C++ for its software. If you know already know C, then you already know the bare minimum to write C++. This is a good starting point, but the additional features C++ provides allow for safer programming practices.

"},{"location":"reference/cpp/start/#for-cc-beginners","title":"For C/C++ Beginners","text":"

If you just need to know how C++ is different from C, then see the Differences Between C and C++. You should also look at it if you go through and finish this section.

If you are new to C and C++, then this the best place to start. The tutorials provided in this section will help you learn the fundamentals of the language. Do not feel pressured to do all the tutorials! Just get comfortable with the syntax and the mechanisms of the language.

Note

The hardest part about this will likely be pointers and dynamic memory, so pay close attention to tutorials concerning them! Additionally, dynamic memory requires the usage of pointers, but pointers do not require dynamic memory!

Tip

Dynamic memory is much more prone to error than statically allocated memory, so try to use static allocation whenever possible

Resource Description w3schools Tutorial A structured tutorial that goes through basic concepts in C++. It's good to do up to the section on Classes. YouTube Tutorial If you prefer video tutorial, then this is a comprehensive 4 hour video covering similar concepts to the one above. It is 4 hours long though. Dynamic Memory Overview A page going over how dynamic memory works in C++.

Feel free to add other resources other than the ones listed above if you find any that you like!

"},{"location":"reference/cpp/tools/","title":"Tools","text":"

A lot goes into making a well structured C++ project, much more than any one team should have to do.

"},{"location":"reference/cpp/tools/#cmake","title":"CMake","text":"

CMake is a powerfull build automation tool that makes compiling code for large projects with a lot of interoperating files a lot easier. Steps 1-3 of the official tutorial are great for understanding the basics.

"},{"location":"reference/cpp/tools/#gdb","title":"GDB","text":"

The GNU Project Debugger is the most commonly debugger for the C language family. VSCode also has a degree of integration with GDB that allows an easy to use GUI. This GDB cheat sheet has all the GDB comands you will need to know. Be aware the VSCode has GUI buttons for some of these commands that are easier to use.

"},{"location":"reference/cpp/tools/#googletest","title":"GoogleTest","text":"

GoogleTest is the C++ unit testing framework we will be using. The GoogleTest Primer is a good place to start.

Example Cached Fibonacci ProgramTest Cached Fibonacci Program cached_fib.h
#include <vector>\nclass CachedFib {\npublic:\n    void CachedFib(int n);\n    int  getFib(int n);\nprivate:\n    std::vector<int> cache;\n}\n
cached_fib.cpp
#include <iostream>\n#include <vector>\n#include \"cached_fib.h\"\n\nvoid CachedFib::CachedFib(int n) {\n    cache.push_back(0);\n    cache.push_back(1);\n    for (int i = 2; i < n; i++) {\n        cache.push_back(cache[i - 1] + cache[i - 2]);\n    }\n}\n\nint CachedFib::getFib(int n) {\n    if (cache.size() < n) {\n        for (int i = cache.size(); i < n; i++) {\n            cache.push_back(cache[i-1] + cache[i-2]);\n        }\n    }\n    std::cout << cache[n - 1] << std::endl;\n}\n
test_cached_fib.cpp
#include \"cached_fib.h\"\n#include \"gtest/gtest.h\"\n\nCachedFib::testFib;\n\nclass TestFib : public ::testing::Test {\nprotected:\n    void Setup override {\n        // Every time a test is started, testFib is reinitialized with a constructor parameter of 5\n        testFib = CachedFib(5);\n    }\n}\n\nTEST_F(TestFib, TestBasic) {\n    ASSERT_EQ(getFib(5), 3) << \"5th fibonacci number must be 3!\";\n}\n\n// more tests\n
"},{"location":"reference/cpp/tools/#google-protocol-buffer","title":"Google Protocol Buffer","text":"

Google Protocol Buffer (Protobuf) is a portable data serialization method. We use it over other methods like JSON and XML because it produces smaller binaries, an important consideration when sending data across an ocean. Unfortunately, there does not seem to be a easy to follow tutorial for using them, but here are the C++ basics. The page is quite dense and can be hard to follow, so do not worry if you do not understand it.

"},{"location":"reference/cpp/tools/#clang","title":"Clang","text":"

In its most basic form, Clang is a compiler for the C language family. Clang has multiple benefits like easier portability compared to, for example, GCC. Clang is actually \"half\" the compiler, the other half being LLVM. Without going into unnecessary detail, Clang compiles C++ code to a generic language before LLVM compiles it to machine specific language.

"},{"location":"reference/cpp/tools/#clangd","title":"Clangd","text":"

Clangd is the Clang language server. It provides a much more powerful intellisense than the default one used in VSCode's C/C++ extension.

"},{"location":"reference/cpp/tools/#clang-tidy","title":"Clang-Tidy","text":"

Clang-Tidy is a linting tool, who's main purpose is to catch potential programming errors caused by bad programming style/practices using just static analysis.

"},{"location":"reference/cpp/tools/#clang-format","title":"Clang Format","text":"

An autoformatting tool that makes enforcing style guidelines much easier. When se tup, it corrects formatting as soon as you hit save.

"},{"location":"reference/cpp/tools/#llvm-cov","title":"llvm-cov","text":"

We will use llvm-cov to evaluate our test coverage. When used with genhtml, we can generate HTML reports that that show our line, function, and branch coverage line-by-line.

"},{"location":"reference/github/workflow/branches/","title":"Developing on Branches","text":"

We use branching to work on issues without modifying the main line. This ensures that the main line only contains functional code and handles merge conflicts that arise when multiple people are developing at the same time. For a quick rundown on branching in git, consult the official git documentation.

"},{"location":"reference/github/workflow/branches/#creating-a-branch","title":"Creating a branch","text":"

When starting a new issue, you will want to create a new branch for it:

Caution

When creating branches locally, it uses your local copy to create the new branch. Remember to do a git pull if you intend on using the latest changes from the remote branch you are creating from.

Creating a new branch from main
# Switch to main\ngit switch main\n\n# Update your local copy\ngit pull\n\n# Clone a new branch from main\ngit switch -c <branch_name>\n

IMPORTANT: When creating a new branch for an issue, you must create the branch from main.

"},{"location":"reference/github/workflow/branches/#branch-naming-convention","title":"Branch naming convention","text":"

When working on a new issue, you will want to create a branch to work on it. We have the following branch naming convention:

user/<github_username>/<issue_number>-<issue_description>\n

Example

If Jill (GitHub Username: jill99) is going to take on an issue titled \"Fix bug on pathfinding software\" and the issue number is 39, then the branch named can be named something like user/jill99/39-fix-pathfinding-bug.

If the branch that you are creating is not tied to an issue, then you do not need to put an issue number. A descriptive title will suffice.

"},{"location":"reference/github/workflow/branches/#tracking-and-committing-changes","title":"Tracking and committing changes","text":"

All files where new changes have been made must first be \"staged\" in order to make commits:

git add <FILES>\n

Files that are staged will be part of your next commit. Once you are confident in your changes and you are ready to finalize them, then you should commit your changes:

git commit -m \"<commit_message>\"\n

Be sure to add a commit message that is descriptive of the changes that you made. It is encouraged that you make commits often so you can keep track of your changes more easily and avoid overwhelmingly large commits when you look back on your version history.

When you are ready to move your local changes to a remote branch, you want to push to the correct branch and potentially set the upstream if it does not yet exist:

git push -u origin <current_branch_name>\n
"},{"location":"reference/github/workflow/branches/#merging-branches","title":"Merging branches","text":"

There may be times where you want to merge two branches together, whether you diverged on some ideas and finally want to synthesize them, or you just want to update your issue's branch with the main branch. In any case, merging branches will be inevitable as part of the development process, so it is essential to understand how to merge branches.

Merge Local BranchMerge Remote Branch
# Checkout to destination branch\ngit checkout <dest_branch>\n\n# Merge with local copy of other branch\ngit merge <other_branch>\n
# Checkout to destination branch\ngit checkout <dest_branch>\n\n# Fetch from remote\ngit fetch\n\n# Merge remote copy of other branch\ngit merge origin/<other_branch>\n

Info

Merging a remote branch into its local counterpart using the method above is essentially the same operation as git pull.

Once the merge operation is complete, your destination branch should have updates both from itself and the other branch that you merge. If you do a git log, you will also see a new commit that indicates that the merge happened.

"},{"location":"reference/github/workflow/branches/#resolving-merge-conflicts","title":"Resolving merge conflicts","text":"

Merging two branches is not always easy since the commit history for both branches could look quite different, and therefore conflicting changes can easily be made. If you run into a scenario like this, you may get something like this:

Upon inspecting bar.txt, we see the following:

Resolving merge conflicts is not always a trivial task, but there are many ways to resolve them which include:

  • Resolving on GitHub (recommended)
  • Resolving in Command Line

Tip

If you cannot resolve a merge conflict on your own, reach out to your lead for help!

"},{"location":"reference/github/workflow/issues/","title":"Creating Issues","text":"

GitHub issues lets us plan and track our work on GitHub.

"},{"location":"reference/github/workflow/issues/#getting-started-with-issue-templates","title":"Getting started with issue templates","text":"

An issue is associated with a specific repository. To open the issues page for a given repository, click on the issues tab in the repository navigation bar.

You will see a list of current issues (if any) for the repository. To create a new issue, click on the New issue button in the upper right corner.

When creating a new issue, you will see a few issue templates. Since issues can be created for a variety of reasons, issues may therefore be structured differently and contain different kinds of information. Issue templates were introduced to give us a quick and structured way to writing issues.

Note

GitHub issues are written using GitHub-flavoured markdown. To add a little spice to your issues, refer to the official GitHub documentation for some quick tips and tricks on how to write awesome markdown!

Click on the Get started button to open the issue template. For this example, let's go with the New Feature issue template. Upon opening the issue template, you should see a page like the one below:

At this point, you should give a succinct title and describe the issue in the textbox. You will also see some templated sections to fill out. Try to give only the necessary details to make a clear and concise issue. If you are unsure on how to construct your issue, take a look at current or past issues and ask the software leads for further guidance if necessary.

Finally, feel free to make suggestions on new templates or changing current templates!

Tip

We understand that some issues may need extra sections to describe the issue further, or some of the templated sections might not be relevant at all! Add or remove sections as necessary to get your point across. The goal of the issue templates is to provide guidance, not police your documentation methodologies!

"},{"location":"reference/github/workflow/issues/#adding-issues-to-a-project","title":"Adding issues to a project","text":"

We use projects to plan and track the status of our issues and pull requests. To add an issue to an existing project, click on the gear icon in the Projects section and add it to your desired project. You will almost always want to add your issue to the Software organization project.

To verify that your issue has been added to your desired project, go to the UBC Sailbot organization, go to the Projects tab on the organization banner, and select the project that it is added to. When added to a project, it should show up under the General tab (depending on the project, this might not always be the case).

"},{"location":"reference/github/workflow/issues/#adding-issues-to-a-milestone","title":"Adding issues to a milestone","text":"

We use milestones to track progress on groups of issues or pull requests that we want to complete by a certain date. Since our projects span over many years, it is important to work incrementally with small, yet achievable goals. If your issue should belong to a milestone, simply add it to a milestone by clicking on the gear icon in the Milestone section and add it to your desired milestone.

Note

Unlike projects, milestones are strictly associated with a repository.

"},{"location":"reference/github/workflow/issues/#labelling-issues","title":"Labelling issues","text":"

GitHub allows us to label our issues so that we can categorize them. It helps us identify at first glance what kind of a problem that an issue aims to solve and which issues are more important. To add a label to your issue, click on the gear icon in the Labels section and add your desired label(s).

The issue templates will already have labels assigned to them, but you should add or remove labels as you see fit to make them as relevant as possible.

Note

Each repository might have different labels available, so be sure to check out all of the labels at least once in the repository that you are working in. Feel free to suggest additional labels as well!

"},{"location":"reference/github/workflow/issues/#adding-assignees","title":"Adding assignees","text":"

Every issue should be assigned to at least one person to work on it. If you are not sure who should be assigned the issue initially, then don't worry about it for now since you can assign someone to the issue later on. To assign someone an issue, click on the gear icon in the Assignees section and add the desired people.

"},{"location":"reference/github/workflow/issues/#submit-the-issue","title":"Submit the issue","text":"

Once you are finished writing your issue, click on the Submit new issue button. You should now see your issue in the issues list and in the UBC Sailbot software project.

"},{"location":"reference/github/workflow/overview/","title":"Development Workflow Overview","text":"
graph LR\n    B[Problem Conception] --> C{Small Fix?};\n    C --> |Yes| E[Development];\n    C --> |No| D[Issue Creation];\n    D --> E;\n    E --> F[Pull Request];\n    F --> G{Approved?};\n    G --> |No| E;\n    G --> |Yes| H[Merge PR into Main];

A good development workflow is essential to maintain a robust codebase and stay organized. The above diagram is a high level overview of how our development process works, and parts of this process are explained in subsequent sections.

"},{"location":"reference/github/workflow/overview/#version-control-git","title":"Version control: Git","text":"

We use git to help us keep track of the version history of our codebase. Git is a free and open source distributed version control system, and it is commonly used by many developers to keep track of changes to their code over time. As a member of the software team on UBC Sailbot, it is absolutely necessary that you know git. If you are unfamiliar with git, here are a few resources to help you get started:

Resource Description Beginners Tutorial A 30 minute video on git for beginners. Good if you want to learn git quickly and nail all the fundamentals. Pro Git book A textbook on using git. Good if you are a completionist and want to deep dive into how git works (and if you have some time on your hands). Common Git Commands A condensed summary of some common git commands. Good to refer to once you are familiar with the fundamentals of git."},{"location":"reference/github/workflow/overview/#remote-server-github","title":"Remote server: GitHub","text":"

We use GitHub as our remote server where we store our codebase. In addition to using it for storage, we also leverage many of GitHub's features to make for a smoother development process. Some examples of features that we use are:

  • Issues
  • Projects
  • Milestones
  • GitHub Organizations
  • Repository Permissions and Branch Protection Rules
  • And more!
"},{"location":"reference/github/workflow/pr/","title":"Pull Requests","text":"

Pull requests are used to verify code functionality and quality of a development branch before merging into the main branch, accomplished through CI and code reviews.

Note

Pull requests are much like issues where we can do many of the same things. This goes for creating comments in markdown, assigning reviewers, adding labels, adding projects, or adding milestones. Sometimes we skip writing an issue when the change is relatively small.

"},{"location":"reference/github/workflow/pr/#creating-a-pull-request","title":"Creating a pull request","text":"

To create a pull request in a repository, to go the Pull requests tab and then click New pull request:

On the next screen, you need to select the base branch that you are merging into, and the branch that you are comparing. For the most part, the base branch will be the main branch, and the branch that you are comparing will be the issue branch.

Once you have decided on your base and compare branches, click on Create pull request. You should see the page below (looking in the dropdown menu, you can open the pull request as a draft to avoid notifying reviewers until you are ready):

Notice how this is remarkably similar to the page of an issue. To link a pull request to an issue, simply add <KEYWORD> #<ISSUE NUMBER> to the initial comment in the pull request. A list of valid keywords can be found here.

Example

\"This issue resolves #49. Please review my pull request!\"

Observe that the right-hand side banner contains the following:

Field Description Reviewers Assign reviewers to review your pull request. Always try to assign at least one reviewer. Assignees Assign the people who worked on the issue. Labels Assign labels to categorize pull requests. Projects Assign a pull request to a project. Milestone Assign a pull request to a milestone.

Attention

If you linked the pull request to an issue, you should not add the pull request to a project or a milestone to avoid duplicate cards.

"},{"location":"reference/github/workflow/pr/#merging-into-main","title":"Merging into main","text":"

Once the pull request and code reviews are complete, it is time to merge the changes in the pull request into the main branch! However, this can only be done when the following conditions are met:

  1. All CI checks pass (look for a green checkmark beside your latest commit on GitHub).
  2. All reviewers have reviewed the PR and approved the PR.
  3. There are no unresolved comments and suggestions from the reviewers.
  4. There are no merge conflicts with the main branch.

If all of these conditions are met, confirm that the merge is good to go by clicking Squash and merge:

"},{"location":"reference/github/workflow/pr/#reviewing-a-pull-request","title":"Reviewing a pull request","text":"

A common activity that you will participate in is reviewing pull requests to give your feedback on other's code. You will be notified when you have been requested to review a pull request and should promptly review it as soon as time permits.

In particular, you will most likely be doing the following in a pull request:

  • Asking Questions: Clarify your understanding about something that you are not sure about.
  • Providing Suggestions: Give some ideas about how to improve the current implementation and provide feedback to your peers. This is a good opportunity to share your knowledge with others.
  • Verify Implementations: Identify potential bugs in the implementation and raise your concerns with the person who developed the solution. This will reduce the likelihood of bugs and significantly bring down the number of issues in the future.
  • Documentation: Record why certain changes were made, especially if this diverges from the proposed solution in the linked issue (if any).
"},{"location":"reference/python/conventions/","title":"Conventions","text":"

At UBC Sailbot, we follow standards in how we code to maintain a clean and comprehensible codebase. This page addresses what conventions we use specifically when programming in Python and the tools to help us maintain these conventions.

"},{"location":"reference/python/conventions/#style-guide","title":"Style guide","text":""},{"location":"reference/python/conventions/#linting","title":"Linting","text":"

To ensure that the codebase stays clean, we use flake8, which is a tool for style guide enforcement mostly based off pep8. To automate most of this process, we use autopep8, which is a tool that resolves most style issues. However, there will be some issues that must be resolved by you!

Refer to this guide on how to write readable code in python with the pep8 style guide.

Note

Our CI automatically checks that your code follows the pep8 standard. If it does not, your pull requests will be blocked from being merged until those issues are resolved!

"},{"location":"reference/python/conventions/#type-hinting","title":"Type hinting","text":"

Even though Python is a dynamically typed language, newer versions support type hinting. Type hinting catches errors, documents code, improves IDEs and linters, and helps build and maintain a clean software architecture.1 Expanding on how it catches errors, a static type checker such as mypy can be used.

There is some syntax to get familiar in order to use type checking. We recommend the following resources:

  • mypy Typing Cheatsheet
  • PEP 483: The Theory of Type Hints (A Simplified Guide)
  • PEP 484: Type Hints (Fully Comprehensive Guide)

Below are a few examples of using type hinting:

Return the sum of a sequence
from typing import Sequence, Union\n\n\nNumber = Union[int, float]\n\n\ndef sumseq(seq : Sequence[Number]) -> Number:\n    return sum(seq)\n
Function with optional parameters and default values
from typing import Optional\n\n\ndef printArgs(a : str, b : str=\"World\", c : Optional[str]=None) -> None:\n    print(f\"Value of a: {a}\")\n    print(f\"Value of b: {b}\")\n    if c is not None:\n        print(f\"Value of c: {c}\")\n
Function with custom class
class MyClass:\n    def __init__(self) -> None:\n        pass\n\n\ndef foo(a : MyClass) -> None:\n    print(a)\n
Forward referencing a class With __future__Without __future__
from __future__ import annotations\n\n\ndef foo(a : MyClass) -> None:\n    print(a)\n\n\nclass MyClass:\n    def __init__(self) -> None:\n        pass\n
def foo(a : 'MyClass') -> None:\n    print(a)\n\n\nclass MyClass:\n    def __init__(self) -> None:\n        pass\n
Function that never returns
from typing import NoReturn\n\n\ndef bar() -> NoReturn:\n    while True:\n        print(\"Hello World!\")\n
"},{"location":"reference/python/conventions/#documentation","title":"Documentation","text":"

Code is written once and read a thousand times, so it is important to provide good documentation for current and future members of the software team. The major things that we document in our code are:

  1. Classes and Objects:
    • What does it represent? What is it used for?
    • What are its member variables? What are they used for?
  2. Functions:
    • What are the inputs and outputs?
    • What is the overall behavior and purpose of the function?
  3. Code:
    • Is a line of code obscure and/or not clear? Add an inline comment to clear things up.
    • Break down a large process.

Ideally, the third point should be avoided as much as possible since we would want our code to be self explanatory. It should be done only when absolutely necessary.

"},{"location":"reference/python/conventions/#generating-docstrings","title":"Generating docstrings","text":"

We use a vscode extension called autoDocstring which autogenerates docstrings that we use to document our code. To install this extension, go to the Extensions tab in vscode and search autoDocstring in the marketplace.

To generate docstrings, type \"\"\" at the beginning of the function that you want to document and the template will be generated for you! If you use type hinting, this extention will autofill some of the documentation for you!

Note

The autoDocstring extension only works for functions. It does not work for classes and objects, so documenting these will have to be done manually. Be sure to follow the same format used by functions.

"},{"location":"reference/python/conventions/#example-on-documentation","title":"Example on documentation","text":"

It's hard to imagine what good documentation looks like. We provide a few examples below of documenting code using the autoDocstring extension. The extension uses Google style docstrings by default.

Documentation example on a function
from typing import List\ndef inner_product(v1 : List[float], v2 : List[float]) -> float:\n    \"\"\"\n    Computes the inner product between two 1D real vectors. Input vectors should have the\n    same dimensions.\n\n    Args:\n        v1 (List[float]): The first vector of real numbers.\n        v2 (List[float]): The second vector of real numbers.\n\n    Returns:\n        float : The inner product between v1 and v2\n    \"\"\"\n    assert (len(v1) == len(v2)), \"Input lists must have same length\"\n\n    # Iterate through elementwise pairs\n    summation = 0\n    for e1, e2 in zip(v1, v2):\n        summation += (e1 * e2)\n    return float(summation)\n
Documentation example with a stack
from typing import Any\nclass Stack:\n\n    \"\"\"\n    This class represents a stack, which is an abstract data type that serves as a collection of\n    elements. The stack is a LIFO datastructure defined by two main operations: Push and Pop.\n\n    Attributes:\n        __stack (List[Any]): A list containing the elements on the stack.\n    \"\"\"\n\n    def __init__(self):\n        \"\"\"\n        Initializes the Stack object.\n        \"\"\"\n        self.__stack = []\n\n    def push(self, element : Any) -> Any:\n        \"\"\"\n        Pushes an element to the top of the stack.\n\n        Args:\n            element (Any): The element to be pushed on to the stack.\n        \"\"\"\n        self.__stack.append(element)\n\n    def pop(self) -> Any:\n        \"\"\"\n        Removes the element at the top of the stack and returns it. If the stack is empty,\n        then None is returned.\n\n        Returns:\n            Any, NoneType: The element at the top of the stack.\n        \"\"\"\n        if self.is_empty():\n            return None\n        else:\n            return self.__stack.pop()\n\n    def is_empty(self) -> bool:\n        \"\"\"\n        Determines whether the stack is empty or not.\n\n        Returns:\n            bool: Returns True if the stack is empty, and False otherwise.\n        \"\"\"\n        empty = (len(self.__stack) == 0)\n        return empty\n\n    def __len__(self) -> int:\n        \"\"\"\n        Gets the number of elements on the stack.\n\n        Returns:\n            int: The number of elements on the stack.\n        \"\"\"\n        length = len(self.__stack)\n        return length\n

For more examples, see Example Google Style Python Docstrings.

  1. https://realpython.com/lessons/pros-and-cons-type-hints/ \u21a9

"},{"location":"reference/python/start/","title":"Getting Started","text":"

We use Python 3 to write the majority of our software at UBC Sailbot. Pathfinding and Controls mainly use Python 3, so it is critical that you are familiar with the language if you are on one of these sub-teams.

"},{"location":"reference/python/start/#python-tutorials","title":"Python tutorials","text":"

We understand that not everyone who joins Sailbot has Python in their toolkit, nor do we expect it either! Whether you are learning Python for the first time or you just want to brush up, we have provided some resources below. You may not learn absolutely everything from the resources below, but it is a good starting point. You will mostly learn through doing, as you would with most technical skills!

Resource Description The Python Tutorial The official python tutorial. Good if you have some time on your hands and you are a completionist. Sections 1 - 5 and 9 are the most relevant. w3schools Tutorial Good if you want a more brief introduction to Python. It breaks down a lot of concepts into sections. Everything up to Python Classes/Objects is relevant. YouTube Tutorial If you like video tutorials, then we recommend this tutorial. This video is about 5 hours long, but it pretty much covers everything that you'll need to know for Python and there are some hands on projects. Shorter YouTube Tutorial A shorter alternative YouTube tutorial condensed into 1 hour. It covers less material but still covers many of the essentials. CodingBat Practice Good resource to put your Python skills to practice on some simple coding problems. Note that this resource does not teach you python.

Feel free to add other resources other than the ones listed above if you find any that you like!

"},{"location":"reference/python/virtual-environments/","title":"Virtual Environments","text":"

The Python virtual environment is a tool for dependency management and project isolation. They solve many common issues, including:

  • Dependency Resolution: A project might want a package with version A while another project might want a package with version B. With a virtual environment, you can separate which packages that you want to use for a given project.

  • Project Isolation: The environment for your project is self-contained and reproducible by capturing all dependencies in a configuration file.

  • Housekeeping: Virtual environments allow you to keep your global workspace tidy.

There are two main methods of creating virtual environments: virtualenv and Anaconda. Each have their own benefits and drawbacks. Here are some differences between the two:

Virtualenv Anaconda Environment files are local. Environment files are available globally. Must activate environment by giving the path. Can activate the environment without knowing the path, but only the name. Can only use pip to install packages. Can either use pip or built-in conda package manager. Installation is very simple. Installation takes more effort. Can only install python packages. In addition to packages, you can download many data science tools.

We recommend virtualenv over Anaconda because of its simplicity. However, feel free to appeal to your preferences.

"},{"location":"reference/python/virtual-environments/#installation","title":"Installation","text":"Virtualenv Anaconda

If you already have python and the pip package manager installed, just execute the following:

Using pip to install virtualenv
pip install virtualenv\n

Go to the official Anaconda website and follow the installation instructions for your operating system.

"},{"location":"reference/python/virtual-environments/#using-virtual-environments","title":"Using virtual environments","text":"

The name of a virtual environment is configurable. For the purposes of this site, we will use env as the environment name unless specified otherwise.

"},{"location":"reference/python/virtual-environments/#creating-a-virtual-environment","title":"Creating a virtual environment","text":"Virtualenv Anaconda

Since virtualenv creates the environment directory in a specific location, make sure that you are in the located in the project that you want to work on.

Create virtual environment with virtualenv
# Go to desired location\ncd <PATH TO DIRECTORY>\n\n# Create the environment with the name env\npython3 -m venv env\n

Verify that your environment is created by examining your current directory and look for the directory that matches the name of your virtual environment.

Since the environment will be available globally, there is no need to go to a specific location to create it.

Create virtual environment with Anaconda
# Create environment with name env and python version\nconda env create -n env python=<PYTHON VERSION NUM>\n

If you don't specify a python version, the default is the version you used when you downloaded and installed Anaconda. Verify that your environment is created by executing conda env list.

"},{"location":"reference/python/virtual-environments/#activating-the-virtual-environment","title":"Activating the virtual environment","text":"

To use the virtual environment, you must activate it.

Virtualenv Anaconda Windows macOS Linux Activation for Windows
env\\Scripts\\activate\n
Activation for macOS
source env/bin/activate\n
Activation for Linux
source env/bin/activate\n
Activation for Anaconda
conda activate env\n

After activating your virtual environment, you might see (env) on your terminal before or after your current line. Now you are in your virtual environment!

"},{"location":"reference/python/virtual-environments/#installing-dependencies","title":"Installing dependencies","text":"

Any dependencies that you install while your virtual environment is activated are only available in your virtual environment. If you deactivate your environment and try to use those dependencies, you will find that you will get errors because they will not be found unless you install those dependencies in the other environment!

Virtualenv Anaconda

Use the pip package manager to install python dependencies. Before installing any Python dependencies, it is good practice to upgrade pip:

Upgrade pip
pip install --upgrade pip\n

Now, install any Python dependencies pip:

Install dependency with pip
pip install <PACKAGE>\n
Option 1: pipOption 2: conda

Use the pip package manager to install python dependencies.

Install dependency with pip
# Install pip using conda\nconda install pip\n\n# Install python packages using pip\npip install <PACKAGE>\n

Use the built-in conda package manager to install python dependencies.

Install dependency with conda
conda install -c <CHANNEL> <PACKAGE>\n

Sometimes, installing a package like this simply won't work because you are not installing from the correct channel. You usually will have to google the command to use in order to install your package correctly because it usually comes from a specific channel that you don't know about. Some common channels to try are:

  • conda-forge
  • anaconda
  • bioconda
  • r
"},{"location":"reference/python/virtual-environments/#deactivating-the-virtual-environment","title":"Deactivating the virtual environment","text":"

When you are finished using your virtual environment, you will need to deactivate it.

Virtualenv Anaconda Deactivate virtualenv environment
deactivate\n
Deactivate anaconda environment
conda deactivate\n
"},{"location":"reference/python/virtual-environments/#reproducing-your-virtual-environment","title":"Reproducing your virtual environment","text":"

When you want to share your code with others, it is important for others to be able to reproduce the environment that you worked in. We discuss two topics in this section: exporting your environment and reproducing the environment.

"},{"location":"reference/python/virtual-environments/#exporting-your-virtual-environment","title":"Exporting your virtual environment","text":"

In order to reproduce your virtual environment, you need to export some information about your environment. Be sure to follow the instructions below while your environment is activated.

Virtualenv Anaconda

You will create a requirements.txt file, which essentially lists all of your python dependencies in one file:

Creating requirements file
pip freeze > requirements.txt\n

The pip freeze command prints all of your pip dependencies, and > requirements.txt redirects the output to a text file.

Anaconda uses configuration files to recreate an environment.

Windows macOS Linux

Execute the following command to create a file called environment.yml:

Create config file
conda env export > environment.yml\n

Then, open the environment.yml file and delete the line with prefix:.

Execute the following command to create a file called environment.yml:

Create config file
conda env export | grep -v \"^prefix: \" > environment.yml\n

Execute the following command to create a file called environment.yml:

Create config file
conda env export | grep -v \"^prefix: \" > environment.yml\n
"},{"location":"reference/python/virtual-environments/#reproducing-the-environment","title":"Reproducing the environment","text":"

You can reproduce your virtual environment when given the information about it. The steps above tell you how to extract the information, and now we will use that information to recreate the virtual environment. Remember to deactivate the current environment before making a new environment.

Virtualenv Anaconda

We use the requirements.txt file that we generated earlier to recreate the environment.

Recreate virtualenv environment
# Create the new environment\npython -m venv <NEW ENV NAME>\n\n# Activate the environment\nsource <NEW ENV NAME>/bin/activate\n\n# Install dependencies\npip install -r <PATH TO requirements.txt file>\n

We use the environment.yml file that we generated earlier to recreate the environment.

Recreate the conda environment
# Create the new environment with the dependencies\nconda env create -f <PATH TO environment.yml> -n <ENV NAME>\n
"},{"location":"reference/python/virtual-environments/#official-references","title":"Official references","text":"

In this section, we summarized what virtual environments are, why they are used, and how to use them. We did not cover all of the functions of virtual environments, but feel free to consult the official references to learn about virtual environments more in depth.

  • Virtualenv Reference
  • Anaconda Reference
"},{"location":"reference/sailing/ais_terms/","title":"AIS Terms","text":"

This section explains the most unfamiliar fields that we receive from the AIS.

"},{"location":"reference/sailing/ais_terms/#mmsi-aka-id","title":"MMSI a.k.a ID","text":"

A 9-digit, unique identification number for the ship.

"},{"location":"reference/sailing/ais_terms/#cog-course-over-ground","title":"COG: Course over Ground","text":"

The direction the boat is travelling, relative to the sea floor. This is the direction of the rate of change of the Track Made Good.

This is measured with the navigational angle convention, where 0\u00b0 is towards the North, and angles increase in the clockwise direction. If we make the slight simplification of neglecting the effect of the wind, then

  • If the boatspeed is positive and there is no current, the boat's Course over Ground will be the same as the Heading.
  • If the boatspeed is zero and there is positive current, the boat's Course over Ground will be the same direction as the current is flowing.
"},{"location":"reference/sailing/ais_terms/#sog-speed-over-ground","title":"SOG: Speed over Ground","text":"

The speed the boat is travelling at, relative to the sea floor. This is the magnitude of the rate of change of the Track Made Good.

\\(\\begin{align*} \\text{SoG} &= \\left|\\frac{d}{dt} \\overrightarrow{(\\text{Track Made Good})} \\right|\\\\ \\end{align*}\\)

If we make the slight simplification of neglecting the effect of the wind, then

  • If the boatspeed is positive and there is no current, the boat's Speed over Ground will be the same as the speed of water hitting your hand, if you were sitting on the boat and put your hand in the water.
  • If the boatspeed is zero and there is positive current, the boat's Speed over Ground will be the same speed as the current.
"},{"location":"reference/sailing/ais_terms/#rot-rate-of-turn","title":"RoT: Rate of Turn","text":"

The angular velocity of the boat (how fast it's turning), measured in degrees per minute.

"},{"location":"reference/sailing/boat_parts/","title":"Parts of a Sailboat","text":"

This page names some important parts of a sailboat, and explains what the part is for. Read the descriptions of the parts below, and refer to the image to see where the part fits in.

"},{"location":"reference/sailing/boat_parts/#hull","title":"Hull","text":"

The Hull is the \"boat\" part of the boat, which displaces water to create buoyancy. The following parts of the boat are attached to the hull:

  • Keel: The keel has a large mass on the end, which keeps the sailboat upright. The fin-like shape of the keel provides lateral resistance to prevent the boat from slipping sideways through the water.
  • Rudder: Raye has two rudders for redundancy. The rudders can angle side to side to steer the boat. To steer the boat effectively, the rudders need enough water flowing over them to create a pressure difference when they angle sideways. Controls sends commands to the rudder to steer the boat.

It is also helpful to know the names of the following \"regions\" of the hull:

  • Bow: The front of the boat.
  • Stern: The back of the boat.
    • Aft means \"backwards towards the stern\".
  • Starboard: The side of the boat which is on the right, for someone standing on the boat facing the bow.
  • Port: The side of the boat which is on the left, for someone standing on the boat facing the bow.
    • To remember which is which between starboard and port, remember that \"port\" and \"left\" both have 4 letters.

The image below shows a birds-eye view of the outline of a hull of a sailboat, where the \"regions\" of the hull are labeled.

"},{"location":"reference/sailing/boat_parts/#jib","title":"Jib","text":"

The Jib is the sail located near the bow, and is the smaller of the two sails.

  • Jib Sheet: In general, sheets are ropes that pull a sail in to the boat, and the jib sheet does this for the jib. On Raye, the jib sheet connects to the back bottom corner of the jib, through a pulley near the bottom of the mast to the Jib Winch. Most sailboats have two jib sheets, one on either side, but Raye is designed differently for autonomy.
  • The Jib Winch is a motor-driven device that tightens or pulls in the jib by pulling on the jib sheet. Controls sends commands to the winches.
  • The jib halyard: In general, a halyard is a rope that pulls a sail up. The jib halyard pulls up the jib. It connects to the top of the jib, runs through a pulley near the top of the mast, and is tied off near the bottom of the mast.
"},{"location":"reference/sailing/boat_parts/#mast","title":"Mast","text":"

The Mast is the long vertical pole which connects to hull. It holds up the sails and some instruments.

The following instruments are at the top of the mast:

  • One of the 3 Wind Sensors. The top of the mast is a good location to measure undisturbed wind. Pathfinding and Controls both use data from the wind sensors.
  • The AIS antenna. AIS (\"Autonomous Identification System\") is a system by which ships communicate their location, speed, and other information to surrounding ships via radio signals. Pathfinding uses AIS data to avoid other ships.

The mast is held upright by three lines:

  • The forestay connects the mast from the top of the jib to the bow, and runs parallel to the front edge of the jib.
  • The two shrouds connect the mast from the top of the jib to the outside edges of the hull slightly aft of the mast. There is one shroud on the startboard side and one on the port side.
"},{"location":"reference/sailing/boat_parts/#main-sail","title":"Main Sail","text":"

The Main Sail is the larger of the two sails, and is located aft of the mast. Most of the boat's propulsion comes from the main sail.

  • The Boom is the horizontal pole that holds the bottom corner of the main sail out from the mast.
  • Main Sheet is the rope that pulls the main sail in towards the center of the boat. It connects from the back end of the boom, through a pulley on the stern, to the Main Winch.
  • The Main Winch is a motor-driven device that pulls in the main sail by pulling on the main sheet. Controls sends commands to the main winch.
  • The main halyard is the line used to hoist the main sail.
"},{"location":"reference/sailing/boat_parts/#conclusion","title":"Conclusion","text":"

Hopefully this section helped you gain familiarity with some common sailing terms. It likely feels like this section contains a lot of new information. It's unrealistic to remember it all perfectly, but make an effort to remember the terms which are Bolded and Italicized.

"},{"location":"reference/sailing/boat_parts/#keywords-on-this-page","title":"Keywords on this Page","text":"
  • Hull
  • Keel
  • Rudder
  • Bow
  • Stern
  • Starboard
  • Port
  • Jib
  • Jib winch
  • Mast
  • Wind Sensor
  • AIS Antenna
  • Main Sail
  • Main Winch
"},{"location":"reference/sailing/miscellaneous/","title":"Miscellaneous Sailing Knowledge","text":"

This section covers some other useful information.

"},{"location":"reference/sailing/miscellaneous/#wind-direction-convention","title":"Wind Direction Convention","text":"

Generally speaking, there are two ways to use an angle to describe the wind direction.

  1. The angle tells you which way the wind is blowing towards. For example, 0\u00b0 means the wind is blowing from North to South.
  2. The angle tells you which way the wind is coming from. For example, 0\u00b0 means the wind is blowing from South to North.

In sailing, we normally talk about \"where the wind is coming from\". Somehow this ends up being more intuitive when talking about maneuvers or sail angle adjustments.

However, when describing the wind as a vector, it can make more sense for the vector to represent the actual speed and direction the air is flowing. Make sure to document which convention you are using in your work when its applicable, and don't be afraid to ask someone to clarify which convention they are using in their work.

"},{"location":"reference/sailing/miscellaneous/#navigation-terms","title":"Navigation Terms","text":""},{"location":"reference/sailing/miscellaneous/#heading","title":"Heading","text":"

In navigation generally (outside of Sailbot), the Heading is the direction the bow of the boat is pointing towards. Headings are typically (but not always at Sailbot) measured relative to true North in the clockwise direction.

"},{"location":"reference/sailing/miscellaneous/#bearing","title":"Bearing","text":"

A Bearing is used to describe one point in relation to another: the Bearing of point \"A\" from point \"B\" is the direction you would would look towards if you wanted to see point \"A\" while standing at point \"B\". A Range is the distance between points \"A\" and \"B\", so that a Bearing and Range together can locate point \"A\" relative to point \"B\" in polar co-ordinates. There are two main ways of measuring bearings:

  • A True Bearing is a bearing where the angle convention is as follows: 0\u00b0 is towards the North, angles increase in the clockwise direction, and angles are typically bounded within [0\u00b0, 360\u00b0)]
  • A Relative Bearing is a bearing where the angle convention is as follows: 0\u00b0 is straight forwards relative to the boat, and angle measurements increase in the clockwise direction. Angles may be bounded in [-180\u00b0, 180\u00b0) or [0\u00b0, 360\u00b0)

In the example below, the boat \"B\" has a Heading (H) of 30\u00b0. The True Bearing (\\(B_t\\)) of the Lighthouse \"A\" from the boat is 90\u00b0. The Relative Bearing (\\(B_r\\)) of the lighthouse from the boat is 60\u00b0.

"},{"location":"reference/sailing/miscellaneous/#track-made-good","title":"Track Made Good","text":"

Boats do not necessarily travel in the same direction as their Heading, due to the effects of ocean current and wind. The path the boat has traveled relative to the sea floor is called the Track Made Good. This is the same as if you measured motion compared to land or with a GPS.

"},{"location":"reference/sailing/miscellaneous/#heading-and-bearing-in-raye-project","title":"Heading and Bearing in Raye Project","text":"

In Sailbot's Raye project, Heading and Bearing are used to refer to different conventions for describing which way the boat is pointing. The following 3 pieces of information are needed to unambiguously define an angle measuring convention:

  • What does 0\u00b0 mean? If 0\u00b0 is North, is it towards the North or away from the North?
  • Do the angle measurements increase in the clockwise or counter-clockwise direction?
  • What range should the angles be bounded to? This part is often unimportant if the angles are only used in trigonometry functions.

Some common examples of angle measuring conventions which we use are:

  • 0\u00b0 means towards the East, angles increase in the counter-clockwise direction, and angles are bounded in [-180\u00b0, 180\u00b0). This is effectively the main angle convention used in most math courses.
  • 0\u00b0 means towards the North, angles increase in the clockwise direction, and angles are bounded in [0\u00b0, 360\u00b0). This angle convention is more commonly used by navigators.

The specific angle conventions which we call Heading and Bearing can be ambiguous, and may be subject to change, so they are deliberately omitted here. Refer to the applicable source code to determine what the angle conventions are.

"},{"location":"reference/sailing/miscellaneous/#true-apparent-and-boat-wind","title":"True, Apparent, and Boat Wind","text":"
  • True Wind is the wind vector (speed and direction) which you would measure while standing on land (or motionless at sea with unchanging GPS co-ordinates). In sailbot code, this may be referred to as Global Wind. When people refer to \"the wind\", they normally mean True Wind.
  • Boat Wind is the wind vector which you would measure while standing on a moving boat when the True Wind speed is 0. This means that boat wind always blows straight onto the bow of the boat, and the magnitude of the boat wind is equal to the speed of the boat.
  • Apparent Wind is the vector sum of the True Wind and the Boat Wind. This is the wind that you would measure while standing on a moving boat more generally, even if there is non-zero wind. The apparent wind is also what our wind sensors measure, and what our sails feel. In Sailbot code, Apparent Wind may be referred to as Measured Wind.

In the example below, suppose the wind is blowing from the North at 4 m/s, and suppose the boat is moving towards the East at 3 m/s.

  • The True Wind everywhere is blowing at 4 m/s from the North
  • The Boat Wind onboard the boat is blowing from the East at 3 m/s
  • The Apparent Wind onboard the boat is has a magnitude of \\(\\sqrt{3^2 + 4^2} = 5 \\text{ m/s}\\), and is coming from a true bearing of \\(\\arctan{(\\frac{3}{4})} = 36.9\u00b0\\).

"},{"location":"reference/sailing/miscellaneous/#tack","title":"Tack","text":"

In the Types of Turn page, we discussed how a Tack is a type of turn. Weirdly, the word \"tack\" actually has two more distinct meanings in sailing. The word \"Tack\" can refer to:

  • the type of turn, as covered before.
  • Starboard Tack vs Port Tack: The tack is basically the side of the boat which is further upwind. More thoroughly, the tack is the opposite side to the sail. This means that boats change tack when the sail switches sides.
    • In the diagram below, the 3 boats on the left of the diagram are on Starboard Tack, and the 3 boats on the right side are on Port Tack.
    • The tack of a boat in Irons is undefined.
    • The boat in the diagram on a run is on Port Tack. If the boat continued straight but the sail switched sides into the position shown by the dashed line, the boat would be on Starboard Tack.

  • Finally, the Tack can refer to particular region of the main sail. This is not important for software members.
"},{"location":"reference/sailing/miscellaneous/#keywords-on-this-page","title":"Keywords on this Page","text":"
  • Heading
  • Bearing
  • Track Made Good
  • Global Wind (aka True Wind)
  • Measured Wind (aka Apparent Wind)
  • Tack
"},{"location":"reference/sailing/overview/","title":"Sailing Knowledge Section Overview","text":"

In order to make high-quality contributions to Sailbot's Software teams, it is extremely helpful to have some understanding of sailing. This section introduces important parts of a sailboat, explains the 4 types of turns, discusses upwind and downwind sailing, and covers some other helpful knowledge.

In this section, terms which are Bolded and Italicized are the most important terms to know. These terms are listed at the bottom of each page. Terms that are only Italicized are other helpful sailing terms. Words that are bolded are meant to be emphasized, but are not necessarily considered important vocabulary.

"},{"location":"reference/sailing/points_of_sail/","title":"Points of Sail","text":"

In sailing, we sometimes talk about different angles that we can sail on with respect to the wind. Ranges of angles which are close together have special names. These ranges are called points of sail. The discussion below coveres the most important points of sail for software members to understand.

Notice how for higher points of sail (points of sail closer to straight into the wind), the sail is pulled tightly in to the boat. If the boat is on a lower point of sail, the sails should be let further out of the boat. For any point of sail, there is an optimum angle that the sail should be adjusted to. If the sails are adjusted too far in or too far out, the boat will not go as fast as it could if the sails were adjusted correctly.

"},{"location":"reference/sailing/points_of_sail/#irons","title":"Irons","text":"

The range of angles where the boat is roughly pointing straight into the wind are called Irons, or the No-Go Zone. If the boat is pointing in these directions, the sails will be flapping regardless of how the sheets are adjusted. When the sails are flapping, they are not catching the wind in a way that can propell the boat forwards. When the boat looses propulsion, water stops flowing over the rudder, and the boat loses steering. This is why we want our sailbots to avoid being stuck in irons.

"},{"location":"reference/sailing/points_of_sail/#upwind-sailing","title":"Upwind Sailing","text":"

If we want to sail to a destination that is not on too high or low of an angle upwind or downwind from our starting position, we can just point our boat in that direction, adjust our sails, and go there.

However, sometimes we want to sail to a destination that is straight upwind of our starting position. To get there, we will need to do upwind sailing. Since we can't point our boat directly into the wind, we need to sail on an angle on the edge of irons. We will need to tack back and forth every now and then if we want to go directly upwind. The point of sail on the edge of Irons is called Close Hauled.

"},{"location":"reference/sailing/points_of_sail/#downwind-sailing","title":"Downwind Sailing","text":"

Raye also avoids sailing straight downwind. This means that to reach a goal downwind of the starting position, we need to gybe back and forth in a zig-zag pattern. The point of sail straight downwind is called a run, and the next point of sail higher than a run is called a broad reach.

"},{"location":"reference/sailing/points_of_sail/#keywords-on-this-page","title":"Keywords on this Page","text":"
  • Irons (aka No-Go Zone)
  • Upwind Sailing
  • Close Hauled
  • Downwind Sailing
"},{"location":"reference/sailing/turning/","title":"Types of Turns","text":"

In sailing, there are 4 distinct types of turns. Read the descriptions below, and observe how they fit into the diagrams.

Note that any of these types of turn can be done in either the clockwise or counter-clockwise directions.

"},{"location":"reference/sailing/turning/#classifying-types-of-turns-summary","title":"Classifying Types Of Turns Summary","text":"

The following flowchart summarizes how to distinguish between different types of turns. Note:

  • to point higher means to steer your boat to point in a direction closer to straight into the wind
  • to point lower means to steer your boat to point in a direction closer towards to straight downwind
graph LR\n    B[Classify a Turn] --> C{Does the sail change<br/>sides during the turn?};\n    C --> |Yes| E{Which end of<br/>the boat is upwind<br/>during the turn?};\n    C --> |No| D{Does the<br/>boat point higher<br/>or lower at the end<br/>of the turn?};\n    D --> |Higher| F[Heading Up];\n    D --> |Lower| G[Bearing Off];\n    E --> |Bow| H[Tack];\n    E --> |Stern| I[Gybe];

The diagrams in this section show outlines of the hull of a boat and its main sail going through turns. As is common in these types of diagrams, assume that the wind is blowing down from the top of the screen unless there is an arrow that indicates otherwise.

"},{"location":"reference/sailing/turning/#heading-up","title":"Heading Up","text":"

When the boat makes any turn as follows, it is called Heading Up:

  • At the end of the turn, the boat is pointing higher.
  • Throughout the turn, the sails stay on the same side of the boat. In other words, the sails do not cross between the starboard and port sides.

Unlike some of the other turns listed here, heading up can be a large turn or a small course adjustment of just a few degrees.

The image below shows a boat heading up. Notice how the sail stays on the starboard side of the boat.

"},{"location":"reference/sailing/turning/#bearing-off","title":"Bearing Off","text":"

When the boat makes any turn as follows, it is called Bearing Off:

  • At the end of the turn, the boat is pointing lower.
  • Throughout the turn, the sails stays on the same side of the boat (port or starboard).

Like heading up, bearing off can be a small course adjustment.

"},{"location":"reference/sailing/turning/#tacking","title":"Tacking","text":"

When the boat makes any turn as follows, it is called a Tack or Tacking:

  • The sails change sides.
  • Through the turn, the wind hits the bow of the boat before the stern. You can also say that the bow is upwind or windward of the stern.

Notice how at some point throughout this turn, the boat will be pointing straight into the wind. While the boat points nearly straight into the wind, the sails don't generate any forward propulsion. This means that a tack must be a large (at least ~90\u00b0) turn all at once, so that the boat's momentum carries it through the range of angles where it does not get propulsion.

"},{"location":"reference/sailing/turning/#gybing","title":"Gybing","text":"

When the boat makes any turn as follows, it is called a Gybe or Gybing.

  • The sails change sides.
  • Through the turn, the wind hits the stern of the boat before the bow. You can also say that the bow of the boat is downwind or leeward of the stern.

When sailing on most angles relative to the wind, the sail is always blown to the downwind side of the boat. However, sailing nearly straight downwind, both sides of the boat are equally \"downwind\" relative to eachother. This means that the sail can be on either side of the boat.

The sail propells the boat throughout a gybe, so it is possible to conduct the turn more gradually than a tack. However, because the sail can be on either side, the sails can switch sides in an uncontrolled way as the boat moves in the waves. For this reason, Raye avoids sailing on angles close to straight downwind, and gybes by doing a quick ~60\u00b0 turn.

Note that \"gybe\" is the spelling used in Canadian and British english, whereas in American english it is spelled \"Jibe\"

"},{"location":"reference/sailing/turning/#combinations-of-turns","title":"Combinations of Turns","text":"

Of course, it is possible to do two or more of these types of turns in one continuous motion. What two types of turns does the boat do in the image below?

Answer: In the turn shown by the first arrow, the sail stays on the port side of the boat while it steers to point further downwind. This means that the first part of the maneuver is bearing off. In the next part of the maneuver, the sail changes sides and the stern of the boat is upwind of the bow. This part of the maneuver is a gybe.

"},{"location":"reference/sailing/turning/#keywords-on-this-page","title":"Keywords on this Page","text":"
  • Higher (in relation to pointing)
  • Lower (in relation to pointing)
  • Heading Up
  • Bearing Off
  • Tack
  • Gybe (aka Jibe)
"}]} \ No newline at end of file diff --git a/pr-248/sitemap.xml b/pr-248/sitemap.xml deleted file mode 100644 index 339237145..000000000 --- a/pr-248/sitemap.xml +++ /dev/null @@ -1,183 +0,0 @@ - - - - https://UBCSailbot.github.io/docs/pr-248/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/current/overview/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/current/boat_simulator/overview/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/current/controller/overview/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/current/custom_interfaces/overview/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/current/docs/overview/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/current/local_pathfinding/overview/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/current/network_systems/overview/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/current/notebooks/overview/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/current/sailbot_workspace/deployment/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/current/sailbot_workspace/docker_images/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/current/sailbot_workspace/how_to/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/current/sailbot_workspace/launch_files/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/current/sailbot_workspace/overview/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/current/sailbot_workspace/parameters/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/current/sailbot_workspace/setup/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/current/sailbot_workspace/workflow/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/current/website/overview/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/markdown/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/ros/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/cpp/differences/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/cpp/start/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/cpp/tools/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/github/workflow/branches/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/github/workflow/issues/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/github/workflow/overview/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/github/workflow/pr/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/python/conventions/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/python/start/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/python/virtual-environments/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/sailing/ais_terms/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/sailing/boat_parts/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/sailing/miscellaneous/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/sailing/overview/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/sailing/points_of_sail/ - 2024-03-08 - daily - - - https://UBCSailbot.github.io/docs/pr-248/reference/sailing/turning/ - 2024-03-08 - daily - - \ No newline at end of file diff --git a/pr-248/sitemap.xml.gz b/pr-248/sitemap.xml.gz deleted file mode 100644 index acd9433c0..000000000 Binary files a/pr-248/sitemap.xml.gz and /dev/null differ diff --git a/pr-248/stylesheets/extra.css b/pr-248/stylesheets/extra.css deleted file mode 100644 index 3c68478c4..000000000 --- a/pr-248/stylesheets/extra.css +++ /dev/null @@ -1,26 +0,0 @@ -:root > * { - --md-primary-fg-color: #1665a2; - --md-primary-fg-color--light: #73a3c7; - --md-primary-fg-color--dark: #0d3d61; -} - -[data-md-color-scheme="slate"] { - --md-hue: 200; -} - -/* Styling for left navigation panel*/ -.md-sidebar__inner > .md-nav > .md-nav__list > .md-nav__item > .md-nav__link { color: #2f97ec; } -.md-nav__link:hover { color: #2f97ec; } -.md-nav__link.md-nav__link--active { color: #2f97ec; } - -/* Styling for links embedded in text*/ -.md-content__inner a {color: #2f97ec;} -.md-content__inner a:hover {color: #2f97ec;} -.md-content__inner a:focus {color: #2f97ec;} - -/* Styling for right navigation panel (Table of Contents) controlled by javascripts/table_of_contents_themes.js */ -nav.md-nav.md-nav--secondary > .md-nav__list > .md-nav__item > .md-nav__link {color: var(--md-table-of-contents-link-color);} -nav.md-nav.md-nav--secondary > .md-nav__list > .md-nav__item > .md-nav__link:hover {color: #2f97ec;} -nav.md-nav.md-nav--secondary > .md-nav__list > .md-nav__item > .md-nav__link.md-nav__link--passed.md-nav__link--active {color: #2f97ec;} -.md-nav__link--passed {color: var(--md-table-of-contents-link-color);} -.md-nav__link--passed:hover {color: #2f97ec;} diff --git a/versions.json b/versions.json index ece4bd533..fe51488c7 100644 --- a/versions.json +++ b/versions.json @@ -1,7 +1 @@ -[ - { - "version": "pr-248", - "title": "pr-248", - "aliases": [] - } -] +[]