diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..7285cd0 --- /dev/null +++ b/404.html @@ -0,0 +1,1404 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..fa64a66 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +netherlands3d.eu \ No newline at end of file diff --git a/_nieuws/index.html b/_nieuws/index.html new file mode 100644 index 0000000..76428de --- /dev/null +++ b/_nieuws/index.html @@ -0,0 +1,1465 @@ + + + + + + + + + + + + + + + + + + + nieuws - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Ga naar inhoud + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

nieuws

+ +

Nieuws

+
+
    +
  • +

    +

    Beginnen in 5 minuten

    +

    Ontdek de mogelijkheden van Netherlands3D.

    +

    Lees verder

    +
  • +
  • +

    +

    Je eigen cloud, of niet?

    +

    Beheer je Netherlands3D zelf of laat je je ontzorgen?

    +

    Vind het uit

    +
  • +
  • +

    +

    Toon de kaart die jij wil

    +

    Wil je jouw project, stad, gemeente of provincie laten zien? Dat kan.

    +

    Ontdek de opties

    +
  • +
+
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/_uitgelicht/index.html b/_uitgelicht/index.html new file mode 100644 index 0000000..8c8fc02 --- /dev/null +++ b/_uitgelicht/index.html @@ -0,0 +1,1465 @@ + + + + + + + + + + + + + + + + + + + uitgelicht - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Ga naar inhoud + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

uitgelicht

+ +

Uitgelicht

+
+
    +
  • +

    Beginnen in 5 minuten

    +
    +

    Ontdek de mogelijkheden van Netherlands3D.

    +

    Lees verder

    +
  • +
  • +

    Je eigen cloud, of niet?

    +
    +

    Beheer je Netherlands3D zelf of laat je je ontzorgen?

    +

    Vind het uit

    +
  • +
  • +

    Toon de kaart die jij wil

    +
    +

    Wil je jouw project, stad, gemeente of provincie laten zien? Dat kan.

    +

    Ontdek de opties

    +
  • +
+
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/_wat-is-netherlands3d/index.html b/_wat-is-netherlands3d/index.html new file mode 100644 index 0000000..c67ada0 --- /dev/null +++ b/_wat-is-netherlands3d/index.html @@ -0,0 +1,1547 @@ + + + + + + + + + + + + + + + + + + + wat is netherlands3d - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Ga naar inhoud + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + +

wat is netherlands3d

+ +

Wat is Netherlands3D?

+
+

Visualiseren van data

+
+

Zoek je een mogelijkheid om data aantrekkelijk en in samenhang in +beeld te brengen in een 3D omgeving? Dat kan in Netherlands 3D!

+

Netherlands3D maakt het mogelijk datasets vanaf verschillende bronnen +en in verschillende bestandsformaten te koppelen en in samenhang te +visualiseren. Heb je een interessante dataset of link in GeoJSON of WFS +formaat? Ontdek dan hoe deze er in Netherlands 3D uit ziet. +Gecombineerd met andere datasets en in 3D context geeft dit vaak nèt +dat beetje extra informatie dat je nodig hebt.

+

Naast het visualiseren van data, zijn er ook een aantal functionaliteiten +beschikbaar die je kunt inzetten om jouw project tot een succes te +maken. Kies de relevante tools simpelweg bij het opstarten van +Netherlands 3D.

+
+
+
+

Wat onderscheid Netherlands3D van andere viewers?

+
+

Netherlands 3D richt zich op data-visualisatie. Het aantrekkelijk, +integraal en begrijpelijk verbeelden van data staat centraal.

+

Wat Netherlands 3D verder onderscheid is dat jij als gebruiker zelf kiest +welke data er gevisualiseerd wordt en welke functionaliteiten je daarbij +wilt gebruiken. Dus geen lijsten met vooraf gedefinieerde kaartlagen +maar slechts koppelmogelijkheden naar databronnen en externe +applicaties waar jij op dat moment in jouw project behoefte aan hebt. +Denk aan bronnen van jouw lokale server, het landelijke dataloket PDOK +of diverse dataportalen van gemeentelijke en provinciale organisaties.

+
+
+
+

Heel Nederland uniform en consistent beschikbaar

+
+

Netherlands 3D combineert voor heel Nederland een aantal basis +datasets waardoor een eenduidig 3D beeld van Nederland ontstaat.

+

Op dit moment zijn dat de 3D BAG, de 3D Basisvoorziening en boomlocaties +uit de BGT. Het gezamenlijke 3D beeld dat ontstaat kun je gebruiken als +input voor jouw project. Groot voordeel is dat deze brondata voor heel +Nederland uniform en consistent beschikbaar wordt gemaakt. Dus altijd +gemakkelijk en vanaf één plek te gebruiken. Het kan ook input zijn als +basis voor rekenmodellen en simulatiesoftware of juist de output +daarvan zijn zodat uitkomsten aan een breed publiek getoond kunnen +worden. Voor het realiseren van koppelingen tussen Netherlands 3D en +reken- simulatie- en analysetools, nodigen we iedereen actief uit om +mee te ontwikkelen.

+
+
+
+

3D omgeving op maat

+
+

Wil iets specifieks dat meer aansluit bij jouw project of organisatie?

+

Netherlands 3D biedt een oplossing om een eigen 3D omgeving op maat +in te richten. Bijvoorbeeld een aangepaste startlocatie of vooraf +ingestelde functionaliteiten en datasets. Meldt je dan als organisatie aan. +Tot slot is er voor ontwikkelaars de mogelijkheid om actief mee te +bouwen aan het platform.

+

We nodigen iedereen dan ook actief uit om +met ons mee te denken en te bouwen. En wil je dat allemaal niet? +Gebruik dan gewoon de basisviewer van Netherlands 3D hier!

+
+
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/android-chrome-192x192.png b/android-chrome-192x192.png new file mode 100644 index 0000000..6b8ab2e Binary files /dev/null and b/android-chrome-192x192.png differ diff --git a/android-chrome-512x512.png b/android-chrome-512x512.png new file mode 100644 index 0000000..a1fc745 Binary files /dev/null and b/android-chrome-512x512.png differ diff --git a/apple-touch-icon.png b/apple-touch-icon.png new file mode 100644 index 0000000..2af27d7 Binary files /dev/null and b/apple-touch-icon.png differ diff --git a/articles/2024/04/25/nieuwe-website/index.html b/articles/2024/04/25/nieuwe-website/index.html new file mode 100644 index 0000000..5169e32 --- /dev/null +++ b/articles/2024/04/25/nieuwe-website/index.html @@ -0,0 +1,1525 @@ + + + + + + + + + + + + + + + + + + + Nieuwe website - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Ga naar inhoud + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + +

Nieuwe website

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sodales est nec dolor luctus cursus. Sed tincidunt orci +fermentum varius mattis. Quisque ut molestie augue, sit amet ornare sem. Praesent sit amet auctor nisi. Vivamus ornare +tincidunt arcu in euismod. Proin ipsum ante, semper et sollicitudin eget, venenatis maximus nulla. Suspendisse potenti. +Nulla facilisi.

+ + +

Fusce rutrum mi vel purus interdum imperdiet. Curabitur tempus fermentum odio et venenatis. In hac habitasse platea +dictumst. Praesent pellentesque odio vel suscipit malesuada. Nulla facilisis mauris euismod pulvinar ullamcorper. Ut +tincidunt lacus vitae nibh faucibus iaculis. Phasellus vitae risus ex.

+

Donec at orci urna. Ut ac turpis nec quam luctus cursus. Nunc lobortis quam vitae iaculis dignissim. Vestibulum pretium, +nulla id volutpat luctus, mauris leo rhoncus tortor, ut fringilla massa purus in erat. Donec blandit id sem et accumsan. +Pellentesque at eleifend urna. Mauris quis odio odio. Interdum et malesuada fames ac ante ipsum primis in faucibus. +Vivamus sollicitudin magna libero, eget congue odio mollis gravida. Praesent arcu tellus, ultrices id porttitor vitae, +tincidunt quis dolor. Vivamus aliquet nulla non augue molestie, sed elementum nisl maximus. Curabitur ut augue eu urna +ornare hendrerit sed at dolor. Duis vulputate ullamcorper viverra. Proin id urna sed turpis euismod blandit. Etiam +gravida ipsum eu ligula lacinia, ut ultricies magna porta. Cras sem metus, commodo tincidunt ultricies fringilla, +facilisis sit amet enim.

+

Pellentesque nibh dui, iaculis at vehicula in, iaculis ut dui. Donec ac nisl eget massa pellentesque vehicula. Morbi +interdum magna ac leo malesuada commodo. Nulla ac risus ut lectus eleifend semper. Nullam placerat sem quis lectus +aliquam tristique. Nullam ante nisl, laoreet ut elit eu, laoreet gravida ex. Aenean eget sagittis magna. Donec non +faucibus leo. Nullam rutrum euismod nisl, sed consequat arcu imperdiet et. Vivamus sit amet mi placerat, luctus sem a, +posuere sem. Aliquam feugiat est eget risus dapibus efficitur.

+

Nam vehicula sollicitudin nibh, at efficitur ligula tincidunt in. Pellentesque turpis nibh, tempor vitae consequat non, +molestie ac turpis. Duis tempor vulputate urna, non iaculis velit faucibus vel. Aliquam erat volutpat. Donec eu +porttitor lacus, in dignissim odio. Nam at laoreet sapien, pharetra scelerisque sapien. Nulla sed quam pretium, +pellentesque dolor pharetra, aliquam nulla.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/articles/archive/2024/index.html b/articles/archive/2024/index.html new file mode 100644 index 0000000..0b8897f --- /dev/null +++ b/articles/archive/2024/index.html @@ -0,0 +1,1465 @@ + + + + + + + + + + + + + + + + + + + 2024 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Ga naar inhoud + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+

2024

+
+ +
+
+ + +
+
+

Nieuwe website

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sodales est nec dolor luctus cursus. Sed tincidunt orci +fermentum varius mattis. Quisque ut molestie augue, sit amet ornare sem. Praesent sit amet auctor nisi. Vivamus ornare +tincidunt arcu in euismod. Proin ipsum ante, semper et sollicitudin eget, venenatis maximus nulla. Suspendisse potenti. +Nulla facilisi.

+ + + + +
+
+ + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/articles/index.html b/articles/index.html new file mode 100644 index 0000000..5d3668b --- /dev/null +++ b/articles/index.html @@ -0,0 +1,1469 @@ + + + + + + + + + + + + + + + + + + + Artikelen - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Ga naar inhoud + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+

Artikelen

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sodales est nec dolor luctus cursus. Sed tincidunt orci +fermentum varius mattis. Quisque ut molestie augue, sit amet ornare sem. Praesent sit amet auctor nisi. Vivamus ornare +tincidunt arcu in euismod. Proin ipsum ante, semper et sollicitudin eget, venenatis maximus nulla. Suspendisse potenti. +Nulla facilisi.

+
+ +
+
+ + +
+
+

Nieuwe website

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sodales est nec dolor luctus cursus. Sed tincidunt orci +fermentum varius mattis. Quisque ut molestie augue, sit amet ornare sem. Praesent sit amet auctor nisi. Vivamus ornare +tincidunt arcu in euismod. Proin ipsum ante, semper et sollicitudin eget, venenatis maximus nulla. Suspendisse potenti. +Nulla facilisi.

+ + + + +
+
+ + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/articles/nieuws/index.html b/articles/nieuws/index.html new file mode 100644 index 0000000..a2091d0 --- /dev/null +++ b/articles/nieuws/index.html @@ -0,0 +1,1465 @@ + + + + + + + + + + + + + + + + + + + Nieuws - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Ga naar inhoud + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+

Nieuws

+
+ +
+
+ + +
+
+

Nieuwe website

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sodales est nec dolor luctus cursus. Sed tincidunt orci +fermentum varius mattis. Quisque ut molestie augue, sit amet ornare sem. Praesent sit amet auctor nisi. Vivamus ornare +tincidunt arcu in euismod. Proin ipsum ante, semper et sollicitudin eget, venenatis maximus nulla. Suspendisse potenti. +Nulla facilisi.

+ + + + +
+
+ + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000..1cf13b9 Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/javascripts/bundle.83f73b43.min.js b/assets/javascripts/bundle.83f73b43.min.js new file mode 100644 index 0000000..43d8b70 --- /dev/null +++ b/assets/javascripts/bundle.83f73b43.min.js @@ -0,0 +1,16 @@ +"use strict";(()=>{var Wi=Object.create;var gr=Object.defineProperty;var Di=Object.getOwnPropertyDescriptor;var Vi=Object.getOwnPropertyNames,Vt=Object.getOwnPropertySymbols,Ni=Object.getPrototypeOf,yr=Object.prototype.hasOwnProperty,ao=Object.prototype.propertyIsEnumerable;var io=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,$=(e,t)=>{for(var r in t||(t={}))yr.call(t,r)&&io(e,r,t[r]);if(Vt)for(var r of Vt(t))ao.call(t,r)&&io(e,r,t[r]);return e};var so=(e,t)=>{var r={};for(var o in e)yr.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Vt)for(var o of Vt(e))t.indexOf(o)<0&&ao.call(e,o)&&(r[o]=e[o]);return r};var xr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var zi=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Vi(t))!yr.call(e,n)&&n!==r&&gr(e,n,{get:()=>t[n],enumerable:!(o=Di(t,n))||o.enumerable});return e};var Mt=(e,t,r)=>(r=e!=null?Wi(Ni(e)):{},zi(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var co=(e,t,r)=>new Promise((o,n)=>{var i=p=>{try{s(r.next(p))}catch(c){n(c)}},a=p=>{try{s(r.throw(p))}catch(c){n(c)}},s=p=>p.done?o(p.value):Promise.resolve(p.value).then(i,a);s((r=r.apply(e,t)).next())});var lo=xr((Er,po)=>{(function(e,t){typeof Er=="object"&&typeof po!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Er,function(){"use strict";function e(r){var o=!0,n=!1,i=null,a={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 s(k){return!!(k&&k!==document&&k.nodeName!=="HTML"&&k.nodeName!=="BODY"&&"classList"in k&&"contains"in k.classList)}function p(k){var ft=k.type,qe=k.tagName;return!!(qe==="INPUT"&&a[ft]&&!k.readOnly||qe==="TEXTAREA"&&!k.readOnly||k.isContentEditable)}function c(k){k.classList.contains("focus-visible")||(k.classList.add("focus-visible"),k.setAttribute("data-focus-visible-added",""))}function l(k){k.hasAttribute("data-focus-visible-added")&&(k.classList.remove("focus-visible"),k.removeAttribute("data-focus-visible-added"))}function f(k){k.metaKey||k.altKey||k.ctrlKey||(s(r.activeElement)&&c(r.activeElement),o=!0)}function u(k){o=!1}function d(k){s(k.target)&&(o||p(k.target))&&c(k.target)}function y(k){s(k.target)&&(k.target.classList.contains("focus-visible")||k.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(k.target))}function L(k){document.visibilityState==="hidden"&&(n&&(o=!0),X())}function X(){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(k){k.target.nodeName&&k.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",L,!0),X(),r.addEventListener("focus",d,!0),r.addEventListener("blur",y,!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 qr=xr((hy,On)=>{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var $a=/["'&<>]/;On.exports=Pa;function Pa(e){var t=""+e,r=$a.exec(t);if(!r)return t;var o,n="",i=0,a=0;for(i=r.index;i{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof It=="object"&&typeof Yr=="object"?Yr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof It=="object"?It.ClipboardJS=r():t.ClipboardJS=r()})(It,function(){return function(){var e={686:function(o,n,i){"use strict";i.d(n,{default:function(){return Ui}});var a=i(279),s=i.n(a),p=i(370),c=i.n(p),l=i(817),f=i.n(l);function u(V){try{return document.execCommand(V)}catch(A){return!1}}var d=function(A){var M=f()(A);return u("cut"),M},y=d;function L(V){var A=document.documentElement.getAttribute("dir")==="rtl",M=document.createElement("textarea");M.style.fontSize="12pt",M.style.border="0",M.style.padding="0",M.style.margin="0",M.style.position="absolute",M.style[A?"right":"left"]="-9999px";var F=window.pageYOffset||document.documentElement.scrollTop;return M.style.top="".concat(F,"px"),M.setAttribute("readonly",""),M.value=V,M}var X=function(A,M){var F=L(A);M.container.appendChild(F);var D=f()(F);return u("copy"),F.remove(),D},te=function(A){var M=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},F="";return typeof A=="string"?F=X(A,M):A instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(A==null?void 0:A.type)?F=X(A.value,M):(F=f()(A),u("copy")),F},J=te;function k(V){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?k=function(M){return typeof M}:k=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},k(V)}var ft=function(){var A=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},M=A.action,F=M===void 0?"copy":M,D=A.container,Y=A.target,$e=A.text;if(F!=="copy"&&F!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(Y!==void 0)if(Y&&k(Y)==="object"&&Y.nodeType===1){if(F==="copy"&&Y.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(F==="cut"&&(Y.hasAttribute("readonly")||Y.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($e)return J($e,{container:D});if(Y)return F==="cut"?y(Y):J(Y,{container:D})},qe=ft;function Fe(V){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Fe=function(M){return typeof M}:Fe=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},Fe(V)}function ki(V,A){if(!(V instanceof A))throw new TypeError("Cannot call a class as a function")}function no(V,A){for(var M=0;M0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof D.action=="function"?D.action:this.defaultAction,this.target=typeof D.target=="function"?D.target:this.defaultTarget,this.text=typeof D.text=="function"?D.text:this.defaultText,this.container=Fe(D.container)==="object"?D.container:document.body}},{key:"listenClick",value:function(D){var Y=this;this.listener=c()(D,"click",function($e){return Y.onClick($e)})}},{key:"onClick",value:function(D){var Y=D.delegateTarget||D.currentTarget,$e=this.action(Y)||"copy",Dt=qe({action:$e,container:this.container,target:this.target(Y),text:this.text(Y)});this.emit(Dt?"success":"error",{action:$e,text:Dt,trigger:Y,clearSelection:function(){Y&&Y.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(D){return vr("action",D)}},{key:"defaultTarget",value:function(D){var Y=vr("target",D);if(Y)return document.querySelector(Y)}},{key:"defaultText",value:function(D){return vr("text",D)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(D){var Y=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return J(D,Y)}},{key:"cut",value:function(D){return y(D)}},{key:"isSupported",value:function(){var D=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],Y=typeof D=="string"?[D]:D,$e=!!document.queryCommandSupported;return Y.forEach(function(Dt){$e=$e&&!!document.queryCommandSupported(Dt)}),$e}}]),M}(s()),Ui=Fi},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 a(s,p){for(;s&&s.nodeType!==n;){if(typeof s.matches=="function"&&s.matches(p))return s;s=s.parentNode}}o.exports=a},438:function(o,n,i){var a=i(828);function s(l,f,u,d,y){var L=c.apply(this,arguments);return l.addEventListener(u,L,y),{destroy:function(){l.removeEventListener(u,L,y)}}}function p(l,f,u,d,y){return typeof l.addEventListener=="function"?s.apply(null,arguments):typeof u=="function"?s.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(L){return s(L,f,u,d,y)}))}function c(l,f,u,d){return function(y){y.delegateTarget=a(y.target,f),y.delegateTarget&&d.call(l,y)}}o.exports=p},879:function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[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 a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(o,n,i){var a=i(879),s=i(438);function p(u,d,y){if(!u&&!d&&!y)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(y))throw new TypeError("Third argument must be a Function");if(a.node(u))return c(u,d,y);if(a.nodeList(u))return l(u,d,y);if(a.string(u))return f(u,d,y);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(u,d,y){return u.addEventListener(d,y),{destroy:function(){u.removeEventListener(d,y)}}}function l(u,d,y){return Array.prototype.forEach.call(u,function(L){L.addEventListener(d,y)}),{destroy:function(){Array.prototype.forEach.call(u,function(L){L.removeEventListener(d,y)})}}}function f(u,d,y){return s(document.body,u,d,y)}o.exports=p},817:function(o){function n(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var p=window.getSelection(),c=document.createRange();c.selectNodeContents(i),p.removeAllRanges(),p.addRange(c),a=p.toString()}return a}o.exports=n},279:function(o){function n(){}n.prototype={on:function(i,a,s){var p=this.e||(this.e={});return(p[i]||(p[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var p=this;function c(){p.off(i,c),a.apply(s,arguments)}return c._=a,this.on(i,c,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),p=0,c=s.length;for(p;p0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[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 N(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],a;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(s){a={error:s}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(a)throw a.error}}return i}function q(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||p(d,L)})},y&&(n[d]=y(n[d])))}function p(d,y){try{c(o[d](y))}catch(L){u(i[0][3],L)}}function c(d){d.value instanceof nt?Promise.resolve(d.value.v).then(l,f):u(i[0][2],d)}function l(d){p("next",d)}function f(d){p("throw",d)}function u(d,y){d(y),i.shift(),i.length&&p(i[0][0],i[0][1])}}function uo(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 he=="function"?he(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(a){return new Promise(function(s,p){a=e[i](a),n(s,p,a.done,a.value)})}}function n(i,a,s,p){Promise.resolve(p).then(function(c){i({value:c,done:s})},a)}}function H(e){return typeof e=="function"}function ut(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 zt=ut(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 Qe(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ue=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 a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=he(a),p=s.next();!p.done;p=s.next()){var c=p.value;c.remove(this)}}catch(L){t={error:L}}finally{try{p&&!p.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var l=this.initialTeardown;if(H(l))try{l()}catch(L){i=L instanceof zt?L.errors:[L]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=he(f),d=u.next();!d.done;d=u.next()){var y=d.value;try{ho(y)}catch(L){i=i!=null?i:[],L instanceof zt?i=q(q([],N(i)),N(L.errors)):i.push(L)}}}catch(L){o={error:L}}finally{try{d&&!d.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new zt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)ho(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)&&Qe(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Qe(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Tr=Ue.EMPTY;function qt(e){return e instanceof Ue||e&&"closed"in e&&H(e.remove)&&H(e.add)&&H(e.unsubscribe)}function ho(e){H(e)?e():e.unsubscribe()}var Pe={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var dt={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,a=n.isStopped,s=n.observers;return i||a?Tr:(this.currentObservers=null,s.push(r),new Ue(function(){o.currentObservers=null,Qe(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,a=o.isStopped;n?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new j;return r.source=this,r},t.create=function(r,o){return new To(r,o)},t}(j);var To=function(e){oe(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:Tr},t}(g);var _r=function(e){oe(t,e);function t(r){var o=e.call(this)||this;return o._value=r,o}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var o=e.prototype._subscribe.call(this,r);return!o.closed&&r.next(this._value),o},t.prototype.getValue=function(){var r=this,o=r.hasError,n=r.thrownError,i=r._value;if(o)throw n;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t}(g);var At={now:function(){return(At.delegate||Date).now()},delegate:void 0};var Ct=function(e){oe(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=At);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,a=o._infiniteTimeWindow,s=o._timestampProvider,p=o._windowTime;n||(i.push(r),!a&&i.push(s.now()+p)),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,a=n._buffer,s=a.slice(),p=0;p0?e.prototype.schedule.call(this,r,o):(this.delay=o,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,o){return o>0||this.closed?e.prototype.execute.call(this,r,o):this._execute(r,o)},t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!=null&&n>0||n==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.flush(this),0)},t}(gt);var Lo=function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t}(yt);var kr=new Lo(Oo);var Mo=function(e){oe(t,e);function t(r,o){var n=e.call(this,r,o)||this;return n.scheduler=r,n.work=o,n}return t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!==null&&n>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=vt.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 a=r.actions;o!=null&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==o&&(vt.cancelAnimationFrame(o),r._scheduled=void 0)},t}(gt);var _o=function(e){oe(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}(yt);var me=new _o(Mo);var S=new j(function(e){return e.complete()});function Yt(e){return e&&H(e.schedule)}function Hr(e){return e[e.length-1]}function Xe(e){return H(Hr(e))?e.pop():void 0}function ke(e){return Yt(Hr(e))?e.pop():void 0}function Bt(e,t){return typeof Hr(e)=="number"?e.pop():t}var xt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Gt(e){return H(e==null?void 0:e.then)}function Jt(e){return H(e[bt])}function Xt(e){return Symbol.asyncIterator&&H(e==null?void 0:e[Symbol.asyncIterator])}function Zt(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 Zi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var er=Zi();function tr(e){return H(e==null?void 0:e[er])}function rr(e){return fo(this,arguments,function(){var r,o,n,i;return Nt(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,nt(r.read())];case 3:return o=a.sent(),n=o.value,i=o.done,i?[4,nt(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,nt(n)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function or(e){return H(e==null?void 0:e.getReader)}function U(e){if(e instanceof j)return e;if(e!=null){if(Jt(e))return ea(e);if(xt(e))return ta(e);if(Gt(e))return ra(e);if(Xt(e))return Ao(e);if(tr(e))return oa(e);if(or(e))return na(e)}throw Zt(e)}function ea(e){return new j(function(t){var r=e[bt]();if(H(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function ta(e){return new j(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?b(function(n,i){return e(n,i,o)}):le,Te(1),r?De(t):Qo(function(){return new ir}))}}function jr(e){return e<=0?function(){return S}:E(function(t,r){var o=[];t.subscribe(T(r,function(n){o.push(n),e=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new g}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,p=s===void 0?!0:s;return function(c){var l,f,u,d=0,y=!1,L=!1,X=function(){f==null||f.unsubscribe(),f=void 0},te=function(){X(),l=u=void 0,y=L=!1},J=function(){var k=l;te(),k==null||k.unsubscribe()};return E(function(k,ft){d++,!L&&!y&&X();var qe=u=u!=null?u:r();ft.add(function(){d--,d===0&&!L&&!y&&(f=Ur(J,p))}),qe.subscribe(ft),!l&&d>0&&(l=new at({next:function(Fe){return qe.next(Fe)},error:function(Fe){L=!0,X(),f=Ur(te,n,Fe),qe.error(Fe)},complete:function(){y=!0,X(),f=Ur(te,a),qe.complete()}}),U(k).subscribe(l))})(c)}}function Ur(e,t){for(var r=[],o=2;oe.next(document)),e}function P(e,t=document){return Array.from(t.querySelectorAll(e))}function R(e,t=document){let r=fe(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function fe(e,t=document){return t.querySelector(e)||void 0}function Ie(){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 wa=O(h(document.body,"focusin"),h(document.body,"focusout")).pipe(_e(1),Q(void 0),m(()=>Ie()||document.body),G(1));function et(e){return wa.pipe(m(t=>e.contains(t)),K())}function $t(e,t){return C(()=>O(h(e,"mouseenter").pipe(m(()=>!0)),h(e,"mouseleave").pipe(m(()=>!1))).pipe(t?Ht(r=>Le(+!r*t)):le,Q(e.matches(":hover"))))}function Jo(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)Jo(e,r)}function x(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)Jo(o,n);return o}function sr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function Tt(e){let t=x("script",{src:e});return C(()=>(document.head.appendChild(t),O(h(t,"load"),h(t,"error").pipe(v(()=>$r(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),_(()=>document.head.removeChild(t)),Te(1))))}var Xo=new g,Ta=C(()=>typeof ResizeObserver=="undefined"?Tt("https://unpkg.com/resize-observer-polyfill"):I(void 0)).pipe(m(()=>new ResizeObserver(e=>e.forEach(t=>Xo.next(t)))),v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function ce(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ge(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return Ta.pipe(w(r=>r.observe(t)),v(r=>Xo.pipe(b(o=>o.target===t),_(()=>r.unobserve(t)))),m(()=>ce(e)),Q(ce(e)))}function St(e){return{width:e.scrollWidth,height:e.scrollHeight}}function cr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function Zo(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function Ve(e){return{x:e.offsetLeft,y:e.offsetTop}}function en(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function tn(e){return O(h(window,"load"),h(window,"resize")).pipe(Me(0,me),m(()=>Ve(e)),Q(Ve(e)))}function pr(e){return{x:e.scrollLeft,y:e.scrollTop}}function Ne(e){return O(h(e,"scroll"),h(window,"scroll"),h(window,"resize")).pipe(Me(0,me),m(()=>pr(e)),Q(pr(e)))}var rn=new g,Sa=C(()=>I(new IntersectionObserver(e=>{for(let t of e)rn.next(t)},{threshold:0}))).pipe(v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function tt(e){return Sa.pipe(w(t=>t.observe(e)),v(t=>rn.pipe(b(({target:r})=>r===e),_(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function on(e,t=16){return Ne(e).pipe(m(({y:r})=>{let o=ce(e),n=St(e);return r>=n.height-o.height-t}),K())}var lr={drawer:R("[data-md-toggle=drawer]"),search:R("[data-md-toggle=search]")};function nn(e){return lr[e].checked}function Je(e,t){lr[e].checked!==t&&lr[e].click()}function ze(e){let t=lr[e];return h(t,"change").pipe(m(()=>t.checked),Q(t.checked))}function Oa(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 La(){return O(h(window,"compositionstart").pipe(m(()=>!0)),h(window,"compositionend").pipe(m(()=>!1))).pipe(Q(!1))}function an(){let e=h(window,"keydown").pipe(b(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:nn("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),b(({mode:t,type:r})=>{if(t==="global"){let o=Ie();if(typeof o!="undefined")return!Oa(o,r)}return!0}),pe());return La().pipe(v(t=>t?S:e))}function ye(){return new URL(location.href)}function lt(e,t=!1){if(B("navigation.instant")&&!t){let r=x("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function sn(){return new g}function cn(){return location.hash.slice(1)}function pn(e){let t=x("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Ma(e){return O(h(window,"hashchange"),e).pipe(m(cn),Q(cn()),b(t=>t.length>0),G(1))}function ln(e){return Ma(e).pipe(m(t=>fe(`[id="${t}"]`)),b(t=>typeof t!="undefined"))}function Pt(e){let t=matchMedia(e);return ar(r=>t.addListener(()=>r(t.matches))).pipe(Q(t.matches))}function mn(){let e=matchMedia("print");return O(h(window,"beforeprint").pipe(m(()=>!0)),h(window,"afterprint").pipe(m(()=>!1))).pipe(Q(e.matches))}function Nr(e,t){return e.pipe(v(r=>r?t():S))}function zr(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 a=(i=o.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(n.loaded/+a*100)}}),t.progress$.next(5)),o.send(),()=>o.abort()})}function je(e,t){return zr(e,t).pipe(v(r=>r.text()),m(r=>JSON.parse(r)),G(1))}function fn(e,t){let r=new DOMParser;return zr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/html")),G(1))}function un(e,t){let r=new DOMParser;return zr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),G(1))}function dn(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function hn(){return O(h(window,"scroll",{passive:!0}),h(window,"resize",{passive:!0})).pipe(m(dn),Q(dn()))}function bn(){return{width:innerWidth,height:innerHeight}}function vn(){return h(window,"resize",{passive:!0}).pipe(m(bn),Q(bn()))}function gn(){return z([hn(),vn()]).pipe(m(([e,t])=>({offset:e,size:t})),G(1))}function mr(e,{viewport$:t,header$:r}){let o=t.pipe(ee("size")),n=z([o,r]).pipe(m(()=>Ve(e)));return z([r,t,n]).pipe(m(([{height:i},{offset:a,size:s},{x:p,y:c}])=>({offset:{x:a.x-p,y:a.y-c+i},size:s})))}function _a(e){return h(e,"message",t=>t.data)}function Aa(e){let t=new g;return t.subscribe(r=>e.postMessage(r)),t}function yn(e,t=new Worker(e)){let r=_a(t),o=Aa(t),n=new g;n.subscribe(o);let i=o.pipe(Z(),ie(!0));return n.pipe(Z(),Re(r.pipe(W(i))),pe())}var Ca=R("#__config"),Ot=JSON.parse(Ca.textContent);Ot.base=`${new URL(Ot.base,ye())}`;function xe(){return Ot}function B(e){return Ot.features.includes(e)}function Ee(e,t){return typeof t!="undefined"?Ot.translations[e].replace("#",t.toString()):Ot.translations[e]}function Se(e,t=document){return R(`[data-md-component=${e}]`,t)}function ae(e,t=document){return P(`[data-md-component=${e}]`,t)}function ka(e){let t=R(".md-typeset > :first-child",e);return h(t,"click",{once:!0}).pipe(m(()=>R(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function xn(e){if(!B("announce.dismiss")||!e.childElementCount)return S;if(!e.hidden){let t=R(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return C(()=>{let t=new g;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),ka(e).pipe(w(r=>t.next(r)),_(()=>t.complete()),m(r=>$({ref:e},r)))})}function Ha(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function En(e,t){let r=new g;return r.subscribe(({hidden:o})=>{e.hidden=o}),Ha(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))}function Rt(e,t){return t==="inline"?x("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"})):x("div",{class:"md-tooltip",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"}))}function wn(...e){return x("div",{class:"md-tooltip2",role:"tooltip"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function Tn(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return x("aside",{class:"md-annotation",tabIndex:0},Rt(t),x("a",{href:r,class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}else return x("aside",{class:"md-annotation",tabIndex:0},Rt(t),x("span",{class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}function Sn(e){return x("button",{class:"md-clipboard md-icon",title:Ee("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}var Ln=Mt(qr());function Qr(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(p=>!e.terms[p]).reduce((p,c)=>[...p,x("del",null,(0,Ln.default)(c))," "],[]).slice(0,-1),i=xe(),a=new URL(e.location,i.base);B("search.highlight")&&a.searchParams.set("h",Object.entries(e.terms).filter(([,p])=>p).reduce((p,[c])=>`${p} ${c}`.trim(),""));let{tags:s}=xe();return x("a",{href:`${a}`,class:"md-search-result__link",tabIndex:-1},x("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&x("div",{class:"md-search-result__icon md-icon"}),r>0&&x("h1",null,e.title),r<=0&&x("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&x("nav",{class:"md-tags"},e.tags.map(p=>{let c=s?p in s?`md-tag-icon md-tag--${s[p]}`:"md-tag-icon":"";return x("span",{class:`md-tag ${c}`},p)})),o>0&&n.length>0&&x("p",{class:"md-search-result__terms"},Ee("search.result.term.missing"),": ",...n)))}function Mn(e){let t=e[0].score,r=[...e],o=xe(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),a=r.findIndex(l=>l.scoreQr(l,1)),...p.length?[x("details",{class:"md-search-result__more"},x("summary",{tabIndex:-1},x("div",null,p.length>0&&p.length===1?Ee("search.result.more.one"):Ee("search.result.more.other",p.length))),...p.map(l=>Qr(l,1)))]:[]];return x("li",{class:"md-search-result__item"},c)}function _n(e){return x("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>x("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?sr(r):r)))}function Kr(e){let t=`tabbed-control tabbed-control--${e}`;return x("div",{class:t,hidden:!0},x("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function An(e){return x("div",{class:"md-typeset__scrollwrap"},x("div",{class:"md-typeset__table"},e))}function Ra(e){var o;let t=xe(),r=new URL(`../${e.version}/`,t.base);return x("li",{class:"md-version__item"},x("a",{href:`${r}`,class:"md-version__link"},e.title,((o=t.version)==null?void 0:o.alias)&&e.aliases.length>0&&x("span",{class:"md-version__alias"},e.aliases[0])))}function Cn(e,t){var o;let r=xe();return e=e.filter(n=>{var i;return!((i=n.properties)!=null&&i.hidden)}),x("div",{class:"md-version"},x("button",{class:"md-version__current","aria-label":Ee("select.version")},t.title,((o=r.version)==null?void 0:o.alias)&&t.aliases.length>0&&x("span",{class:"md-version__alias"},t.aliases[0])),x("ul",{class:"md-version__list"},e.map(Ra)))}var Ia=0;function ja(e){let t=z([et(e),$t(e)]).pipe(m(([o,n])=>o||n),K()),r=C(()=>Zo(e)).pipe(ne(Ne),pt(1),He(t),m(()=>en(e)));return t.pipe(Ae(o=>o),v(()=>z([t,r])),m(([o,n])=>({active:o,offset:n})),pe())}function Fa(e,t){let{content$:r,viewport$:o}=t,n=`__tooltip2_${Ia++}`;return C(()=>{let i=new g,a=new _r(!1);i.pipe(Z(),ie(!1)).subscribe(a);let s=a.pipe(Ht(c=>Le(+!c*250,kr)),K(),v(c=>c?r:S),w(c=>c.id=n),pe());z([i.pipe(m(({active:c})=>c)),s.pipe(v(c=>$t(c,250)),Q(!1))]).pipe(m(c=>c.some(l=>l))).subscribe(a);let p=a.pipe(b(c=>c),re(s,o),m(([c,l,{size:f}])=>{let u=e.getBoundingClientRect(),d=u.width/2;if(l.role==="tooltip")return{x:d,y:8+u.height};if(u.y>=f.height/2){let{height:y}=ce(l);return{x:d,y:-16-y}}else return{x:d,y:16+u.height}}));return z([s,i,p]).subscribe(([c,{offset:l},f])=>{c.style.setProperty("--md-tooltip-host-x",`${l.x}px`),c.style.setProperty("--md-tooltip-host-y",`${l.y}px`),c.style.setProperty("--md-tooltip-x",`${f.x}px`),c.style.setProperty("--md-tooltip-y",`${f.y}px`),c.classList.toggle("md-tooltip2--top",f.y<0),c.classList.toggle("md-tooltip2--bottom",f.y>=0)}),a.pipe(b(c=>c),re(s,(c,l)=>l),b(c=>c.role==="tooltip")).subscribe(c=>{let l=ce(R(":scope > *",c));c.style.setProperty("--md-tooltip-width",`${l.width}px`),c.style.setProperty("--md-tooltip-tail","0px")}),a.pipe(K(),ve(me),re(s)).subscribe(([c,l])=>{l.classList.toggle("md-tooltip2--active",c)}),z([a.pipe(b(c=>c)),s]).subscribe(([c,l])=>{l.role==="dialog"?(e.setAttribute("aria-controls",n),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",n)}),a.pipe(b(c=>!c)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),ja(e).pipe(w(c=>i.next(c)),_(()=>i.complete()),m(c=>$({ref:e},c)))})}function mt(e,{viewport$:t},r=document.body){return Fa(e,{content$:new j(o=>{let n=e.title,i=wn(n);return o.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",n)}}),viewport$:t})}function Ua(e,t){let r=C(()=>z([tn(e),Ne(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:a,height:s}=ce(e);return{x:o-i.x+a/2,y:n-i.y+s/2}}));return et(e).pipe(v(o=>r.pipe(m(n=>({active:o,offset:n})),Te(+!o||1/0))))}function kn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return C(()=>{let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({offset:s}){e.style.setProperty("--md-tooltip-x",`${s.x}px`),e.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),tt(e).pipe(W(a)).subscribe(s=>{e.toggleAttribute("data-md-visible",s)}),O(i.pipe(b(({active:s})=>s)),i.pipe(_e(250),b(({active:s})=>!s))).subscribe({next({active:s}){s?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe(Me(16,me)).subscribe(({active:s})=>{o.classList.toggle("md-tooltip--active",s)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:s})=>s)).subscribe({next(s){s?e.style.setProperty("--md-tooltip-0",`${-s}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),h(n,"click").pipe(W(a),b(s=>!(s.metaKey||s.ctrlKey))).subscribe(s=>{s.stopPropagation(),s.preventDefault()}),h(n,"mousedown").pipe(W(a),re(i)).subscribe(([s,{active:p}])=>{var c;if(s.button!==0||s.metaKey||s.ctrlKey)s.preventDefault();else if(p){s.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(c=Ie())==null||c.blur()}}),r.pipe(W(a),b(s=>s===o),Ge(125)).subscribe(()=>e.focus()),Ua(e,t).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function Wa(e){return e.tagName==="CODE"?P(".c, .c1, .cm",e):[e]}function Da(e){let t=[];for(let r of Wa(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 a;for(;a=/(\(\d+\))(!)?/.exec(i.textContent);){let[,s,p]=a;if(typeof p=="undefined"){let c=i.splitText(a.index);i=c.splitText(s.length),t.push(c)}else{i.textContent=s,t.push(i);break}}}}return t}function Hn(e,t){t.append(...Array.from(e.childNodes))}function fr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,a=new Map;for(let s of Da(t)){let[,p]=s.textContent.match(/\((\d+)\)/);fe(`:scope > li:nth-child(${p})`,e)&&(a.set(p,Tn(p,i)),s.replaceWith(a.get(p)))}return a.size===0?S:C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=[];for(let[l,f]of a)c.push([R(".md-typeset",f),R(`:scope > li:nth-child(${l})`,e)]);return o.pipe(W(p)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of c)l?Hn(f,u):Hn(u,f)}),O(...[...a].map(([,l])=>kn(l,t,{target$:r}))).pipe(_(()=>s.complete()),pe())})}function $n(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return $n(t)}}function Pn(e,t){return C(()=>{let r=$n(e);return typeof r!="undefined"?fr(r,e,t):S})}var Rn=Mt(Br());var Va=0;function In(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return In(t)}}function Na(e){return ge(e).pipe(m(({width:t})=>({scrollable:St(e).width>t})),ee("scrollable"))}function jn(e,t){let{matches:r}=matchMedia("(hover)"),o=C(()=>{let n=new g,i=n.pipe(jr(1));n.subscribe(({scrollable:c})=>{c&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let a=[];if(Rn.default.isSupported()&&(e.closest(".copy")||B("content.code.copy")&&!e.closest(".no-copy"))){let c=e.closest("pre");c.id=`__code_${Va++}`;let l=Sn(c.id);c.insertBefore(l,e),B("content.tooltips")&&a.push(mt(l,{viewport$}))}let s=e.closest(".highlight");if(s instanceof HTMLElement){let c=In(s);if(typeof c!="undefined"&&(s.classList.contains("annotate")||B("content.code.annotate"))){let l=fr(c,e,t);a.push(ge(s).pipe(W(i),m(({width:f,height:u})=>f&&u),K(),v(f=>f?l:S)))}}return P(":scope > span[id]",e).length&&e.classList.add("md-code__content"),Na(e).pipe(w(c=>n.next(c)),_(()=>n.complete()),m(c=>$({ref:e},c)),Re(...a))});return B("content.lazy")?tt(e).pipe(b(n=>n),Te(1),v(()=>o)):o}function za(e,{target$:t,print$:r}){let o=!0;return O(t.pipe(m(n=>n.closest("details:not([open])")),b(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(b(n=>n||!o),w(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Fn(e,t){return C(()=>{let r=new g;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),za(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}var Un=".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 p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{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)}a .nodeLabel{text-decoration:underline}.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 Gr,Qa=0;function Ka(){return typeof mermaid=="undefined"||mermaid instanceof Element?Tt("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):I(void 0)}function Wn(e){return e.classList.remove("mermaid"),Gr||(Gr=Ka().pipe(w(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Un,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),G(1))),Gr.subscribe(()=>co(this,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${Qa++}`,r=x("div",{class:"mermaid"}),o=e.textContent,{svg:n,fn:i}=yield mermaid.render(t,o),a=r.attachShadow({mode:"closed"});a.innerHTML=n,e.replaceWith(r),i==null||i(a)})),Gr.pipe(m(()=>({ref:e})))}var Dn=x("table");function Vn(e){return e.replaceWith(Dn),Dn.replaceWith(An(e)),I({ref:e})}function Ya(e){let t=e.find(r=>r.checked)||e[0];return O(...e.map(r=>h(r,"change").pipe(m(()=>R(`label[for="${r.id}"]`))))).pipe(Q(R(`label[for="${t.id}"]`)),m(r=>({active:r})))}function Nn(e,{viewport$:t,target$:r}){let o=R(".tabbed-labels",e),n=P(":scope > input",e),i=Kr("prev");e.append(i);let a=Kr("next");return e.append(a),C(()=>{let s=new g,p=s.pipe(Z(),ie(!0));z([s,ge(e),tt(e)]).pipe(W(p),Me(1,me)).subscribe({next([{active:c},l]){let f=Ve(c),{width:u}=ce(c);e.style.setProperty("--md-indicator-x",`${f.x}px`),e.style.setProperty("--md-indicator-width",`${u}px`);let d=pr(o);(f.xd.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")}}),z([Ne(o),ge(o)]).pipe(W(p)).subscribe(([c,l])=>{let f=St(o);i.hidden=c.x<16,a.hidden=c.x>f.width-l.width-16}),O(h(i,"click").pipe(m(()=>-1)),h(a,"click").pipe(m(()=>1))).pipe(W(p)).subscribe(c=>{let{width:l}=ce(o);o.scrollBy({left:l*c,behavior:"smooth"})}),r.pipe(W(p),b(c=>n.includes(c))).subscribe(c=>c.click()),o.classList.add("tabbed-labels--linked");for(let c of n){let l=R(`label[for="${c.id}"]`);l.replaceChildren(x("a",{href:`#${l.htmlFor}`,tabIndex:-1},...Array.from(l.childNodes))),h(l.firstElementChild,"click").pipe(W(p),b(f=>!(f.metaKey||f.ctrlKey)),w(f=>{f.preventDefault(),f.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${l.htmlFor}`),l.click()})}return B("content.tabs.link")&&s.pipe(Ce(1),re(t)).subscribe(([{active:c},{offset:l}])=>{let f=c.innerText.trim();if(c.hasAttribute("data-md-switching"))c.removeAttribute("data-md-switching");else{let u=e.offsetTop-l.y;for(let y of P("[data-tabs]"))for(let L of P(":scope > input",y)){let X=R(`label[for="${L.id}"]`);if(X!==c&&X.innerText.trim()===f){X.setAttribute("data-md-switching",""),L.click();break}}window.scrollTo({top:e.offsetTop-u});let d=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([f,...d])])}}),s.pipe(W(p)).subscribe(()=>{for(let c of P("audio, video",e))c.pause()}),Ya(n).pipe(w(c=>s.next(c)),_(()=>s.complete()),m(c=>$({ref:e},c)))}).pipe(Ke(se))}function zn(e,{viewport$:t,target$:r,print$:o}){return O(...P(".annotate:not(.highlight)",e).map(n=>Pn(n,{target$:r,print$:o})),...P("pre:not(.mermaid) > code",e).map(n=>jn(n,{target$:r,print$:o})),...P("pre.mermaid",e).map(n=>Wn(n)),...P("table:not([class])",e).map(n=>Vn(n)),...P("details",e).map(n=>Fn(n,{target$:r,print$:o})),...P("[data-tabs]",e).map(n=>Nn(n,{viewport$:t,target$:r})),...P("[title]",e).filter(()=>B("content.tooltips")).map(n=>mt(n,{viewport$:t})))}function Ba(e,{alert$:t}){return t.pipe(v(r=>O(I(!0),I(!1).pipe(Ge(2e3))).pipe(m(o=>({message:r,active:o})))))}function qn(e,t){let r=R(".md-typeset",e);return C(()=>{let o=new g;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),Ba(e,t).pipe(w(n=>o.next(n)),_(()=>o.complete()),m(n=>$({ref:e},n)))})}var Ga=0;function Ja(e,t){document.body.append(e);let{width:r}=ce(e);e.style.setProperty("--md-tooltip-width",`${r}px`),e.remove();let o=cr(t),n=typeof o!="undefined"?Ne(o):I({x:0,y:0}),i=O(et(t),$t(t)).pipe(K());return z([i,n]).pipe(m(([a,s])=>{let{x:p,y:c}=Ve(t),l=ce(t),f=t.closest("table");return f&&t.parentElement&&(p+=f.offsetLeft+t.parentElement.offsetLeft,c+=f.offsetTop+t.parentElement.offsetTop),{active:a,offset:{x:p-s.x+l.width/2-r/2,y:c-s.y+l.height+8}}}))}function Qn(e){let t=e.title;if(!t.length)return S;let r=`__tooltip_${Ga++}`,o=Rt(r,"inline"),n=R(".md-typeset",o);return n.innerHTML=t,C(()=>{let i=new g;return i.subscribe({next({offset:a}){o.style.setProperty("--md-tooltip-x",`${a.x}px`),o.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){o.style.removeProperty("--md-tooltip-x"),o.style.removeProperty("--md-tooltip-y")}}),O(i.pipe(b(({active:a})=>a)),i.pipe(_e(250),b(({active:a})=>!a))).subscribe({next({active:a}){a?(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,me)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?o.style.setProperty("--md-tooltip-0",`${-a}px`):o.style.removeProperty("--md-tooltip-0")},complete(){o.style.removeProperty("--md-tooltip-0")}}),Ja(o,e).pipe(w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))}).pipe(Ke(se))}function Xa({viewport$:e}){if(!B("header.autohide"))return I(!1);let t=e.pipe(m(({offset:{y:n}})=>n),Be(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),K()),o=ze("search");return z([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),K(),v(n=>n?r:I(!1)),Q(!1))}function Kn(e,t){return C(()=>z([ge(e),Xa(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),K((r,o)=>r.height===o.height&&r.hidden===o.hidden),G(1))}function Yn(e,{header$:t,main$:r}){return C(()=>{let o=new g,n=o.pipe(Z(),ie(!0));o.pipe(ee("active"),He(t)).subscribe(([{active:a},{hidden:s}])=>{e.classList.toggle("md-header--shadow",a&&!s),e.hidden=s});let i=ue(P("[title]",e)).pipe(b(()=>B("content.tooltips")),ne(a=>Qn(a)));return r.subscribe(o),t.pipe(W(n),m(a=>$({ref:e},a)),Re(i.pipe(W(n))))})}function Za(e,{viewport$:t,header$:r}){return mr(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=ce(e);return{active:o>=n}}),ee("active"))}function Bn(e,t){return C(()=>{let r=new g;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=fe(".md-content h1");return typeof o=="undefined"?S:Za(o,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))})}function Gn(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),K()),n=o.pipe(v(()=>ge(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),ee("bottom"))));return z([o,n,t]).pipe(m(([i,{top:a,bottom:s},{offset:{y:p},size:{height:c}}])=>(c=Math.max(0,c-Math.max(0,a-p,i)-Math.max(0,c+p-s)),{offset:a-i,height:c,active:a-i<=p})),K((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function es(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 I(...e).pipe(ne(o=>h(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")}})),G(1))}function Jn(e){let t=P("input",e),r=x("meta",{name:"theme-color"});document.head.appendChild(r);let o=x("meta",{name:"color-scheme"});document.head.appendChild(o);let n=Pt("(prefers-color-scheme: light)");return C(()=>{let i=new g;return i.subscribe(a=>{if(document.body.setAttribute("data-md-color-switching",""),a.color.media==="(prefers-color-scheme)"){let s=matchMedia("(prefers-color-scheme: light)"),p=document.querySelector(s.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");a.color.scheme=p.getAttribute("data-md-color-scheme"),a.color.primary=p.getAttribute("data-md-color-primary"),a.color.accent=p.getAttribute("data-md-color-accent")}for(let[s,p]of Object.entries(a.color))document.body.setAttribute(`data-md-color-${s}`,p);for(let s=0;sa.key==="Enter"),re(i,(a,s)=>s)).subscribe(({index:a})=>{a=(a+1)%t.length,t[a].click(),t[a].focus()}),i.pipe(m(()=>{let a=Se("header"),s=window.getComputedStyle(a);return o.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(p=>(+p).toString(16).padStart(2,"0")).join("")})).subscribe(a=>r.content=`#${a}`),i.pipe(ve(se)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),es(t).pipe(W(n.pipe(Ce(1))),ct(),w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))})}function Xn(e,{progress$:t}){return C(()=>{let r=new g;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(w(o=>r.next({value:o})),_(()=>r.complete()),m(o=>({ref:e,value:o})))})}var Jr=Mt(Br());function ts(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 Zn({alert$:e}){Jr.default.isSupported()&&new j(t=>{new Jr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||ts(R(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(w(t=>{t.trigger.focus()}),m(()=>Ee("clipboard.copied"))).subscribe(e)}function ei(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,e}function rs(e,t){let r=new Map;for(let o of P("url",e)){let n=R("loc",o),i=[ei(new URL(n.textContent),t)];r.set(`${i[0]}`,i);for(let a of P("[rel=alternate]",o)){let s=a.getAttribute("href");s!=null&&i.push(ei(new URL(s),t))}}return r}function ur(e){return un(new URL("sitemap.xml",e)).pipe(m(t=>rs(t,new URL(e))),de(()=>I(new Map)))}function os(e,t){if(!(e.target instanceof Element))return S;let r=e.target.closest("a");if(r===null)return S;if(r.target||e.metaKey||e.ctrlKey)return S;let o=new URL(r.href);return o.search=o.hash="",t.has(`${o}`)?(e.preventDefault(),I(new URL(r.href))):S}function ti(e){let t=new Map;for(let r of P(":scope > *",e.head))t.set(r.outerHTML,r);return t}function ri(e){for(let t of P("[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 I(e)}function ns(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]",...B("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let n=fe(o),i=fe(o,e);typeof n!="undefined"&&typeof i!="undefined"&&n.replaceWith(i)}let t=ti(document);for(let[o,n]of ti(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=Se("container");return We(P("script",r)).pipe(v(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),S}),Z(),ie(document))}function oi({location$:e,viewport$:t,progress$:r}){let o=xe();if(location.protocol==="file:")return S;let n=ur(o.base);I(document).subscribe(ri);let i=h(document.body,"click").pipe(He(n),v(([p,c])=>os(p,c)),pe()),a=h(window,"popstate").pipe(m(ye),pe());i.pipe(re(t)).subscribe(([p,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",p)}),O(i,a).subscribe(e);let s=e.pipe(ee("pathname"),v(p=>fn(p,{progress$:r}).pipe(de(()=>(lt(p,!0),S)))),v(ri),v(ns),pe());return O(s.pipe(re(e,(p,c)=>c)),s.pipe(v(()=>e),ee("pathname"),v(()=>e),ee("hash")),e.pipe(K((p,c)=>p.pathname===c.pathname&&p.hash===c.hash),v(()=>i),w(()=>history.back()))).subscribe(p=>{var c,l;history.state!==null||!p.hash?window.scrollTo(0,(l=(c=history.state)==null?void 0:c.y)!=null?l:0):(history.scrollRestoration="auto",pn(p.hash),history.scrollRestoration="manual")}),e.subscribe(()=>{history.scrollRestoration="manual"}),h(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),t.pipe(ee("offset"),_e(100)).subscribe(({offset:p})=>{history.replaceState(p,"")}),s}var ni=Mt(qr());function ii(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,a)=>`${i}${a}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return a=>(0,ni.default)(a).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function jt(e){return e.type===1}function dr(e){return e.type===3}function ai(e,t){let r=yn(e);return O(I(location.protocol!=="file:"),ze("search")).pipe(Ae(o=>o),v(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:B("search.suggest")}}})),r}function si(e){var l;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:o,currentBaseURL:n}=e,i=(l=Xr(n))==null?void 0:l.pathname;if(i===void 0)return;let a=ss(o.pathname,i);if(a===void 0)return;let s=ps(t.keys());if(!t.has(s))return;let p=Xr(a,s);if(!p||!t.has(p.href))return;let c=Xr(a,r);if(c)return c.hash=o.hash,c.search=o.search,c}function Xr(e,t){try{return new URL(e,t)}catch(r){return}}function ss(e,t){if(e.startsWith(t))return e.slice(t.length)}function cs(e,t){let r=Math.min(e.length,t.length),o;for(o=0;oS)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:a,aliases:s})=>a===i||s.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),v(n=>h(document.body,"click").pipe(b(i=>!i.metaKey&&!i.ctrlKey),re(o),v(([i,a])=>{if(i.target instanceof Element){let s=i.target.closest("a");if(s&&!s.target&&n.has(s.href)){let p=s.href;return!i.target.closest(".md-version")&&n.get(p)===a?S:(i.preventDefault(),I(new URL(p)))}}return S}),v(i=>ur(i).pipe(m(a=>{var s;return(s=si({selectedVersionSitemap:a,selectedVersionBaseURL:i,currentLocation:ye(),currentBaseURL:t.base}))!=null?s:i})))))).subscribe(n=>lt(n,!0)),z([r,o]).subscribe(([n,i])=>{R(".md-header__topic").appendChild(Cn(n,i))}),e.pipe(v(()=>o)).subscribe(n=>{var a;let i=__md_get("__outdated",sessionStorage);if(i===null){i=!0;let s=((a=t.version)==null?void 0:a.default)||"latest";Array.isArray(s)||(s=[s]);e:for(let p of s)for(let c of n.aliases.concat(n.version))if(new RegExp(p,"i").test(c)){i=!1;break e}__md_set("__outdated",i,sessionStorage)}if(i)for(let s of ae("outdated"))s.hidden=!1})}function ls(e,{worker$:t}){let{searchParams:r}=ye();r.has("q")&&(Je("search",!0),e.value=r.get("q"),e.focus(),ze("search").pipe(Ae(i=>!i)).subscribe(()=>{let i=ye();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=et(e),n=O(t.pipe(Ae(jt)),h(e,"keyup"),o).pipe(m(()=>e.value),K());return z([n,o]).pipe(m(([i,a])=>({value:i,focus:a})),G(1))}function pi(e,{worker$:t}){let r=new g,o=r.pipe(Z(),ie(!0));z([t.pipe(Ae(jt)),r],(i,a)=>a).pipe(ee("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(ee("focus")).subscribe(({focus:i})=>{i&&Je("search",i)}),h(e.form,"reset").pipe(W(o)).subscribe(()=>e.focus());let n=R("header [for=__search]");return h(n,"click").subscribe(()=>e.focus()),ls(e,{worker$:t}).pipe(w(i=>r.next(i)),_(()=>r.complete()),m(i=>$({ref:e},i)),G(1))}function li(e,{worker$:t,query$:r}){let o=new g,n=on(e.parentElement).pipe(b(Boolean)),i=e.parentElement,a=R(":scope > :first-child",e),s=R(":scope > :last-child",e);ze("search").subscribe(l=>s.setAttribute("role",l?"list":"presentation")),o.pipe(re(r),Wr(t.pipe(Ae(jt)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:a.textContent=f.length?Ee("search.result.none"):Ee("search.result.placeholder");break;case 1:a.textContent=Ee("search.result.one");break;default:let u=sr(l.length);a.textContent=Ee("search.result.other",u)}});let p=o.pipe(w(()=>s.innerHTML=""),v(({items:l})=>O(I(...l.slice(0,10)),I(...l.slice(10)).pipe(Be(4),Vr(n),v(([f])=>f)))),m(Mn),pe());return p.subscribe(l=>s.appendChild(l)),p.pipe(ne(l=>{let f=fe("details",l);return typeof f=="undefined"?S:h(f,"toggle").pipe(W(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(b(dr),m(({data:l})=>l)).pipe(w(l=>o.next(l)),_(()=>o.complete()),m(l=>$({ref:e},l)))}function ms(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=ye();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function mi(e,t){let r=new g,o=r.pipe(Z(),ie(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),h(e,"click").pipe(W(o)).subscribe(n=>n.preventDefault()),ms(e,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))}function fi(e,{worker$:t,keyboard$:r}){let o=new g,n=Se("search-query"),i=O(h(n,"keydown"),h(n,"focus")).pipe(ve(se),m(()=>n.value),K());return o.pipe(He(i),m(([{suggest:s},p])=>{let c=p.split(/([\s-]+)/);if(s!=null&&s.length&&c[c.length-1]){let l=s[s.length-1];l.startsWith(c[c.length-1])&&(c[c.length-1]=l)}else c.length=0;return c})).subscribe(s=>e.innerHTML=s.join("").replace(/\s/g," ")),r.pipe(b(({mode:s})=>s==="search")).subscribe(s=>{switch(s.type){case"ArrowRight":e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText);break}}),t.pipe(b(dr),m(({data:s})=>s)).pipe(w(s=>o.next(s)),_(()=>o.complete()),m(()=>({ref:e})))}function ui(e,{index$:t,keyboard$:r}){let o=xe();try{let n=ai(o.search,t),i=Se("search-query",e),a=Se("search-result",e);h(e,"click").pipe(b(({target:p})=>p instanceof Element&&!!p.closest("a"))).subscribe(()=>Je("search",!1)),r.pipe(b(({mode:p})=>p==="search")).subscribe(p=>{let c=Ie();switch(p.type){case"Enter":if(c===i){let l=new Map;for(let f of P(":first-child [href]",a)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,d])=>d-u);f.click()}p.claim()}break;case"Escape":case"Tab":Je("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof c=="undefined")i.focus();else{let l=[i,...P(":not(details) > [href], summary, details[open] [href]",a)],f=Math.max(0,(Math.max(0,l.indexOf(c))+l.length+(p.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}p.claim();break;default:i!==Ie()&&i.focus()}}),r.pipe(b(({mode:p})=>p==="global")).subscribe(p=>{switch(p.type){case"f":case"s":case"/":i.focus(),i.select(),p.claim();break}});let s=pi(i,{worker$:n});return O(s,li(a,{worker$:n,query$:s})).pipe(Re(...ae("search-share",e).map(p=>mi(p,{query$:s})),...ae("search-suggest",e).map(p=>fi(p,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,Ye}}function di(e,{index$:t,location$:r}){return z([t,r.pipe(Q(ye()),b(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>ii(o.config)(n.searchParams.get("h"))),m(o=>{var a;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let p=s.textContent,c=o(p);c.length>p.length&&n.set(s,c)}for(let[s,p]of n){let{childNodes:c}=x("span",null,p);s.replaceWith(...Array.from(c))}return{ref:e,nodes:n}}))}function fs(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return z([r,t]).pipe(m(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(n,Math.max(0,s-i))-n,{height:a,locked:s>=i+n})),K((i,a)=>i.height===a.height&&i.locked===a.locked))}function Zr(e,o){var n=o,{header$:t}=n,r=so(n,["header$"]);let i=R(".md-sidebar__scrollwrap",e),{y:a}=Ve(i);return C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=s.pipe(Me(0,me));return c.pipe(re(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*a}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),c.pipe(Ae()).subscribe(()=>{for(let l of P(".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:d}=ce(f);f.scrollTo({top:u-d/2})}}}),ue(P("label[tabindex]",e)).pipe(ne(l=>h(l,"click").pipe(ve(se),m(()=>l),W(p)))).subscribe(l=>{let f=R(`[id="${l.htmlFor}"]`);R(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),fs(e,r).pipe(w(l=>s.next(l)),_(()=>s.complete()),m(l=>$({ref:e},l)))})}function hi(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return st(je(`${r}/releases/latest`).pipe(de(()=>S),m(o=>({version:o.tag_name})),De({})),je(r).pipe(de(()=>S),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),De({}))).pipe(m(([o,n])=>$($({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return je(r).pipe(m(o=>({repositories:o.public_repos})),De({}))}}function bi(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return st(je(`${r}/releases/permalink/latest`).pipe(de(()=>S),m(({tag_name:o})=>({version:o})),De({})),je(r).pipe(de(()=>S),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),De({}))).pipe(m(([o,n])=>$($({},o),n)))}function vi(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return hi(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return bi(r,o)}return S}var us;function ds(e){return us||(us=C(()=>{let t=__md_get("__source",sessionStorage);if(t)return I(t);if(ae("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return S}return vi(e.href).pipe(w(o=>__md_set("__source",o,sessionStorage)))}).pipe(de(()=>S),b(t=>Object.keys(t).length>0),m(t=>({facts:t})),G(1)))}function gi(e){let t=R(":scope > :last-child",e);return C(()=>{let r=new g;return r.subscribe(({facts:o})=>{t.appendChild(_n(o)),t.classList.add("md-source__repository--active")}),ds(e).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function hs(e,{viewport$:t,header$:r}){return ge(document.body).pipe(v(()=>mr(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),ee("hidden"))}function yi(e,t){return C(()=>{let r=new g;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(B("navigation.tabs.sticky")?I({hidden:!1}):hs(e,t)).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function bs(e,{viewport$:t,header$:r}){let o=new Map,n=P(".md-nav__link",e);for(let s of n){let p=decodeURIComponent(s.hash.substring(1)),c=fe(`[id="${p}"]`);typeof c!="undefined"&&o.set(s,c)}let i=r.pipe(ee("height"),m(({height:s})=>{let p=Se("main"),c=R(":scope > :first-child",p);return s+.8*(c.offsetTop-p.offsetTop)}),pe());return ge(document.body).pipe(ee("height"),v(s=>C(()=>{let p=[];return I([...o].reduce((c,[l,f])=>{for(;p.length&&o.get(p[p.length-1]).tagName>=f.tagName;)p.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let d=f.offsetParent;for(;d;d=d.offsetParent)u+=d.offsetTop;return c.set([...p=[...p,l]].reverse(),u)},new Map))}).pipe(m(p=>new Map([...p].sort(([,c],[,l])=>c-l))),He(i),v(([p,c])=>t.pipe(Fr(([l,f],{offset:{y:u},size:d})=>{let y=u+d.height>=Math.floor(s.height);for(;f.length;){let[,L]=f[0];if(L-c=u&&!y)f=[l.pop(),...f];else break}return[l,f]},[[],[...p]]),K((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([s,p])=>({prev:s.map(([c])=>c),next:p.map(([c])=>c)})),Q({prev:[],next:[]}),Be(2,1),m(([s,p])=>s.prev.length{let i=new g,a=i.pipe(Z(),ie(!0));if(i.subscribe(({prev:s,next:p})=>{for(let[c]of p)c.classList.remove("md-nav__link--passed"),c.classList.remove("md-nav__link--active");for(let[c,[l]]of s.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",c===s.length-1)}),B("toc.follow")){let s=O(t.pipe(_e(1),m(()=>{})),t.pipe(_e(250),m(()=>"smooth")));i.pipe(b(({prev:p})=>p.length>0),He(o.pipe(ve(se))),re(s)).subscribe(([[{prev:p}],c])=>{let[l]=p[p.length-1];if(l.offsetHeight){let f=cr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2,behavior:c})}}})}return B("navigation.tracking")&&t.pipe(W(a),ee("offset"),_e(250),Ce(1),W(n.pipe(Ce(1))),ct({delay:250}),re(i)).subscribe(([,{prev:s}])=>{let p=ye(),c=s[s.length-1];if(c&&c.length){let[l]=c,{hash:f}=new URL(l.href);p.hash!==f&&(p.hash=f,history.replaceState({},"",`${p}`))}else p.hash="",history.replaceState({},"",`${p}`)}),bs(e,{viewport$:t,header$:r}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function vs(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:a}})=>a),Be(2,1),m(([a,s])=>a>s&&s>0),K()),i=r.pipe(m(({active:a})=>a));return z([i,n]).pipe(m(([a,s])=>!(a&&s)),K(),W(o.pipe(Ce(1))),ie(!0),ct({delay:250}),m(a=>({hidden:a})))}function Ei(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(W(a),ee("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),h(e,"click").subscribe(s=>{s.preventDefault(),window.scrollTo({top:0})}),vs(e,{viewport$:t,main$:o,target$:n}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))}function wi({document$:e,viewport$:t}){e.pipe(v(()=>P(".md-ellipsis")),ne(r=>tt(r).pipe(W(e.pipe(Ce(1))),b(o=>o),m(()=>r),Te(1))),b(r=>r.offsetWidth{let o=r.innerText,n=r.closest("a")||r;return n.title=o,B("content.tooltips")?mt(n,{viewport$:t}).pipe(W(e.pipe(Ce(1))),_(()=>n.removeAttribute("title"))):S})).subscribe(),B("content.tooltips")&&e.pipe(v(()=>P(".md-status")),ne(r=>mt(r,{viewport$:t}))).subscribe()}function Ti({document$:e,tablet$:t}){e.pipe(v(()=>P(".md-toggle--indeterminate")),w(r=>{r.indeterminate=!0,r.checked=!1}),ne(r=>h(r,"change").pipe(Dr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),re(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function gs(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Si({document$:e}){e.pipe(v(()=>P("[data-md-scrollfix]")),w(t=>t.removeAttribute("data-md-scrollfix")),b(gs),ne(t=>h(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 Oi({viewport$:e,tablet$:t}){z([ze("search"),t]).pipe(m(([r,o])=>r&&!o),v(r=>I(r).pipe(Ge(r?400:100))),re(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 ys(){return location.protocol==="file:"?Tt(`${new URL("search/search_index.js",eo.base)}`).pipe(m(()=>__index),G(1)):je(new URL("search/search_index.json",eo.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var ot=Go(),Ut=sn(),Lt=ln(Ut),to=an(),Oe=gn(),hr=Pt("(min-width: 960px)"),Mi=Pt("(min-width: 1220px)"),_i=mn(),eo=xe(),Ai=document.forms.namedItem("search")?ys():Ye,ro=new g;Zn({alert$:ro});var oo=new g;B("navigation.instant")&&oi({location$:Ut,viewport$:Oe,progress$:oo}).subscribe(ot);var Li;((Li=eo.version)==null?void 0:Li.provider)==="mike"&&ci({document$:ot});O(Ut,Lt).pipe(Ge(125)).subscribe(()=>{Je("drawer",!1),Je("search",!1)});to.pipe(b(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=fe("link[rel=prev]");typeof t!="undefined"&<(t);break;case"n":case".":let r=fe("link[rel=next]");typeof r!="undefined"&<(r);break;case"Enter":let o=Ie();o instanceof HTMLLabelElement&&o.click()}});wi({viewport$:Oe,document$:ot});Ti({document$:ot,tablet$:hr});Si({document$:ot});Oi({viewport$:Oe,tablet$:hr});var rt=Kn(Se("header"),{viewport$:Oe}),Ft=ot.pipe(m(()=>Se("main")),v(e=>Gn(e,{viewport$:Oe,header$:rt})),G(1)),xs=O(...ae("consent").map(e=>En(e,{target$:Lt})),...ae("dialog").map(e=>qn(e,{alert$:ro})),...ae("palette").map(e=>Jn(e)),...ae("progress").map(e=>Xn(e,{progress$:oo})),...ae("search").map(e=>ui(e,{index$:Ai,keyboard$:to})),...ae("source").map(e=>gi(e))),Es=C(()=>O(...ae("announce").map(e=>xn(e)),...ae("content").map(e=>zn(e,{viewport$:Oe,target$:Lt,print$:_i})),...ae("content").map(e=>B("search.highlight")?di(e,{index$:Ai,location$:Ut}):S),...ae("header").map(e=>Yn(e,{viewport$:Oe,header$:rt,main$:Ft})),...ae("header-title").map(e=>Bn(e,{viewport$:Oe,header$:rt})),...ae("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?Nr(Mi,()=>Zr(e,{viewport$:Oe,header$:rt,main$:Ft})):Nr(hr,()=>Zr(e,{viewport$:Oe,header$:rt,main$:Ft}))),...ae("tabs").map(e=>yi(e,{viewport$:Oe,header$:rt})),...ae("toc").map(e=>xi(e,{viewport$:Oe,header$:rt,main$:Ft,target$:Lt})),...ae("top").map(e=>Ei(e,{viewport$:Oe,header$:rt,main$:Ft,target$:Lt})))),Ci=ot.pipe(v(()=>Es),Re(xs),G(1));Ci.subscribe();window.document$=ot;window.location$=Ut;window.target$=Lt;window.keyboard$=to;window.viewport$=Oe;window.tablet$=hr;window.screen$=Mi;window.print$=_i;window.alert$=ro;window.progress$=oo;window.component$=Ci;})(); +//# sourceMappingURL=bundle.83f73b43.min.js.map + diff --git a/assets/javascripts/bundle.83f73b43.min.js.map b/assets/javascripts/bundle.83f73b43.min.js.map new file mode 100644 index 0000000..fe920b7 --- /dev/null +++ b/assets/javascripts/bundle.83f73b43.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/escape-html/index.js", "node_modules/clipboard/dist/clipboard.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/tslib/tslib.es6.mjs", "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/BehaviorSubject.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/QueueAction.ts", "node_modules/rxjs/src/internal/scheduler/QueueScheduler.ts", "node_modules/rxjs/src/internal/scheduler/queue.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/debounce.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/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/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/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/tooltip2/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/tooltip/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/findurl/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 * 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 * 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 * 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({ viewport$, 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 /* 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 */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\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", "/******************************************************************************\nCopyright (c) Microsoft Corporation.\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n***************************************************************************** */\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\n\nvar extendStatics = function(d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\n return extendStatics(d, b);\n};\n\nexport function __extends(d, b) {\n if (typeof b !== \"function\" && b !== null)\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n}\n\nexport var __assign = function() {\n __assign = Object.assign || function __assign(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\n }\n return t;\n }\n return __assign.apply(this, arguments);\n}\n\nexport function __rest(s, e) {\n var t = {};\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\n t[p] = s[p];\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\n t[p[i]] = s[p[i]];\n }\n return t;\n}\n\nexport function __decorate(decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\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;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n}\n\nexport function __param(paramIndex, decorator) {\n return function (target, key) { decorator(target, key, paramIndex); }\n}\n\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\n var _, done = false;\n for (var i = decorators.length - 1; i >= 0; i--) {\n var context = {};\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\n if (kind === \"accessor\") {\n if (result === void 0) continue;\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\n if (_ = accept(result.get)) descriptor.get = _;\n if (_ = accept(result.set)) descriptor.set = _;\n if (_ = accept(result.init)) initializers.unshift(_);\n }\n else if (_ = accept(result)) {\n if (kind === \"field\") initializers.unshift(_);\n else descriptor[key] = _;\n }\n }\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\n done = true;\n};\n\nexport function __runInitializers(thisArg, initializers, value) {\n var useValue = arguments.length > 2;\n for (var i = 0; i < initializers.length; i++) {\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\n }\n return useValue ? value : void 0;\n};\n\nexport function __propKey(x) {\n return typeof x === \"symbol\" ? x : \"\".concat(x);\n};\n\nexport function __setFunctionName(f, name, prefix) {\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\n};\n\nexport function __metadata(metadataKey, metadataValue) {\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\n}\n\nexport function __awaiter(thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n}\n\nexport function __generator(thisArg, body) {\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\n function verb(n) { return function (v) { return step([n, v]); }; }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\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;\n if (y = 0, t) op = [op[0] & 2, t.value];\n switch (op[0]) {\n case 0: case 1: t = op; break;\n case 4: _.label++; return { value: op[1], done: false };\n case 5: _.label++; y = op[1]; op = [0]; continue;\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\n if (t[2]) _.ops.pop();\n _.trys.pop(); continue;\n }\n op = body.call(thisArg, _);\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\n }\n}\n\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n});\n\nexport function __exportStar(m, o) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\n}\n\nexport function __values(o) {\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\n if (m) return m.call(o);\n if (o && typeof o.length === \"number\") return {\n next: function () {\n if (o && i >= o.length) o = void 0;\n return { value: o && o[i++], done: !o };\n }\n };\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\n}\n\nexport function __read(o, n) {\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\n if (!m) return o;\n var i = m.call(o), r, ar = [], e;\n try {\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\n }\n catch (error) { e = { error: error }; }\n finally {\n try {\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\n }\n finally { if (e) throw e.error; }\n }\n return ar;\n}\n\n/** @deprecated */\nexport function __spread() {\n for (var ar = [], i = 0; i < arguments.length; i++)\n ar = ar.concat(__read(arguments[i]));\n return ar;\n}\n\n/** @deprecated */\nexport function __spreadArrays() {\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\n r[k] = a[j];\n return r;\n}\n\nexport function __spreadArray(to, from, pack) {\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\n if (ar || !(i in from)) {\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\n ar[i] = from[i];\n }\n }\n return to.concat(ar || Array.prototype.slice.call(from));\n}\n\nexport function __await(v) {\n return this instanceof __await ? (this.v = v, this) : new __await(v);\n}\n\nexport function __asyncGenerator(thisArg, _arguments, generator) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\n function fulfill(value) { resume(\"next\", value); }\n function reject(value) { resume(\"throw\", value); }\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\n}\n\nexport function __asyncDelegator(o) {\n var i, p;\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\n}\n\nexport function __asyncValues(o) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var m = o[Symbol.asyncIterator], i;\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);\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); }); }; }\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\n}\n\nexport function __makeTemplateObject(cooked, raw) {\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\n return cooked;\n};\n\nvar __setModuleDefault = Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n};\n\nexport function __importStar(mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\n __setModuleDefault(result, mod);\n return result;\n}\n\nexport function __importDefault(mod) {\n return (mod && mod.__esModule) ? mod : { default: mod };\n}\n\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\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\");\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\n}\n\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\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\");\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\n}\n\nexport function __classPrivateFieldIn(state, receiver) {\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\n}\n\nexport function __addDisposableResource(env, value, async) {\n if (value !== null && value !== void 0) {\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\n var dispose, inner;\n if (async) {\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\n dispose = value[Symbol.asyncDispose];\n }\n if (dispose === void 0) {\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\n dispose = value[Symbol.dispose];\n if (async) inner = dispose;\n }\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\n env.stack.push({ value: value, dispose: dispose, async: async });\n }\n else if (async) {\n env.stack.push({ async: true });\n }\n return value;\n}\n\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\n var e = new Error(message);\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\n};\n\nexport function __disposeResources(env) {\n function fail(e) {\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\n env.hasError = true;\n }\n var r, s = 0;\n function next() {\n while (r = env.stack.pop()) {\n try {\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\n if (r.dispose) {\n var result = r.dispose.call(r.value);\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\n }\n else s |= 1;\n }\n catch (e) {\n fail(e);\n }\n }\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\n if (env.hasError) throw env.error;\n }\n return next();\n}\n\nexport default {\n __extends,\n __assign,\n __rest,\n __decorate,\n __param,\n __metadata,\n __awaiter,\n __generator,\n __createBinding,\n __exportStar,\n __values,\n __read,\n __spread,\n __spreadArrays,\n __spreadArray,\n __await,\n __asyncGenerator,\n __asyncDelegator,\n __asyncValues,\n __makeTemplateObject,\n __importStar,\n __importDefault,\n __classPrivateFieldGet,\n __classPrivateFieldSet,\n __classPrivateFieldIn,\n __addDisposableResource,\n __disposeResources,\n};\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 { Subject } from './Subject';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\n\n/**\n * A variant of Subject that requires an initial value and emits its current\n * value whenever it is subscribed to.\n *\n * @class BehaviorSubject\n */\nexport class BehaviorSubject extends Subject {\n constructor(private _value: T) {\n super();\n }\n\n get value(): T {\n return this.getValue();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n const subscription = super._subscribe(subscriber);\n !subscription.closed && subscriber.next(this._value);\n return subscription;\n }\n\n getValue(): T {\n const { hasError, thrownError, _value } = this;\n if (hasError) {\n throw thrownError;\n }\n this._throwIfClosed();\n return _value;\n }\n\n next(value: T): void {\n super.next((this._value = value));\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 { Subscription } from '../Subscription';\nimport { QueueScheduler } from './QueueScheduler';\nimport { SchedulerAction } from '../types';\nimport { TimerHandle } from './timerHandle';\n\nexport class QueueAction extends AsyncAction {\n constructor(protected scheduler: QueueScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (delay > 0) {\n return super.schedule(state, delay);\n }\n this.delay = delay;\n this.state = state;\n this.scheduler.flush(this);\n return this;\n }\n\n public execute(state: T, delay: number): any {\n return delay > 0 || this.closed ? super.execute(state, delay) : this._execute(state, delay);\n }\n\n protected requestAsyncId(scheduler: QueueScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\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\n if ((delay != null && delay > 0) || (delay == null && this.delay > 0)) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n\n // Otherwise flush the scheduler starting with this action.\n scheduler.flush(this);\n\n // HACK: In the past, this was returning `void`. However, `void` isn't a valid\n // `TimerHandle`, and generally the return value here isn't really used. So the\n // compromise is to return `0` which is both \"falsy\" and a valid `TimerHandle`,\n // as opposed to refactoring every other instanceo of `requestAsyncId`.\n return 0;\n }\n}\n", "import { AsyncScheduler } from './AsyncScheduler';\n\nexport class QueueScheduler extends AsyncScheduler {\n}\n", "import { QueueAction } from './QueueAction';\nimport { QueueScheduler } from './QueueScheduler';\n\n/**\n *\n * Queue Scheduler\n *\n * Put every next task on a queue, instead of executing it immediately\n *\n * `queue` scheduler, when used with delay, behaves the same as {@link asyncScheduler} scheduler.\n *\n * When used without delay, it schedules given task synchronously - executes it right when\n * it is scheduled. However when called recursively, that is when inside the scheduled task,\n * another task is scheduled with queue scheduler, instead of executing immediately as well,\n * that task will be put on a queue and wait for current one to finish.\n *\n * This means that when you execute task with `queue` scheduler, you are sure it will end\n * before any other task scheduled with that scheduler will start.\n *\n * ## Examples\n * Schedule recursively first, then do something\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(() => {\n * queueScheduler.schedule(() => console.log('second')); // will not happen now, but will be put on a queue\n *\n * console.log('first');\n * });\n *\n * // Logs:\n * // \"first\"\n * // \"second\"\n * ```\n *\n * Reschedule itself recursively\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(function(state) {\n * if (state !== 0) {\n * console.log('before', state);\n * this.schedule(state - 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * console.log('after', state);\n * }\n * }, 0, 3);\n *\n * // In scheduler that runs recursively, you would expect:\n * // \"before\", 3\n * // \"before\", 2\n * // \"before\", 1\n * // \"after\", 1\n * // \"after\", 2\n * // \"after\", 3\n *\n * // But with queue it logs:\n * // \"before\", 3\n * // \"after\", 3\n * // \"before\", 2\n * // \"after\", 2\n * // \"before\", 1\n * // \"after\", 1\n * ```\n */\n\nexport const queueScheduler = new QueueScheduler(QueueAction);\n\n/**\n * @deprecated Renamed to {@link queueScheduler}. Will be removed in v8.\n */\nexport const queue = queueScheduler;\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 + +
+
<iframe
+        src="https://netherlands3d.eu/twin/?origin=161088,503050,300&features=terrain,buildings,trees,sun"
+        width="640"
+        height="480"
+        frameborder="0"
+></iframe>
+
+
    +
  • +

    src: Set this attribute to the URL of the Netherlands3D digital twin. You can customize the location and features + using the query string.

    +
  • +
  • +

    width and height: Adjust these attributes to define the dimensions of the iframe on your webpage. Make sure the + width + fits your design, and the height provides an optimal viewing experience.

    +
  • +
  • +

    frameborder: Set this attribute to "0" to remove the iframe border.

    +
  • +
+

Configuring Location and Features

+

You can customize the location and features displayed in your embedded Netherlands3D using a query string. In the query +string, the origin parameter should follow the format origin=X,Y,elevation, where X and Y is the position of +the camera in Rijksdriehoekformaat (EPSG:28992) coordinates and elevation is the desired +viewing elevation.

+

Here's an example URL with a query string for your reference:

+
https://netherlands3d.eu/twin/?origin=161088,503050,300&features=terrain,buildings,trees,sun
+
+
    +
  • +

    origin: This parameter sets the initial view location in the + Rijksdriehoekformaat (EPSG:28992) coordinate system. It follows the format + origin=X,Y,elevation, where elevation is a value between 0 and 1400.

    +
  • +
  • +

    features: Use this parameter to specify which features to display in the digital twin. You can include multiple + features, separated by commas.

    +
  • +
+

Using the Setup Wizard

+

For an even easier way to generate the query string with the desired configuration, you can visit the +Netherlands3D Setup Wizard (https://netherlands3d.eu/twin/). This interactive tool +allows you to select the location, features, and other options, and it generates the corresponding query string for your +iframe.

+

Once you have the query string, insert it into the src attribute of your iframe code, and you'll have a fully +customized embedded Netherlands3D digital twin on your website.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/features/floating-origin/index.html b/docs/developers/features/floating-origin/index.html new file mode 100644 index 0000000..52fade2 --- /dev/null +++ b/docs/developers/features/floating-origin/index.html @@ -0,0 +1,2012 @@ + + + + + + + + + + + + + + + + + + + + + + + Working with large distances (Floating Origin) - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + +

Working with large distances (Floating Origin)

+

The Floating Origin System is a solution designed to manage 3D world coordinates in Unity3D. It operates on the idea of +retaining the real-world coordinates of objects while the origin within the computer-generated world adjusts. The +Floating Origin System achieves this through the integral use of the following components:

+

Overview of the Floating Origin System

+

Before the deep-dive into the components of the Floating Origin system, a basic understanding of the concepts in this +system is required:

+

Real world coordinates and Unity positions

+

In Unity, the transform component is responsible for tracking the position relative to Unity's (0,0,0) origin point, +when working with large worlds -such as our real-world- this system no longer works due to floating point precision +errors. To solve this, a second mechanic for tracking positions is introduced using real world coordinates in +the WGS84 lat/long ECEF coordinate system.

+

As the distances expressed by real world coordinates are greater than Unity's floating point based transforms, it is +needed to track the real world coordinate of the (0,0,0) origin -called the Floating Origin- and the coordinates +of real world objects.

+

When the camera moves thus far away from the Floating Origin that floating point issues may occur, the origin will +change its real-world coordinate to be nearer to the camera's real-world coordinate and shift the Unity position of all +real-world objects to match the new real-world coordinate of Unity's (0,0,0) origin.

+

(Floating) Origin

+

The Origin represents the real world coordinates of Unity's (0,0,0) origin and is responsible for tracking the distance +of the Camera from Unity's (0,0,0) origin. If the Camera moves too far away, rendering and calculation anomalies can +happen due to floating point precision issues and all real world objects -including the origin's own real-world +coordinates- need to be shifted so that all elements are back into a safe distance in Unity's coordinate system.

+

Shifting

+

Shifting is the term for when real world objects need to have their Unity position adjusted to get closer to the +Floating Origin. Because the origin's Unity position never changes -it is always at 0,0,0- but its real-world coordinate +can change, we need to respond to that by moving real-world objects' Unity position. As soon as this happened, all Game +Objects should be within a safe distance from 0,0,0 again.

+

Real-world objects

+

Real-world objects -sometimes referred to as spatial objects- are game objects whose location is tied to the real-world +and have a location in the real world. An example may be a windmill, where a UI element is an example of something that +is not.

+

Real-world objects have a World Transform component that tracks the real-world position and orientation of the object +and when a shift is emitted, they will reposition themselves or its parts.

+

World Transform

+

The World Transform tracks the real-world position and orientation for a real-world object. The World Transform also +reacts to the Origin calling for a shift, but delegates the actual work to a World Transform Shifter component. This +allows for objects to respond to shifts differently.

+

World Transform Shifter

+

The World Transform Shifter is the component that handles the shifting of a Game Object. Usually you do not need to +provide a specific World Transform Shifter with a World Transform, in that case the default +GameObjectWorldTransformShifter is used.

+

Origin

+

In the Floating Origin System, the Origin is an important concept referring to the focal point within the Unity game +world. It is crucial for large scale projects where precision becomes a challenge.

+

The Origin is typically associated with its own separate GameObject. This GameObject serves as an indicator or marker of +the origin point, and doesn't have any visual representation in the game environment. Its primary role is to maintain +the game world's central point and handle shifts in that point to ensure accuracy and consistency in objects' locations +in the world.

+

To use the Origin, create an empty GameObject in your scene. This empty GameObject often doesn't need any other +components besides Origin. Then, attach the Origin script to this GameObject. Now, the Origin GameObject will +continually monitor its distance from the camera (or a specified mainShifter object) while playing.

+

Performance Optimizations in Origin

+

Understanding and managing performance is key in any system, and the Floating Origin System includes several performance +optimizations to ensure efficient operation.

+

Using sqrMagnitude Instead of Magnitude

+

The process of calculating distance often involves the operation of square roots, which introduce computational +complexity. In the Floating Origin System, we use Unity's built-in sqrMagnitude method instead of magnitude for distance +calculations.

+

The sqrMagnitude method calculates the square of the magnitude of a distance vector, which omits the need for a square +root operation. Since the Origin uses a squared threshold for determining shifting necessity, this optimization aligns +perfectly. Simply put, both the mainShifter's distance and the threshold are squared values, so no square root operation +is needed, resulting in improved performance without comprising correct operation.

+

Using a Coroutine to Wait Until the End of Frame

+

The Floating Origin System uses Unity's Coroutine feature to postpone the shifting evaluation to the end of each frame. +The shifting operation checks at the end of the frame if it needs to reposition the Origin based on the final position +of the mainShifter.

+

This optimization minimizes unnecessary Origin shifting, as multiple changes to the mainShifter position within a single +frame will only trigger the shifting operation once at the end of the frame. This approach effectively reduces +computational load by minimizing the frequency of costly shifting operations.

+

The Origin triggers the shifts

+

The Origin in the Floating Origin System demonstrates an example of encapsulation, a fundamental principle of +object-oriented programming. Instead of distributing responsibilities across multiple components (like making the camera +responsible for calculating distances and triggering shifts), the Floating Origin System centralizes these +responsibilities within the Origin.

+

Encapsulating Responsibilities

+

The Origin is responsible for continually monitoring its distance from the camera (or another defined mainShifter +object). Using a pre-determined threshold, the Origin decides when the shift operation needs to occur. Once that +threshold is surpassed, the Origin triggers a shift operation to reestablish itself closer to the camera or mainShifter. +This design decision encapsulates the behavior within the Origin, making it a standalone component that can +independently determine and respond to shifts.

+

Performance and Design Improvements

+

The encapsulation model presents several advantages:

+
    +
  • +

    Performance: By monitoring the mainShifter's distance at the end of each frame and shifting only when necessary, + the Origin can optimize performance and reduce unnecessary shifts, irrespective of how many changes occur to the + mainShifter during the frame.

    +
  • +
  • +

    Design Simplicity: Having the Origin act as the central point of control simplifies task delegation amongst + components and makes the overall system easier to understand.

    +
  • +
  • +

    Encapsulation: By keeping related functions within the Origin, the design favors encapsulation, ensuring a clean + system where components' responsibilities are clearly defined and do not blend.

    +
  • +
+

WorldTransform

+
+

An analogy: transforms and rigidbodies

+

To illustrate how the WorldTransform and their Shifters work, you can compare them to the way Transform and RigidBodies +work in Unity. The Transform holds the position, rotation and scale and can be manipulated; but the actual physics +interaction -movement forced by external factors- is accomplished through a RigidBody component, or even a +CharacterController.

+

In Unity, you have one transform and one type of RigidBody/Character Controller; and with the WorldTransform this +works similarly: the WorldTransform works like a Transform and records the position of the real-world object, where a +WorldTransformShifter functions like a RigidBody and exerts force on the WorldTransform when a Shift happens.

+
+

The WorldTransform is effectively the real-world equivalent of an object's location within the Unity game world. When +the Origin shifts, the WorldTransform is adjusted correspondingly. Meanwhile, Unity's native Transform class remains +untouched. This results in two interconnected, but separate, coordinate systems.

+

Not all objects will need to handle a shift in the same way. The default is to shift the GameObject itself, but some +objects are containers whose children need to shift and not themselves -example: tile handlers- or where transformation +matrices are used instead of transform components -example: instanced objects-. By delegating this responsibility to +a World Transform Shifter all real-world objects will track their coordinates in the same way but have different ways +to react to shift events.

+

WorldTransformShifter

+

The WorldTransformShifter is meant to manage the effects of moving the Origin on GameObjects. Custom behaviors for +different types of GameObjects when the Origin shifts can be defined by implementing the method ShiftTo in derived +classes of WorldTransformShifter.

+

When it is not the GameObject itself whose position needs to change but a child or something not directly linked to the +transform position, then you need to use one of the other strategies. See below for more information.

+
+

Tip

+

The WorldTransform and its Shifters is an implementation of the Strategy design pattern.

+
+

Specialized Shifters

+

Specialized Shifters are used to manage Origin shifts for unique types of GameObjects not part of the Unity base +package. These could be packages like Netherlands3D 3DTiles that have their unique behaviors. +An example of this is ThreeDTilesWorldTransformShifter. This sub-class handles shifts for child GameObjects associated +with the Content component. When an Origin shift happens, it iterates over all Content components and adjusts the +positions of all their children.

+

Usage

+

To use the Floating Origin System within your Unity projects:

+
    +
  1. Attach the Origin component to a GameObject that will act as the world origin.
  2. +
  3. For objects that should maintain real-world positions, attach the WorldTransform component.
  4. +
  5. For specialized objects, for example, objects from the Netherlands3D 3DTiles package, attach a specialized + WorldTransformShifter, like ThreeDTilesWorldTransformShifter.
  6. +
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/features/geojson/index.html b/docs/developers/features/geojson/index.html new file mode 100644 index 0000000..4a5bb77 --- /dev/null +++ b/docs/developers/features/geojson/index.html @@ -0,0 +1,1602 @@ + + + + + + + + + + + + + + + + + + + + + + + GeoJSON - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

GeoJSON

+

Netherlands3D offers support for importing GeoJSON data.

+

Features

+
    +
  • Currently only GeoJSON files are supported. The default file extensions are .json or .geojson. In case custom file + types should be supported, these can be added in the FileOpen script (to allow selection of other file types) and in + the FileTypeAdapter (to process the opened file).
  • +
  • The GeoJSON parsing is spread over multiple frames, (default is max 0.01s of parsing per frame) as to not freeze the + application when importing large files. This duration can be changed by changing the variable maxParseDuration in + GeoJSONLayer.cs
  • +
  • GeoJSON features of type Polygon, MultiPolygon, LineString, MultiLineString, Point, and MultiPoint are supported.
  • +
  • GeoJSON coordinates are passed to + the eu.netherlands3d.coordinates package using the + parsed CRS in order to convert them to Unity coordinates (see also the section on limitations for CRS limitations). A + CRS object of type Linked CRS is currently not supported
  • +
  • A material can be assigned to the GeoJSONLayer that determines the appearance of the visualization.
  • +
  • An event is invoked when parsing fails, to allow for display of error messages on screen.
  • +
  • GeoJSON files can be uploaded and embedded in the NL3D Project as assets
  • +
  • URLs pointing to GeoJSON files -such as https://raw.githubusercontent.com/blackmad/neighborhoods/refs/heads/master/utrecht.geojson- + will be added as a remote url so that they are reloaded every time a project opens
  • +
+

Limitations

+

The GeoJSON support currently has the following limitations:

+
    +
  • A LinkedCRS object is parsed correctly in the GeoJSONLayer, however it is currently not interpreted to provide a valid + input for the visualization through + the eu.netherlands3d.coordinates package.
  • +
  • All features of the Type Polygon and MultiPolygon are combined to form 1 layer in the application
  • +
  • All features of the Type LineString and MultiLineString are combined to form 1 layer in the application
  • +
  • All features of the Type Point and MultiPoint are combined to form 1 layer in the application
  • +
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/functionalities/index.html b/docs/developers/functionalities/index.html new file mode 100644 index 0000000..57dd680 --- /dev/null +++ b/docs/developers/functionalities/index.html @@ -0,0 +1,1717 @@ + + + + + + + + + + + + + + + + + + + + + + + Functionalities - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Functionalities

+

At the core of Netherlands3D, various functionalities serve as building blocks. Each functionality represents a distinct +element or feature of the platform, designed to perform specific tasks. +These functionalities are modular, meaning they can be enabled or disabled individually based on user requirements or +the needs of a particular project.

+

This ability to toggle functionalities on and off ensures that you have control over the features you want to deploy in +your project.

+

Configuring Functionalities

+

Furthermore, each functionality can be individually configured according to specific project requirements. Read more +about this in detail in the chapter about configuration.

+

Through additional configuration settings, you have the flexibility to adjust the functionality's behavior and +characteristics to match your project's needs. You'll have access to a broad range of settings allowing you to refine +each element of your project in detail.

+

To give some context to the above: examples of additional configuration options are API keys, URLs of services, +default values for new projects and more.

+

How does it work

+

In Unity's context for the Netherlands3D platform, each functionality is encapsulated in a ScriptableObject. Exploring +further, you'll discover that each ScriptableObject is highly configurable, granting you the ability to tailor it to +satisfy specific demands.

+

Key Components

+

ScriptableObjects

+

The ScriptableObjects for the functionalities can be located in the Scriptables/Functionalities folder. Each +ScriptableObject represents a distinct functionality in the application, and can be configured to define its behavior +and properties within the application.

+

Functionality System

+

The code powering the functionality system is hosted within the embedded package named +eu.netherlands3d.eu.twin-functionalities. Digging into this package reveals the design and implementation of +the system that manages and orchestrates the functionalities.

+

The Functionality Script

+

At the core, the Functionality script exists as a foundational unit for the ScriptableObject. Here is where you define +key attributes such as its title, description, ID, and any additional configuration. This script performs the following +roles:

+
    +
  1. +

    Title and Description: The title and description fields of the ScriptableObject are primarily designed for your +reference in the settings screen of the application.

    +
  2. +
  3. +

    Functionality ID: The ID (or code) is used to refer to the functionality from the configuration file. This ID attribute +is instrumental in organizing and managing configurations of functionalities within the system robustly.

    +
  4. +
  5. +

    Enabled State: The enabled state of the functionality indicates whether it's active or inactive. The system can query +this status to determine the functionality's availability and act accordingly in any part of the application.

    +
  6. +
  7. +

    Additional Configuration: The ScriptableObject also includes an additional configuration section where you can define +supplementary settings and properties that control its behavior within your application.

    +
  8. +
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/index.html b/docs/developers/index.html new file mode 100644 index 0000000..6f6dcf9 --- /dev/null +++ b/docs/developers/index.html @@ -0,0 +1,1547 @@ + + + + + + + + + + + + + + + + + + + + + + + Introduction - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Introduction

+ +

Welcome to the technical documentation for Netherlands3D, an advanced Digital Twin platform designed to visualize and +analyze geospatial data in a 3D environment. This documentation is intended for developers who want to understand how +the platform works, explore its architecture, and contribute new modules or functionalities.

+
+

Why English?

+

While the rest of this website and the user-facing documentation is provided in Dutch, in accordance with +government guidelines, the technical documentation is written in English.

+

This decision has been made to ensure a broad range of developers can contribute to Netherlands3D and to maintain +consistency with the platform's codebase, which uses English terms and concepts. By using English in the technical +documentation, we aim to make it easier for developers to map concepts between the documentation and the code +itself, facilitating a smoother development and contribution process.

+
+

Overview

+

Netherlands3D is built on a modular architecture, allowing for extensibility. Developers can add, modify, or replace +individual modules without affecting the entire system. The platform integrates data from various +sources, such as 3D Tiles, the national georegister, and sensor information, to create an interactive and dynamic 3D +environment for spatial analysis and visualization.

+

This documentation will provide you with a detailed understanding of how the system operates under the hood, including +its component-based architecture, event-driven communication, and handling of secure data layers.

+

Key Technologies

+
    +
  • Unity3D: The platform is developed using Unity, leveraging its robust rendering capabilities to display + high-performance 3D models and large-scale datasets.
  • +
  • Scriptable Objects: Used extensively to manage data, configuration, and communication between different modules + and functionalities.
  • +
  • Event-Driven System: Internal communication within modules is handled via UnityEvents, while inter-module + interactions are driven by Scriptable Object Events, which allow asynchronous communication and keep the + system decoupled.
  • +
+

This documentation will guide you through the internal workings of the platform, including detailed API references, +examples of module creation, and best practices for integrating new functionality.

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/layers-with-authentication/index.html b/docs/developers/layers-with-authentication/index.html new file mode 100644 index 0000000..0c5169d --- /dev/null +++ b/docs/developers/layers-with-authentication/index.html @@ -0,0 +1,1765 @@ + + + + + + + + + + + + + + + + + + + + + + + Managing Layers with Authentication - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + +

Managing Layers with Authentication

+

In Netherlands3D, certain data layers require user authentication to be accessed and displayed. This guide explains how +the system manages authentication for these layers, handles different types of credentials, and ensures sensitive +information remains protected while maintaining layer visibility settings.

+

1. Layer Authentication Process

+

When a user adds a layer that requires authentication, they are prompted to provide credentials in one of the +following forms:

+
    +
  • Username and Password
  • +
  • Access Key
  • +
+

Regardless of the credential type, the system records that the source associated with the layer (typically the layer's +URL) requires authentication. This information is stored in a KeyVault.

+

2. Credential Storage and Security

+

To ensure that sensitive information is never compromised:

+
    +
  • Credentials are not stored in the project file. Instead, the KeyVault retains only the metadata necessary to + recognize that the layer is protected and requires credentials.
  • +
  • The KeyVault is saved alongside the project, but it does not contain the actual credentials, only the + authentication type and associated layer source.
  • +
  • This design prevents sensitive credentials from being shared when the project file is distributed to third parties, + ensuring privacy and security.
  • +
+

3. Handling Credentials: HTTP Basic Authentication vs. Access Key

+

The system supports two main methods of authentication, depending on the type of credentials provided by the user:

+
    +
  • +

    HTTP Basic Authentication (Username and Password): If the user provides a username and password, the system + queries the protected endpoint using HTTP Basic Authentication. This involves sending the credentials in the request + header in accordance with the Basic Authentication protocol.

    +
  • +
  • +

    Access Key: If an access key is provided, the system attempts to use the key within the query string of the + endpoint's URL. The application performs auto-detection to determine the appropriate query parameter name by testing + common parameters like:

    +
  • +
  • +

    code

    +
  • +
  • key
  • +
  • token
  • +
+

The system will make repeated requests to the endpoint using each query parameter name and observe the HTTP response. + If the server returns an HTTP status code 401 (Unauthorized) or 403 (Forbidden) for each attempt, the + application concludes that the provided credential is incorrect. If a valid credential is found, the system records + the correct query parameter name in the KeyVault for future use.

+

4. Handling Layers Without Credentials

+

If a layer requiring authentication is added (either manually by the user or through reloading a project), and no +valid credentials are provided, the following behavior occurs:

+
    +
  • The layer enters a non-visible state, as it cannot be displayed without valid credentials.
  • +
  • The system issues a warning prompting the user to enter (or re-enter) credentials for the layer.
  • +
+

Note: The user must resolve this issue by supplying valid credentials before the layer can be displayed.

+

5. Maintaining Layer Visibility Settings

+

One critical aspect of this system is that the visibility of the layer is preserved, even if it cannot be displayed +due to authentication issues:

+
    +
  • If the layer was hidden (via the eye icon) when the project was saved, it will remain hidden after authentication + is provided.
  • +
  • Similarly, if the layer was visible, it will become visible once valid credentials are entered and the authentication + issue is resolved.
  • +
+

This approach ensures that the visibility state is consistent and independent of the authentication status.

+

6. Temporary State of Authentication

+

The system does not store the authentication status (whether credentials have been verified or not) in the project +file. This is because authentication status is a temporary state that should not persist across sessions. Each time +a project is loaded, the authentication process must be reinitiated for protected layers, ensuring that credentials are +entered securely and as needed.

+

7. Need to re-authenticate reopening the application

+

A key implication of this design is that users will need to re-authenticate any protected layers every time they reopen +the application or load a saved project file. Since credentials are never stored in the project file for security +reasons, the system requires users to provide valid credentials again when the project is reloaded.

+

This approach ensures that sensitive credentials are handled securely, but it also means that re-authentication is +necessary whenever a protected layer is accessed in a new session. Users should be aware that any protected layers will +initially be in a non-visible state until the required credentials are provided.

+

8. Warnings and User Flow

+

The current user flow for managing layer authentication includes:

+
    +
  • A warning message indicating that the layer cannot be displayed due to missing or invalid credentials.
  • +
  • A prompt that allows the user to supply new credentials for the layer.
  • +
  • Once the credentials are provided and validated, the layer’s display status is updated accordingly, without altering + its pre-existing visibility state.
  • +
+

By following this process, Netherlands3D ensures that users can securely manage layers requiring authentication while +maintaining proper visibility states and safeguarding sensitive data.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/layers/index.html b/docs/developers/layers/index.html new file mode 100644 index 0000000..052bcb3 --- /dev/null +++ b/docs/developers/layers/index.html @@ -0,0 +1,1818 @@ + + + + + + + + + + + + + + + + + + + + + + + Layers - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + +

Layers

+

Layers

+

Layers in Netherlands3D are objects that have an effect on the 3D environment. Layers are hierarchical, similar to +GameObjects in Unity. Because of this. the Layer data structure makes use of Unity's GameObject/Transform hierarchy in +order to minimize custom coding of the data structure, and to visualize the data structure in the Unity Editor +hierarchy.

+

Functionality of layers

+

All layers have the following basic functionality:

+
    +
  • Parenting: This works similar to the Unity Hierarchy.
  • +
  • Reordering: This works similar to changing the sibling index within the same parent in the Unity hierarchy.
  • +
  • Visibility: This works similar to setting a GameObject active/inactive in the Unity hierarchy.
  • +
  • Properties: Each layer can have its own settings specific to the layer type, or even more specific to the content of + the layer. This is similar to how the inspector in Unity will display all components and serialized fields of a + GameObject.
  • +
+

The layer data structure hierarchy is managed by the +class LayerData. There should +only be 1 LayerData object in the scene.

+

Layer types

+

Layers are split in 2 categories:

+
    +
  1. Regular layers that do not need their own GameObject hierarchy (basic layers)
  2. +
  3. Layers that do require a GameObject hierarchy (referenced layers)
  4. +
+

Basic layers

+

The first category of layers is the most simple. These layers are an extension of the LayerNL3DBase class.

+

Folder layer:

+

The most simple form of a Layer in this category is a FolderLayer. This layers has no functionality, except for +providing the user with a way to organise other layers. It is similar to an empty GameObject.

+

Polygon selection layer:

+

A PolygonSelectionLayer is a layer that represents a polygon in the 3D environment. This layer can be created by +providing a List<Vector3> of points (either by user input or from another source). This will create a layer in the +hierarchy, with a visualisation that will listen to user input so that the layer can be selected by the user by clicking +on the visualisation in the scene.

+

Referenced layers

+

In case a layer has an internal hierarchy (such as a hierarchy of object parts, or a container with different tile +objects) a layer can be integrated in the layer system as a referenced layer. A referenced layer exists outside of the +Layer data structure, but has a connection to it through a ReferencedProxyLayer . +The ReferencedProxyLayer will pass the required actions that affect the layer to its reference outside of the layer +hierarchy, thereby making the referenced layer comply with the layer functionalities.

+

In order to do this, the layer object outside of the hierarchy must have an extension of the abstract +class ReferencedLayer attached to it. This class will automatically create the ReferencedProxyLayer, thereby +creating the connection to the layer system.

+

The following Layers are referenced layers:

+

Hierarchical object layer:

+

A HierarchicalObjectLayer is an object that can be placed in the 3D environment. By default, this object can be moved +by the transform handles. Objects of this type either come from a library of developer defined objects, or by user +uploads. The most simple Hierarchical object layer is a GameObject with a MeshFilter, MeshRenderer, Collider, +and HierarchicalObjectLayer component attached. More complex Hierarchical object layers can have nested GameObjects +with each their own internal logic.

+

Cartesian tile layer:

+

A CartesianTileLayer is a layer that makes use of the Netherlands3D custom tile file format. The objects and tiles in +the layer are managed by the TileHandler and through the class CartesianTileLayer can interact with the layer +system. For more information see the section on Cartesian Tiles.

+

3D tile layer:

+

A Tile3DLayer2 is a layer that makes use of the 3D Tiles file format. These layers are managed by Read3DTileset and +through the class Tile3DLayer2 can interact with the layer system. For more information see the section on 3D Tiles.

+

Object scatter layer:

+

An ObjectScatterLayer is a special type of layer that will scatter the combined mesh of a HierarchicalObjectLayer in +the area defined by a PolygonLayer. To do this, create a PolygonLayer and make a HierarchicalObjectLayer the child +of the PolygonLayer. This will convert the HierarchicalObjectLayer to an ObjectScatterLayer. Unparenting the +ObjectScatterLayer from a PolygonLayer will revert the layer back to a HierarchicalObjectLayer. +The ObjectScatterLayer has scatter settings that determine how the scattering should occur.

+
+

Warning

+

The scattering is achieved through GPUInstancing, and therefore the mesh and material should support this. All +objects in the ObjectLibrary support scattering.

+
+

Layer UI

+

Each Layer can have an associated UI component defined by the +class LayerUI. This component +controls the UI elements associated with the layer. Interacting with the LayerUI will affect the +corresponding LayerNL3DBase. +The Layer UI hierarcy is managed +by LayerManager. This class +holds a references to the layer type icons that are requested by LayerUI.

+

Creating your own layers of an existing type

+

In order to add one of your own layers as an option through the existing UI, the following steps should be followed:

+
    +
  1. Create a prefab with the layer object. Make sure this object has a component of either LayerNL3DBase + or ReferencedLayer attached to it.
  2. +
  3. Instantiate the prefab. If this is done in the existing LayerPanel UI, add a button or toggle in the + prefab AddLayerPanel.prefab in the appropriate panel sub section. Then have the script that controls the button or + toggle logic instantiate the prefab made in step 1.
  4. +
+

Creating your own layers of a new type

+

Follow the following steps to create a new layer type:

+
    +
  1. Choose if the new layer type can be a simple layer (direct extension of LayerNL3DBase) or it needs to be a more + complex referenced layer (extension of ReferencedLayer).
  2. +
  3. Extend the chosen class and implement the required methods.
  4. +
  5. Add your own logic to the new class
  6. +
  7. Add a reference to the appropriate type and icon in LayerManager to ensure the new layer does not use the default ? + icon.
  8. +
+

Layers and Functionalities

+

Specific layers or specific types of layers can also be enabled/disabled with the functionalities system. Follow these +steps to do this:

+
    +
  1. Add a FunctionalityListener to the prefab of the layer. Add the functionality you want to link the layer to, and in + the OnDisableFunctionality event, add a function that will destroy the gameObject itself. This will clean up any + active layers when disabling the functionality.
  2. +
  3. For any buttons that instantiate the prefab above, add a FunctionalityListener to the button that will + enable/disable the button's gameObject when the functionality is enabled/disabled respectively. This will ensure the + user cannot instantiate layers from functionalities that are disabled.
  4. +
  5. (Optional) In case your layers should by default spawn when the functionality is enabled, add a PrefabSpawner script + to the scene where the functionality and the associated prefab are set in the inspector.
  6. +
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/packages/creating-and-managing/index.html b/docs/developers/packages/creating-and-managing/index.html new file mode 100644 index 0000000..594c80c --- /dev/null +++ b/docs/developers/packages/creating-and-managing/index.html @@ -0,0 +1,1947 @@ + + + + + + + + + + + + + + + + + + + + + + + Creating and managing Packages - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + +

Creating and managing Packages

+

Netherlands3D is a framework where you, as developer, can pick the functionality that you need from a series of +packages. This guide will empower you to develop your own packages by providing step-by-step instructions how +you can create your own package, release it and last, but not least, publish it to OpenUPM.

+

Variables / Placeholders

+

Before we get started, let's define some terms as if they were variables. This documentation and the template files +in the Gist mentioned in the chapter Creating a new package uses various placeholders. These +can recognized as they are between brackets, for example {NAME}.

+

This table provides an overview of them and their meaning.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameExplanationExample
NAMEThe name of the code repository, this is usually the DISPLAY_NAME in PascalCase. It is recommended to limit the name to two words, if more words add clarity than that takes precedence.PeriodicSnapshots
PACKAGE_NAMEThe package name as known in the package manager and/or package.json; always starts with eu.netherlands3d. and followed by the display name -or similar- in kebab-caseeu.netherlands3d.periodic-snapshots
DISPLAY_NAMEThe human-readable name of a package, shown in the README or in the Package ManagerPeriodic Snapshots
REPOSITORY_NAMEAlias of NAME, used to make explicit that we want the repository name there.PeriodicSnapshots
VERSIONThe version number in the package.json, according to SemVer. Important: contrary to the git tag, this does not start with a letter v1.0.1
DESCRIPTIONA single-line short description describing the package, can also be used on Github as the description for the repositoryThis package provides the means to do take a series of snapshots for specific moments throughout the year.
LONG_DESCRIPTIONThe description of the package detailing what it is used for.
USAGE_INFORMATIONDocumentation how to use this package.See existing packages for examples
NOTEOne or more entries in the CHANGELOG -according to Keep A Changelog- describing what has been added, changed, removed or deprecated in a package
+

This vocabulary and their definitions will help to have a consistent experience when working with packages.

+

Creating a new package

+

When creating a new package, it is recommended to first create it as an embedded package in the +Netherlands3D twin project. This will help to prototype and experiment until +the point where it is stable enough to be distributed.

+

This can be done using the following steps:

+
    +
  1. +

    Clone the Netherlands3D Twin project, located at https://github.com/Netherlands3D/twin.

    +
  2. +
  3. +

    Make sure Unity is not open while performing the following steps; it generally tends to crash when it updates while setting up a package.

    +
  4. +
  5. +

    Create a directory for your embedded package in the + Packages folder.

    +
    +

    Important

    +

    The directory name MUST follow the structure eu.netherlands3d.[name] where name is in lower-case kebab-case.

    +
    +
  6. +
  7. +

    Copy the template files, located at https://gist.github.com/mvriel/8a8251b492d9d8f742da16667c49e412, and fill in the placeholders.

    +
  8. +
  9. +

    Set up the package according to the following recommended directory structure: https://docs.unity3d.com/Manual/cus-layout.html

    +
  10. +
  11. +

    Start Unity and allow for it to install the new package, you should see your new package in the "Installed Packages" section of the Package Manager with the tag Custom behind the name.

    +
  12. +
  13. +

    When you want to add Scripts to this package: Make sure you have created the folder Runtime\Scripts as a location to store them and add an Assembly Definition with the name {PACKAGE_NAME}.Runtime.

    +
  14. +
+

Promoting an embedded package

+

It takes time and effort to get a package into a usable and moderately stable condition. Because of this the recommended +way of working is to first create an embedded package in the Twin project -like an incubator project- and once it is +moderately stable: promote it to its own repository.

+

Promoting an embedded package is done by one of the project maintainers -if not alone for the fact that a repository +needs to be made- and follows the following sequence of steps:

+
    +
  1. +

    Create a repository whose name matches the {NAME} placeholder, thus a PascalCase representation of the + human-readable name.

    +
  2. +
  3. +

    Move all files from the Packages\{PACKAGE_NAME} folder in the Twin repository to the newly made repository's root + folder

    +
  4. +
  5. +

    Check and adjust, at least, the URL's for the LICENSE, README and CHANGELOG in the package.json.

    +
  6. +
  7. +

    Release and publish the promoted package, see the chapters on releasing and + publishing for the involved steps.

    +
  8. +
  9. +

    In the Twin project, add a reference to {PACKAGE_NAME} to the package.json or install the package through + the package manager.

    +
    +

    Important

    +

    After a package is added to OpenUPM for the first time, it can take between 30 and 60 minutes for it to +show up in the package listing.

    +
    +
  10. +
+

Once a package is promoted, you can no longer edit it as part of the Twin project; in the next chapter +Changing a Package it is described how you can change a promoted package without all too much +hassle.

+

Changing a package

+
+

To be written

+
+

Releasing a package

+

When you want to release a new version of a package you generally go through the following steps:

+
+

Tip

+

Before releasing, make sure you have tested your package in the Twin -or +another Netherlands3D-based project- installed as a local package.

+

Especially important is:

+
    +
  • verifying that the correct dependencies are set and installed
  • +
  • that the Assembly Definition can be used and
  • +
  • that the Assembly Definition does not depend on Assembly Definitions that are not a dependency of your package, + especially Netherlands3D.Core.Runtime should be avoided
  • +
+

Failure to do so may cause extra work because once a package is released, it is impossible to change something +in the release and a new release must be made.

+
+
    +
  1. +

    Go to the repository of your package. If there is none: see the chapter on + Promoting an embedded package and do that first.

    +
  2. +
  3. +

    Ensure the version number in the package.json is updated

    +
  4. +
  5. +

    Check the CHANGELOG.md:

    +
  6. +
  7. +

    Does it contain all changes since the last version?

    +
  8. +
  9. +

    Is the top-entry [Unreleased]? Fill in the new version number and add a date, see: https://keepachangelog.com/en/1.0.0/#effort

    +
  10. +
  11. +

    Ensure any changes in the above are in the main branch

    +
  12. +
  13. +

    Go to "Releases" on Github (https://github.com/Netherlands3D/{NAME}/releases) and

    +
  14. +
  15. +

    Draft a new release + Draft new release

    +
  16. +
  17. Click on "Choose a tag"
  18. +
  19. Enter the version number from the package.json with a preceding letter v, for example: v1.0.1.
  20. +
  21. Click on the option "Create new tag: v{VERSION} on publish"
  22. +
  23. (optional) Add a release title and description
  24. +
  25. Click on the button "Publish Release"
  26. +
+

Once a new release/tag has been made, your new release of your package is all set! If it has already been +published on OpenUPM before, no further action is needed. OpenUPM will automatically pick +up on the new tag and make the new version available.

+

When the package has not been published on OpenUPM yet, now is a good time to do it.

+

Publishing a package on OpenUPM

+

When a package is released for the first time, it needs to be published by registering it on OpenUPM.

+
+

Tip

+

Before publishing, make sure you have tested your package in https://github.com/Netherlands3D/twin -or another +Netherlands3D-based project- installed as a local package.

+

Especially important is:

+
    +
  • verifying that the correct dependencies are set and installed
  • +
  • that the Assembly Definition can be used and
  • +
  • that the Assembly Definition does not depend on Assembly Definitions that are not a dependency of your package, + especially Netherlands3D.Core.Runtime should be avoided
  • +
+

Failure to do so may cause extra work because once a package is published, it is inconvenient to unpublish it or +change publication details such as the name.

+
+

To do so, you can take the following steps:

+
    +
  1. +

    Go to https://openupm.com.

    +
  2. +
  3. +

    Click on the "+" button in the menu bar to add a new package + Screenshot of OpenUPM showing add button

    +
  4. +
  5. +

    Fill out the Github repository name in the intended field and click on "Go". + Screenshot of OpenUPM where repository can be submitted + A form expands where you can verify the package name, ReadMe location and more

    +
  6. +
  7. +

    Enter your github username in the "Discovered by" field; this is used to base a fork of OpenUPM from and add the + package as a pull request.

    +

    +
    +

    Note

    +

    The section "Advanced" is for advanced use when multiple packages are hosted in the same repository; at the moment +this method is not recommended and as such that part of the submission form can be skipped

    +
    +
  8. +
  9. +

    Check the "Promotion" section if the package fits any of these categories, and check these.

    +
  10. +
  11. +

    Click on the "Verify Package" button, all other fields can be left unchanged.

    +
  12. +
+

As soon as the steps above have completed, you are asked to fork the OpenUPM repository and to commit these changes in +a feature branch. At this point you can use the "Create pull request" button to create a pull request to OpenUPM.

+
+

Tip

+

If this is your first time submitting a package to OpenUPM, the maintainer of OpenUPM needs to approve the Pull +Request manually; this is generally done within 24 hours. Any subsequent pull requests will be automatically merged.

+
+

After the pull request is merged, it will take 30 to 60 minutes for OpenUPM to add the package to the registry.

+

FAQ

+
+

Can I depend on the Netherlands3D.Core.Runtime assembly?

+
+

At time of writing, this assembly is within the code of the https://github.com/Amsterdam/Netherlands3D skeleton; meaning +that this dependency only works when the package is used within a project that is based on this skeleton. This can be +a problematic dependency since we are actively working on moving the contents of this skeleton into components.

+

Because of the above, it is not recommended for a published package to depend on the Netherlands3D.Core.Runtime +assembly.

+

If code from this assembly is needed, it is recommended to extract this code into another package that you can depend +on or duplicate it into your own package until such a package can be made.

+

For more information on this, see the question Can I depend on Assemblies that are not in my own package?.

+
+

Can I depend on Assemblies that are not in my own package?

+
+

You sure can! As long as these assemblies are in a package that is published in a Unity Package Registry such as +OpenUPM, and that you have added that package as a dependency to your package.json file.

+

If you add an assembly whose package cannot be included in your package.json -either because it is not a package or +a git-based package- then you will need to add installation instructions in the README. Without these instructions, +any user of the package will have a missing assembly -and thus errors- without knowing how to fix it.

+
+

One of the packages does not show up in the Package Manager after publishing

+
+

Have you checked the minimum unity version in the package.json? If the minimum version is newer than your +installed version, it will not be visible.

+
+

Why OpenUPM and not add packages through a Git URL?

+
+

Good question! When you add packages through a Git URL you lose certain features that packages hosted on a registry do +have. This includes but is not limited to:

+
    +
  1. +

    Unable to update to a new version: when you use a Git tag/release, then you need to uninstall and reinstall the + package when a new becomes available instead of just using the Update button.

    +
  2. +
  3. +

    Git-url based packages cannot be used as dependencies: this means that if a package depends on another, with git + urls you need to manually install the correct dependencies and face possible version conflicts with newer versions.

    +
  4. +
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/projects/index.html b/docs/developers/projects/index.html new file mode 100644 index 0000000..a82db57 --- /dev/null +++ b/docs/developers/projects/index.html @@ -0,0 +1,1834 @@ + + + + + + + + + + + + + + + + + + + + + + + Projects - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + +

Projects

+

Overview

+

Netherlands3D is a dynamic platform designed to allow users to interact with 3D geographical maps of locations across +the Netherlands. This platform enables a wide array of spatial data interactions, from visualizing sensor telemetry +to managing infrastructure data like cables, pipes, and zoning information. At the core of this functionality is the +concept of Projects, which serve as containers for all user interactions and data on the platform.

+

Projects in Netherlands3D encompass everything from the base map to the specific data layers, styles, and configurations +that define the user's current view. Users can save these projects as snapshots of their work, allowing them to revisit, +share, or continue working seamlessly at any time. This structure not only facilitates complex data analysis but also +ensures that all spatial relationships are preserved and accurately represented.

+

Project File Format

+

Before diving into the technical specifics of how projects operate within the system, it's essential to understand +the file format used to store project data. The project file acts as a blueprint, capturing all the layers, styles, +configurations, and settings that define the project.

+
+

Todo

+

Describe the file format

+
+

System Operation Overview

+

Netherlands3D operates on a robust system that initializes with a default project file^1. This default project acts as +the foundation upon which all subsequent user interactions and loaded projects are based.

+

Included Empty Project

+

Upon startup, the system loads an empty project file^1 from the Assets^2 folder. This project serves as the +initial state of the application, providing a base map and predefined configurations that set the scene for the user's +interaction. The empty project is crucial for ensuring that the application is ready to use immediately upon launch, +without requiring any user input.

+

Customizable Default Project

+

A default project is fully configurable per deployment, allowing providers to tailor the initial view and settings to +their specific needs. For instance, a province or municipality might want to highlight a particular region or set +specific visualization parameters to align with their objectives. This flexibility ensures that the application can be +used in various contexts, whether as an interactive tool for end-users or a static viewer for presenting a digital twin +of a specific location.

+
+

Todo

+

Describe how this can be done

+
+

Project Loading and Replacement

+

Once the application is running, users can load their own project files. When a new project file is loaded, it replaces +the current project in its entirety, bringing in all new data layers, styles, and configurations. This process ensures a +smooth transition between different datasets and views, allowing users to focus on their work without worrying about +residual configurations from previous projects.

+
+

Todo

+

Describe how this works in more detail with a flowchart illustrating the overall process

+
+

How Layers Work

+
+

Self-registering layers

+

A variant of the described flow exists where by adding the visualisation of a layer it self-registers the layer's +data onto the current project. This flow will be described in a separate chapter for clarity.

+
+

how layers work

+

The diagram provided illustrates the lifecycle of layers within a project, detailing how they are acquired, +constructed, and visualized. Here's a breakdown:

+

Acquiring Layers

+

Layers in Netherlands3D can be added to a project in several ways:

+
    +
  • +

    Importing via URL or File Upload: The system allows users to import data layers by providing a URL or uploading a file. +The uploaded or referenced file is stored within the current project, and the system determines the layer type based on +the file name or its contents.

    +
  • +
  • +

    Dragging from Object Library: Users can also add layers by dragging objects from a pre-existing object library. The +system determines the type of layer based on the selected object.

    +
  • +
  • Opening a Project: When a project is opened, the system unpacks its contents to a predefined storage path. This process +involves instantiating a ProjectData object and associated LayerData objects through deserialization.
  • +
+

Constructing Layers

+

Once a layer is added, the system begins constructing it:

+
    +
  • The system finds a visualizer identifier for the specific layer type and then instantiates a LayerData object +accordingly.
  • +
  • The LayerData is then registered with the current project, linking it to the broader project context.
  • +
+

Layers consist of two primary components:

+
    +
  • Layer Data: This is a lightweight, serializable data object containing information about the layer, such as its source +data location (e.g., file paths, URIs).
  • +
  • Layer Visualizer: This component handles the actual visualization work, such as spawning tiles and rendering geometry.
  • +
+

Visualizing Layers

+

The final stage is visualizing the layers within the application:

+
    +
  • The system grabs the appropriate visualizer prefab based on the identifier in LayerData.
  • +
  • The visualizer loads the data from LayerData, and the application schedules visualization activities.
  • +
  • If necessary, the source data is parsed or interpreted before visualization, ensuring that all data layers are +accurately represented.
  • +
  • Finally, the visualization scheduler renders the data, allowing users to interact with the newly loaded layers +seamlessly.
  • +
+

This structured approach to handling layers ensures that all data is properly managed, visualized, and integrated within +the project framework, offering a powerful tool for spatial analysis and geographic data management in the Netherlands3D +platform.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/quickstart/index.html b/docs/developers/quickstart/index.html new file mode 100644 index 0000000..26a5a03 --- /dev/null +++ b/docs/developers/quickstart/index.html @@ -0,0 +1,1699 @@ + + + + + + + + + + + + + + + + + + + + + + + Quick Start Guide - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + +

Quick Start Guide

+
+

Info

+

This quickstart is aimed at developers who want to build on top of this platform, if all +you want is to deploy it pre-configured on your platform, please read the guide on +deploying using Docker.

+
+

Prerequisites

+

Netherlands3D is developed in Unity, thus you'll need few prerequisites to start with the platform:

+
    +
  1. Install Unity, the recommended version is Unity 2023 LTS, including the WebGL build options. Please check the Unity + Download Page -or Unity Hub- and the official Installation Guide for assistance.
  2. +
  3. Basic understanding of Unity and C# programming language.
  4. +
+

Getting Started

+
    +
  1. +

    Firstly, clone the Netherlands3D/twin repository from GitHub:

    +
    git clone https://github.com/Netherlands3D/twin.git
    +
    +
  2. +
  3. +

    Once cloned, open the project in Unity. After the project has loaded, you can start tweaking and modifying the + application as per your needs.

    +
  4. +
+

Do make sure that your build settings are set to build the project as a WebGL application, as several functionalities +are tailored to that platform.

+

Recommendations

+

Copy the Main scene

+

It's recommended to create a copy of the 'Main' scene before making any modifications. This ensures that you're not +directly altering the original scene which, as the project is in continuous development, can lead to conflicts.

+

To copy a scene in Unity:

+
    +
  • Select the 'Main' scene in the Project window.
  • +
  • Press Ctrl+D or right-click and select 'Duplicate' from the context menu.
  • +
+

Inspect the Configuration and ConfigurationStarter Scriptable Objects

+

Check the 'Configuration' and 'ConfigurationStarter' scriptable objects for initial settings of the project. This +includes the starting position of your digital twin. These configurable parameters allow you to customize initial +settings as per your requirements.

+

Next Steps

+

With just these steps, you can immediately build and release your own customized Digital Twin; next up you can read up +on what and how you can configure the platform without making any code changes.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/ui/blocks/property-anatomy.png b/docs/developers/ui/blocks/property-anatomy.png new file mode 100644 index 0000000..b1e61f4 Binary files /dev/null and b/docs/developers/ui/blocks/property-anatomy.png differ diff --git a/docs/developers/ui/blocks/property-example.png b/docs/developers/ui/blocks/property-example.png new file mode 100644 index 0000000..2a02c1d Binary files /dev/null and b/docs/developers/ui/blocks/property-example.png differ diff --git a/docs/developers/ui/blocks/property-panel-and-sections/index.html b/docs/developers/ui/blocks/property-panel-and-sections/index.html new file mode 100644 index 0000000..d8f8ba7 --- /dev/null +++ b/docs/developers/ui/blocks/property-panel-and-sections/index.html @@ -0,0 +1,1946 @@ + + + + + + + + + + + + + + + + + + + + + + + Properties - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + +

Properties

+

Design

+

Usage

+

The "Properties" block in Netherlands3D serves as a critical interface element, allowing users to interact with and +modify the attributes of various layers within the application. This documentation provides insights into the design +considerations and implementation strategies for this block, ensuring a seamless integration within the Netherlands3D +platform.

+

This block enables users to access and adjust the transformation properties (position, rotation, scale) of selected 3D +objects or layers. Additionally, it provides access to specific attributes for certain objects, such as Windmills, which +may include properties like axis height or rotor diameter.

+
+ property-example.png +
A screenshot of a windmill with an open properties panel
+
+

Anatomy

+
+ property-anatomy.png +
Anatomical breakdown of the properties block
+
+

The Properties panel is constructed using a Card component, organized into several key areas for intuitive +user interaction:

+
    +
  1. Card: The overarching container for the Properties panel, designed for clarity and + cohesion.
  2. +
  3. Card Header: Displays the Properties icon and the term "Eigenschappen," indicating the + panel's purpose.
  4. +
  5. Section: Each layer or object type, such as Windmills, has its dedicated section within the card.
  6. +
  7. Section Header: Identifies the specific layer or object type, e.g., "Windmolen" for Windmills, guiding users to the + relevant properties.
  8. +
  9. Section Content: Contains form elements for adjusting the specific properties of the selected layer or object. This + area is designed for direct interaction, allowing users to modify attributes like axis height or rotor diameter.
  10. +
+

Design Principles

+
    +
  • Clarity: The use of a card-based layout ensures that the Properties panel is easily navigable and understandable, + with distinct sections for different layers or objects.
  • +
  • Consistency: Maintaining a uniform structure across different sections ensures that users have a cohesive + experience, regardless of the variety of objects or layers they interact with.
  • +
  • Immediate Feedback: Interactions within the Properties panel are designed to provide immediate visual feedback in + the Netherlands3D environment, emphasizing the panel's role in direct manipulation of objects and layers.
  • +
+

Interaction

+

Users interact with the Properties panel by selecting an object or layer within the Netherlands3D platform, and clicking +on the properties button with that layer.

+

Upon opening, the panel updates to display the relevant sections for the chosen item. Adjustments made within the form +elements of a section are immediately applied, allowing users to see the effects of their changes in real-time. This +design supports a tactile and intuitive experience, encouraging exploration and modification within the digital twin +environment.

+

Implementation

+

Overview

+

The Properties panel's functionality in the Netherlands3D platform is achieved through a combination of Unity prefabs +and scripting. This implementation ensures dynamic interaction within the UI, allowing for the seamless addition of +properties sections based on the selected layer or object.

+

Prefab Structure

+
    +
  • +

    Main Properties Prefab: The core of the Properties panel, this prefab is a part of the default canvas and acts as + the container for all dynamically added sections.

    +
  • +
  • +

    Section Prefabs: Individual prefabs for each type of section (e.g., Windmill properties) that can be instantiated + within the main Properties panel.

    +
  • +
+

Scripting and Interfaces

+

IPropertySection Interface

+

An essential element in the dynamic nature of the Properties panel is the IPropertySection interface. This interface +allows for the detection and instantiation of properties sections based on the presence of specific MonoBehaviours +within a layer's prefab.

+
    +
  • +

    Detection and Instantiation: A script running in the layer system detects MonoBehaviours that implement + the IPropertySection interface on a layer's prefab being added to the scene. Upon detection, the AddToProperties + method of the interface is called.

    +
  • +
  • +

    Factory Behavior: Implementations of the IPropertySection can act as factories, instantiating the appropriate + section prefab and adding it to the Properties panel as a "slot". The AddToProperties method is being provided with + the properties content area to which the section can be added.

    +
  • +
+
AddToProperties Method
+

The AddToProperties method is where the logic for instantiating the section prefab and integrating it into the +Properties panel is defined. This method allows for flexibility in the UI, permitting custom designs per section while +adhering to the overall design guidelines.

+
+

Example

+

Example: Windmill Property Section

+

The Windmolen prefab, used for the Windmill implementation in the Object Library, includes a MonoBehaviour +named WindmillPropertySectionInstantiator that demonstrates this system in action.

+
    +
  • +

    Functionality: The WindmillPropertySectionInstantiator responds to the addition of a Windmill object and + instantiates a dedicated section within the Properties panel. This section is specifically designed to bind and + display the properties of the windmill, such as axis height or rotor diameter.

    +
  • +
  • +

    Binding: The script binds the windmill's data to the instantiated section, ensuring that any adjustments made + within the Properties panel are immediately reflected in the windmill's properties within the scene.

    +
  • +
+
+

Implementation Steps

+

Given an existing layer's prefab:

+

Implement the IPropertySection interface: For each layer requiring a properties section, create a MonoBehaviour +that implements IPropertySection and defines how its section prefab should be instantiated and added to the +Properties panel.

+

Design Adherence

+

While implementing sections, it's crucial to adhere to the established design rules to maintain consistency across the +Properties panel. However, the flexibility of this system allows for the introduction of unique UI elements where +necessary to better represent specific properties.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/ui/components/accordion-anatomy.png b/docs/developers/ui/components/accordion-anatomy.png new file mode 100644 index 0000000..caaf2f3 Binary files /dev/null and b/docs/developers/ui/components/accordion-anatomy.png differ diff --git a/docs/developers/ui/components/accordion/index.html b/docs/developers/ui/components/accordion/index.html new file mode 100644 index 0000000..c20e159 --- /dev/null +++ b/docs/developers/ui/components/accordion/index.html @@ -0,0 +1,1835 @@ + + + + + + + + + + + + + + + + + + + + + + + Accordion - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Accordion

+

Design

+

Usage

+

The Accordion component in the Netherlands3D platform is a versatile UI element designed to efficiently manage and +display hierarchical content in a compact form. Featuring a title, an optional label, and a toggle mechanism indicated +by a chevron, the Accordion allows for an organized presentation of content sections, each with its dedicated header to +signify its integration within the Accordion. This component is ideal for:

+
    +
  • +

    Settings Menus: Grouping related settings or options to streamline user navigation, such as layer property sections.

    +
  • +
  • +

    Grouping Content: Grouping related content to focus on related information, such as quality indicator dossiers.

    +
  • +
  • +

    Interactive FAQs: Presenting frequently asked questions in a concise, expandable format.

    +
  • +
+

Modes of Operation

+
    +
  1. +

    Fixed Mode: This mode locks the Accordion in an always-open state, without the option to collapse or expand. The + absence of a chevron signifies its static nature.

    +
  2. +
  3. +

    Multiple Open Mode: Allows for multiple Accordions within a group to be opened or closed independently. The chevron + points up when opened, indicating a "roll-up" action, and down when closed, suggesting a "roll-down" action.

    +
  4. +
  5. +

    Single Open Mode: In a group of Accordions, only one can be open at a time. Opening another Accordion automatically + closes the previously open one. This mode features a chevron pointing down when closed; and when open, the chevron is + hidden, supplemented by an additional outline to highlight the Accordion is considered to be an active -or selected- + Accordion.

    +
  6. +
+

Anatomy

+
+ accordion-anatomy.png +
Anatomical breakdown of the Accordion component
+
+
    +
  1. +

    Title: A text representative of the Accordion's content.

    +
  2. +
  3. +

    Icon (Optional): Provides additional context or classification.

    +
  4. +
  5. +

    Expand/Contract Toggle (Optional): A chevron-based toggle mechanism indicating the Accordion's state and + possible actions.

    +
  6. +
  7. +

    Content Section: The expandable area containing detailed content or components.

    +
  8. +
  9. +

    Content Header: A header within the content section styled specifically to emphasize its connection to the + Accordion.

    +
  10. +
+

Design Principles

+

Title and Icon

+

The title provides a succinct summary of the content contained within the Accordion, while the optional icon offers +additional context or categorization.

+

Both elements are designed to be immediately visible to enhance usability and guide the user's navigation through the +content sections.

+

Expand/Contract Toggle

+

The Expand/Contract Toggle includes a chevron symbol that visually communicates the Accordion's state and possible +actions to the user. The direction of the chevron indicates the effect of the interaction (down for roll-down, up for +roll-up).

+

The toggle may be absent when there is no action available, as can be the case for the fixed mode of operation or when +no roll-up is provided because one element always needs to be open.

+

Content Section

+

The content section beneath each Accordion title is designed for expandability, housing detailed information or +additional UI components.

+

Optionally, a specific header within the content section further emphasizes its association with the Accordion.

+

Best Practices

+
    +
  • +

    Clarity: Ensure the title and label (if used) are clear and informative, guiding the user's understanding of the + content within.

    +
  • +
  • +

    Intuitive Interaction: Design the Expand Toggle for easy recognition and interaction, with chevron icons that + intuitively indicate the possible actions.

    +
  • +
  • +

    Consistency: Maintain visual and functional consistency across all Accordion components, especially within groups + operating in multiple or single open modes.

    +
  • +
+

Implementation

+
+

Info

+

As of writing, there is no component in Unity available to provide the Accordion component. This part of the +documentation should be updated as soon as one is available.

+
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/ui/components/cards/index.html b/docs/developers/ui/components/cards/index.html new file mode 100644 index 0000000..2540cce --- /dev/null +++ b/docs/developers/ui/components/cards/index.html @@ -0,0 +1,1581 @@ + + + + + + + + + + + + + + + + + + + + + + + Cards - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Cards

+

Usage

+

Cards are surfaces that display content and actions on a single topic.

+

They should be easy to scan for relevant and actionable information. Elements, like text and images, should be placed on +them in a way that clearly indicates hierarchy.

+

Anatomy

+
+

Info

+

The contents of this chapter need to be written

+
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/ui/components/double-slider-anatomy.png b/docs/developers/ui/components/double-slider-anatomy.png new file mode 100644 index 0000000..4371eb7 Binary files /dev/null and b/docs/developers/ui/components/double-slider-anatomy.png differ diff --git a/docs/developers/ui/components/double-sliders/index.html b/docs/developers/ui/components/double-sliders/index.html new file mode 100644 index 0000000..5f0b861 --- /dev/null +++ b/docs/developers/ui/components/double-sliders/index.html @@ -0,0 +1,1823 @@ + + + + + + + + + + + + + + + + + + + + + + + Double Slider - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Double Slider

+

Design

+

Usage

+

Similar to the slider component, the double slider component in the Netherlands3D platform provides +a graphical interface element enabling users to select a range between two values from a predefined scale. +Accompanied by two Text Fields, this component allows for both graphical adjustment +and direct numerical input, ensuring a versatile and user-friendly experience. The main difference between +the double slider and the regular slider is that the double slider has two handles and input fields, +defining the minimum and maximum value of the range.

+

Immediate effects

+

Changes made with double sliders are immediate, allowing the user to make slider adjustments while determining a selection. +Sliders shouldn’t be used to adjust settings with any delay in providing user feedback.

+

Current state

+

Sliders reflect the current state of the settings they control.

+

Anatomy

+
+ double-slider-anatomy.png +
Anatomical breakdown of the double slider component
+
+
    +
  1. Slider Track: The horizontal line along which the double slider thumb moves, representing the range of possible values.
  2. +
  3. Minimum Slider Thumb: The circular control that users drag to select the minimum value on the slider track.
  4. +
  5. Minimum Text Field (Optional): Adjacent to the double slider, this field displays the currently selected minimum value and allows for manual + entry. It can be configured as readonly or omitted based on specific requirements.
  6. +
  7. Maximum Slider Thumb: The circular control that users drag to select the maximum value on the slider track.
  8. +
  9. Maximum Text Field (Optional): Adjacent to the double slider, this field displays the currently selected maximum value and allows for manual + entry. It can be configured as readonly or omitted based on specific requirements.
  10. +
+

Implementation

+

This chapter guides you through adding the double slider prefab to your scene, configuring its behavior, and responding to user +input.

+

Adding the Slider to Your Scene

+
    +
  1. +

    Prefab Placement: Locate the double slider prefab in the project's asset directory. Drag and drop the prefab into your + scene or hierarchy where you need the slider component to appear.

    +
  2. +
  3. +

    Initial Setup: By default, the double slider comes with linked Text Fields for numerical input. Together, they provide + a cohesive component for value selection.

    +
  4. +
+

Configuring the Slider

+

Listening to Value Changes

+

Use the Unity Editor to attach a listener function to the slider's onMinValueChanged and onMaxValueChanged events. These functions will be called +whenever the slider's minimum or maximum value changes, respectively, whether through direct manipulation of the double slider or numerical entry in the Text +Fields.

+

Customizing the Text Field

+
    +
  • +

    Readonly Option: The DoubleSlider MonoBehaviour includes a readonly boolean. When set to true, the Text + Fields becomes readonly, allowing users to see the value but preventing manual entry. This is useful in situations + where you want to restrict input to slider manipulation only.

    +
  • +
  • +

    Omitting the Text Field: If your use case does not require numerical input, you can deactivate the Text Fields + entirely. Simply set the Text Field GameObjects to inactive, and the double slider will automatically expand to fill the + space, maintaining the UI's visual continuity.

    +
  • +
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/ui/components/slider-anatomy.png b/docs/developers/ui/components/slider-anatomy.png new file mode 100644 index 0000000..d6d2853 Binary files /dev/null and b/docs/developers/ui/components/slider-anatomy.png differ diff --git a/docs/developers/ui/components/sliders/index.html b/docs/developers/ui/components/sliders/index.html new file mode 100644 index 0000000..c94d054 --- /dev/null +++ b/docs/developers/ui/components/sliders/index.html @@ -0,0 +1,1817 @@ + + + + + + + + + + + + + + + + + + + + + + + Slider - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Slider

+

Design

+

Usage

+

The slider component in the Netherlands3D platform provides a graphical interface element enabling users to select a +value or range from a predefined scale. Accompanied by a Text Field, this component allows for both graphical adjustment +and direct numerical input, ensuring a versatile and user-friendly experience.

+

Immediate effects

+

Changes made with sliders are immediate, allowing the user to make slider adjustments while determining a selection. +Sliders shouldn’t be used to adjust settings with any delay in providing user feedback.

+

Current state

+

Sliders reflect the current state of the settings they control.

+

Anatomy

+
+ slider-anatomy.png +
Anatomical breakdown of the slider component
+
+
    +
  1. Slider Track: The horizontal line along which the slider thumb moves, representing the range of possible values.
  2. +
  3. Slider Thumb: The circular control that users drag to select a value on the slider track.
  4. +
  5. Text Field (Optional): Adjacent to the slider, this field displays the currently selected value and allows for manual + entry. It can be configured as readonly or omitted based on specific requirements.
  6. +
+

Implementation

+

This chapter guides you through adding the slider prefab to your scene, configuring its behavior, and responding to user +input.

+

Adding the Slider to Your Scene

+
    +
  1. +

    Prefab Placement: Locate the slider prefab in the project's asset directory. Drag and drop the prefab into your + scene or hierarchy where you need the slider component to appear.

    +
  2. +
  3. +

    Initial Setup: By default, the slider comes with a linked Text Field for numerical input. Together, they provide + a cohesive component for value selection.

    +
  4. +
+

Configuring the Slider

+

Listening to Value Changes

+

Use the Unity Editor to attach a listener function to the slider's onValueChanged event. This function will be called +whenever the slider's value changes, whether through direct manipulation of the slider or numerical entry in the Text +Field.

+

Customizing the Text Field

+
    +
  • +

    Readonly Option: The PropertySlider MonoBehaviour includes a readonly boolean. When set to true, the Text + Field becomes readonly, allowing users to see the value but preventing manual entry. This is useful in situations + where you want to restrict input to slider manipulation only.

    +
  • +
  • +

    Omitting the Text Field: If your use case does not require numerical input, you can deactivate the Text Field + entirely. Simply set the Text Field GameObject to inactive, and the slider will automatically expand to fill the + space, maintaining the UI's visual continuity.

    +
  • +
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/ui/components/text-fields/index.html b/docs/developers/ui/components/text-fields/index.html new file mode 100644 index 0000000..845da45 --- /dev/null +++ b/docs/developers/ui/components/text-fields/index.html @@ -0,0 +1,1758 @@ + + + + + + + + + + + + + + + + + + + + + + + Text Field - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Text Field

+

Design

+

Usage

+

"Text Fields" in the Netherlands3D platform allow users to input or modify text information. They can be used singularly +or in combination with other components like a Slider or Form Label in the +Properties block. They're an essential part of Netherlands3D's user +interface, providing users with interactive fields to enter or manipulate various properties.

+

Parts

+

The Text Field component comprises two main parts:

+
    +
  1. +

    Form Label: The descriptive text associated with the Text Field, explaining what value is being manipulated. This + is a reusable part and can be shared with other components like the Slider.

    +
  2. +
  3. +

    Form Field: It serves as the component for user text input or changing text information. It is styled to account + for different states like Idle, Focus, Pressing, and Read-Only.

    +
  4. +
+

Anatomy

+
+

Note

+

Add an image to showcase the anatomy of a form field and elaborate some more on this in the text

+
+
    +
  1. +

    Form Label: Positioned above or adjacent to the Form Field, providing a textual description of the information + required or displayed.

    +
  2. +
  3. +

    Form Field: The interactive text input area where users can enter or modify data. It transitions between various + states (Idle, Focus, Pressed, Read-only) to visually communicate its current mode of interaction.

    +
  4. +
+

Design Principles

+
    +
  • +

    Clarity: Labels are clear and informative, ensuring users understand what information is required.

    +
  • +
  • +

    Feedback: Built-in states (Idle, Focused, Pressed, and Read-Only) provide immediate visual feedback to users, + aiding an interactive experience.

    +
  • +
+

Implementation

+

The Text Field is modeled using Unity prefabs and scripting, offering dynamic interaction within the user interface, +allowing for adaptive properties adjustment based on user input.

+

Adding a Text Field to Your Scene

+
    +
  • Prefab Placement: Locate the Text Field prefab in the project's asset directory. Drag and drop the prefab into your + scene where you need the Text Field component to appear.
  • +
+

Configuring the Text Field

+

You can utilize the Unity Editor to add listener functions that get triggered on text value change events, enabling you +to manipulate and use the user-input text as per application needs.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/ui/introduction/index.html b/docs/developers/ui/introduction/index.html new file mode 100644 index 0000000..79afc4e --- /dev/null +++ b/docs/developers/ui/introduction/index.html @@ -0,0 +1,1675 @@ + + + + + + + + + + + + + + + + + + + + + + + Introduction - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Introduction

+

Welcome to the foundational section of our technical documentation, where we introduce the user interface (UI) design +system of Netherlands3D. This documentation is crafted to guide developers, designers, and contributors through the +design architecture that shapes the user experience within our digital twin environment.

+

Purpose of the Design System

+

The design system serves as a comprehensive framework that harmonizes UI design and development, ensuring consistency, +scalability, and efficiency across the application. It is the blueprint that orchestrates the visual and interactive +elements, making the digital twin not only a technological marvel but also an intuitive and engaging experience for +users.

+

Overview of the UI Design System

+

At the heart of our UI design system are two fundamental concepts: Blocks and Components. These elements are the +building blocks of our application's interface, structured to offer clarity and flexibility in design and +implementation. Inspired by the BEM (Block, Element, Modifier) methodology, our system disambiguates between blocks and +components to streamline the development process and enhance the UI's modularity and reusability.

+

Blocks

+

Blocks are the larger, more general sections of the UI that function as the containers for components. They define the +structural layout and set the stage for the detailed elements within. Blocks can be as broad as the entire application +window or as specific as a user interaction panel.

+

Components

+

Components, on the other hand, are the more granular, reusable elements that reside within blocks. They are the nuts and +bolts of the UI, encompassing buttons, sliders, text fields, and other interactive elements. Components are designed +with flexibility in mind, allowing them to be adapted and reused across different blocks.

+

Objectives of This Documentation

+

The subsequent chapters of this documentation are dedicated to diving deeper into the specifics of blocks and +components, outlining their characteristics, how they are designed, and how they interact within the UI framework of our +digital twin platform. Our goals are to:

+
    +
  • Clarify the distinction between blocks and components, providing a clear understanding of their roles within the + UI design system.
  • +
  • Guide the design and development process, offering insights into best practices for creating and integrating + blocks and components.
  • +
  • Facilitate scalability and customization, enabling developers and designers to extend the UI efficiently and + creatively.
  • +
+

As we proceed, this documentation will serve as a reference for enhancing and expanding the user interface of +Netherlands3D, ensuring that the developers and designers within the ecosystem are aligned.

+

In the next chapter, we will explore the concept of blocks in detail, examining their structure, functionality, and how +they contribute to the overall user experience of the application.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/ui/keeping-our-documentation-current/index.html b/docs/developers/ui/keeping-our-documentation-current/index.html new file mode 100644 index 0000000..fba4dea --- /dev/null +++ b/docs/developers/ui/keeping-our-documentation-current/index.html @@ -0,0 +1,1586 @@ + + + + + + + + + + + + + + + + + + + + + + + Keeping Our Documentation Current - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Keeping Our Documentation Current

+

Important Note: While we strive to maintain this documentation as the definitive guide to our UI design system, the +realm of digital development is one of constant evolution. To accommodate this, we ensure that the most up-to-date +design resources and UI guidelines are housed in an internal Figma board. This board is regularly updated to reflect the +latest design decisions, component updates, and UI patterns adopted by our team.

+

Accessing the Latest Information

+

For the most current design specifications and UI elements, we encourage team members to refer to our internal Figma +board. This resource is designed to be a living document, offering real-time updates and insights into our UI design +strategy.

+

Requesting Documentation Updates

+

We understand that despite our best efforts, there may be instances where this documentation does not fully capture the +latest UI developments or design nuances. In such cases, we welcome requests for documentation updates.

+

By fostering a culture of continuous improvement and open communication, we aim to ensure that our UI design system +supports the dynamic needs of our project and team. Whether you're a developer, a designer, or a project stakeholder, +your contributions and feedback are invaluable to the ongoing refinement of Netherlands3D.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/ui/understanding-blocks/index.html b/docs/developers/ui/understanding-blocks/index.html new file mode 100644 index 0000000..8557078 --- /dev/null +++ b/docs/developers/ui/understanding-blocks/index.html @@ -0,0 +1,1715 @@ + + + + + + + + + + + + + + + + + + + + + + + Understanding Blocks - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + +

Understanding Blocks

+

In the Netherlands3D platform, our design system is structured for clear organization and effective scaling. Blocks form +the fundamental part of this structure, acting as the primary containers for the interface's elements. This chapter +focuses on blocks, explaining their purpose, features, and their role in creating a unified user experience in +Netherlands3D.

+

What Are Blocks?

+

Blocks are the large-scale elements of our UI design system. They serve as the main sections that organize and contain +various components. Think of blocks as the framework of a building, providing structure and defining the layout within +our digital environment. In Netherlands3D, blocks have specific functions, such as guiding navigation, displaying +information, facilitating user interactions, or showcasing data visually.

+

Features of Blocks

+
    +
  • +

    Modular: Blocks are designed as self-contained units that can be mixed and matched in different layouts, offering + flexibility and reuse across the platform.

    +
  • +
  • +

    Scalable: They are built to support the platform's growth, making it easy to add new features or adapt to changing + user needs without disrupting the existing design.

    +
  • +
  • +

    Consistent: Despite their variety, blocks maintain a unified design language and user experience across + Netherlands3D.

    +
  • +
+

Designing Blocks

+

Creating blocks for Netherlands3D requires attention to:

+
    +
  • +

    User Needs: Design each block with the user's requirements in mind, ensuring it fulfills its intended purpose + effectively.

    +
  • +
  • +

    Adaptability: Make blocks adaptable for different types of content and scenarios within the platform.

    +
  • +
+

Managing Blocks

+

The evolution of Netherlands3D involves regular updates to blocks, including additions, removals, or modifications. This +process entails:

+
    +
  1. +

    Planning: Identifying the need based on user feedback or new functionalities.

    +
  2. +
  3. +

    Design and Development: Crafting the block to fit Netherlands3D's design norms and technical specifications.

    +
  4. +
  5. +

    Testing: Ensuring compatibility and performance across devices.

    +
  6. +
  7. +

    Implementation: Incorporating the block into Netherlands3D and monitoring its effectiveness.

    +
  8. +
+

Distinction Between Blocks and Components

+

Blocks and components are fundamental elements of the Netherlands3D UI, but they serve distinct purposes.

+

Blocks are the overarching sections that structure and organize the user interface, similar to the chapters in a +book. They are designed for flexibility, accommodating a variety of components to fulfill different functionalities +within the platform. Blocks ensure a consistent layout and design language across Netherlands3D, providing a cohesive +user experience.

+

Components, on the other hand, are the detailed, interactive or static elements such as buttons, text fields, and +sliders that reside within blocks. These elements are highly customizable and reusable, designed to perform specific +functions or convey particular information. Components depend on blocks for context and placement, but their modularity +allows them to be used in multiple scenarios across the platform.

+

The primary distinction lies in their scale and function: blocks form the UI's structural foundation, while components +populate these structures with content and interactivity. Understanding this difference is crucial for efficient design +and development within Netherlands3D, ensuring a robust and user-friendly interface.

+

Conclusion

+

Blocks are foundational to the Netherlands3D UI design system, offering the structural basis for our digital twin +platform. By leveraging blocks effectively, we can ensure a robust, user-friendly interface that reflects the innovative +spirit of digital twin technology.

+

The following chapter will detail the components within these blocks, including their design and role in enhancing the +interactive experience of Netherlands3D.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/developers/ui/understanding-components/index.html b/docs/developers/ui/understanding-components/index.html new file mode 100644 index 0000000..feedb02 --- /dev/null +++ b/docs/developers/ui/understanding-components/index.html @@ -0,0 +1,1679 @@ + + + + + + + + + + + + + + + + + + + + + + + Understanding Components - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Understanding Components

+

In this chapter, we delve deeper into the essence of components—the dynamic and interactive elements that enable users +to engage with the Netherlands3D platform effectively. Components are the individual pieces of the user interface, such +as buttons, sliders, dropdown menus, and data visualization charts, which users interact with directly.

+

What Are Components?

+

Components are the versatile and interactive building blocks housed within the UI's structural blocks. They range from +input fields (where users can enter data), buttons (like 'Submit' or 'Cancel'), sliders (for adjusting +settings or zoom levels), to more complex elements such as the minimap (allowing users to explore).

+

Features of Components

+
    +
  • +

    Reusability: Designed for reuse, components like toggle switches for on/off settings ensure consistency and + streamline development across Netherlands3D.

    +
  • +
  • +

    Customization: Components can be tailored for different informational purposes to adhere to the specific aesthetic + of the platform.

    +
  • +
  • +

    Interactivity: Interactive components, such as accordions, for customization features or detailed content + display, to engage users directly, offering a dynamic experience.

    +
  • +
+

Designing Components

+

The design of components focuses on the user's needs, aiming for clarity, efficiency, and aesthetic harmony:

+
    +
  • +

    User-Centric Design: Components are crafted to be intuitive and accessible for easy + navigation or with validation to guide user input.

    +
  • +
  • +

    Aesthetic Consistency: Despite their diversity, components maintain the Netherlands3D's cohesive design language,

    +
  • +
  • reinforcing brand identity.
  • +
+

Managing Components

+

The lifecycle of components in Netherlands3D involves continuous evaluation and refinement:

+
    +
  1. +

    Identification of Needs: New functionalities might require the development of novel components interaction with + the 3D environment or its data.

    +
  2. +
  3. +

    Rigorous Testing: Components undergo extensive testing.

    +
  4. +
  5. +

    Iterative Improvement: Feedback might lead to the evolution of components, such as enhancing them + for better data visualization or for improved usability.

    +
  6. +
+

Conclusion

+

Components are vital for the interactive and functional richness of the Netherlands3D platform. Each component plays a +crucial role in engaging users and facilitating a smooth experience. Through careful design and management, components +ensure Netherlands3D remains an intuitive, efficient, and visually appealing platform.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/imgs/DataKoppelen/image1.png b/docs/imgs/DataKoppelen/image1.png new file mode 100644 index 0000000..bf741cc Binary files /dev/null and b/docs/imgs/DataKoppelen/image1.png differ diff --git a/docs/imgs/DataKoppelen/image2.png b/docs/imgs/DataKoppelen/image2.png new file mode 100644 index 0000000..270bc69 Binary files /dev/null and b/docs/imgs/DataKoppelen/image2.png differ diff --git a/docs/imgs/DataKoppelen/image3.png b/docs/imgs/DataKoppelen/image3.png new file mode 100644 index 0000000..8a53f26 Binary files /dev/null and b/docs/imgs/DataKoppelen/image3.png differ diff --git a/docs/imgs/DataKoppelen/image4.png b/docs/imgs/DataKoppelen/image4.png new file mode 100644 index 0000000..11f44ef Binary files /dev/null and b/docs/imgs/DataKoppelen/image4.png differ diff --git a/docs/imgs/bag/image1.png b/docs/imgs/bag/image1.png new file mode 100644 index 0000000..34ec5f6 Binary files /dev/null and b/docs/imgs/bag/image1.png differ diff --git a/docs/imgs/bag/image2.png b/docs/imgs/bag/image2.png new file mode 100644 index 0000000..0985458 Binary files /dev/null and b/docs/imgs/bag/image2.png differ diff --git a/docs/imgs/bag/image3.png b/docs/imgs/bag/image3.png new file mode 100644 index 0000000..dd50881 Binary files /dev/null and b/docs/imgs/bag/image3.png differ diff --git a/docs/imgs/bag/image4.png b/docs/imgs/bag/image4.png new file mode 100644 index 0000000..81b4359 Binary files /dev/null and b/docs/imgs/bag/image4.png differ diff --git a/docs/imgs/basis/image1.png b/docs/imgs/basis/image1.png new file mode 100644 index 0000000..8f56790 Binary files /dev/null and b/docs/imgs/basis/image1.png differ diff --git a/docs/imgs/basis/image10.png b/docs/imgs/basis/image10.png new file mode 100644 index 0000000..2d67120 Binary files /dev/null and b/docs/imgs/basis/image10.png differ diff --git a/docs/imgs/basis/image11.png b/docs/imgs/basis/image11.png new file mode 100644 index 0000000..2c1bde3 Binary files /dev/null and b/docs/imgs/basis/image11.png differ diff --git a/docs/imgs/basis/image12.png b/docs/imgs/basis/image12.png new file mode 100644 index 0000000..caa2f25 Binary files /dev/null and b/docs/imgs/basis/image12.png differ diff --git a/docs/imgs/basis/image13.png b/docs/imgs/basis/image13.png new file mode 100644 index 0000000..0a41083 Binary files /dev/null and b/docs/imgs/basis/image13.png differ diff --git a/docs/imgs/basis/image14.png b/docs/imgs/basis/image14.png new file mode 100644 index 0000000..39b04d7 Binary files /dev/null and b/docs/imgs/basis/image14.png differ diff --git a/docs/imgs/basis/image15.png b/docs/imgs/basis/image15.png new file mode 100644 index 0000000..e756246 Binary files /dev/null and b/docs/imgs/basis/image15.png differ diff --git a/docs/imgs/basis/image16.png b/docs/imgs/basis/image16.png new file mode 100644 index 0000000..c95a565 Binary files /dev/null and b/docs/imgs/basis/image16.png differ diff --git a/docs/imgs/basis/image17.png b/docs/imgs/basis/image17.png new file mode 100644 index 0000000..dff0fe4 Binary files /dev/null and b/docs/imgs/basis/image17.png differ diff --git a/docs/imgs/basis/image18.png b/docs/imgs/basis/image18.png new file mode 100644 index 0000000..0597439 Binary files /dev/null and b/docs/imgs/basis/image18.png differ diff --git a/docs/imgs/basis/image19.png b/docs/imgs/basis/image19.png new file mode 100644 index 0000000..29c9c76 Binary files /dev/null and b/docs/imgs/basis/image19.png differ diff --git a/docs/imgs/basis/image2.png b/docs/imgs/basis/image2.png new file mode 100644 index 0000000..0bdf598 Binary files /dev/null and b/docs/imgs/basis/image2.png differ diff --git a/docs/imgs/basis/image20.png b/docs/imgs/basis/image20.png new file mode 100644 index 0000000..142658b Binary files /dev/null and b/docs/imgs/basis/image20.png differ diff --git a/docs/imgs/basis/image21.png b/docs/imgs/basis/image21.png new file mode 100644 index 0000000..6f89aba Binary files /dev/null and b/docs/imgs/basis/image21.png differ diff --git a/docs/imgs/basis/image3.png b/docs/imgs/basis/image3.png new file mode 100644 index 0000000..f5194cd Binary files /dev/null and b/docs/imgs/basis/image3.png differ diff --git a/docs/imgs/basis/image4.png b/docs/imgs/basis/image4.png new file mode 100644 index 0000000..d75da6c Binary files /dev/null and b/docs/imgs/basis/image4.png differ diff --git a/docs/imgs/basis/image5.png b/docs/imgs/basis/image5.png new file mode 100644 index 0000000..b151860 Binary files /dev/null and b/docs/imgs/basis/image5.png differ diff --git a/docs/imgs/basis/image6.png b/docs/imgs/basis/image6.png new file mode 100644 index 0000000..361baaf Binary files /dev/null and b/docs/imgs/basis/image6.png differ diff --git a/docs/imgs/basis/image7.png b/docs/imgs/basis/image7.png new file mode 100644 index 0000000..5dbad14 Binary files /dev/null and b/docs/imgs/basis/image7.png differ diff --git a/docs/imgs/basis/image8.png b/docs/imgs/basis/image8.png new file mode 100644 index 0000000..ce7b17b Binary files /dev/null and b/docs/imgs/basis/image8.png differ diff --git a/docs/imgs/basis/image9.png b/docs/imgs/basis/image9.png new file mode 100644 index 0000000..0dd85cc Binary files /dev/null and b/docs/imgs/basis/image9.png differ diff --git a/docs/imgs/beginnen/create_folder.png b/docs/imgs/beginnen/create_folder.png new file mode 100644 index 0000000..9c143a6 Binary files /dev/null and b/docs/imgs/beginnen/create_folder.png differ diff --git a/docs/imgs/beginnen/editor_herstart.png b/docs/imgs/beginnen/editor_herstart.png new file mode 100644 index 0000000..bcab9dc Binary files /dev/null and b/docs/imgs/beginnen/editor_herstart.png differ diff --git a/docs/imgs/beginnen/image0.png b/docs/imgs/beginnen/image0.png new file mode 100644 index 0000000..8e0202f Binary files /dev/null and b/docs/imgs/beginnen/image0.png differ diff --git a/docs/imgs/beginnen/image1.png b/docs/imgs/beginnen/image1.png new file mode 100644 index 0000000..a7295a7 Binary files /dev/null and b/docs/imgs/beginnen/image1.png differ diff --git a/docs/imgs/beginnen/image10.png b/docs/imgs/beginnen/image10.png new file mode 100644 index 0000000..f774be8 Binary files /dev/null and b/docs/imgs/beginnen/image10.png differ diff --git a/docs/imgs/beginnen/image11.png b/docs/imgs/beginnen/image11.png new file mode 100644 index 0000000..6183001 Binary files /dev/null and b/docs/imgs/beginnen/image11.png differ diff --git a/docs/imgs/beginnen/image12.png b/docs/imgs/beginnen/image12.png new file mode 100644 index 0000000..5cd321e Binary files /dev/null and b/docs/imgs/beginnen/image12.png differ diff --git a/docs/imgs/beginnen/image13.png b/docs/imgs/beginnen/image13.png new file mode 100644 index 0000000..48467a9 Binary files /dev/null and b/docs/imgs/beginnen/image13.png differ diff --git a/docs/imgs/beginnen/image14.png b/docs/imgs/beginnen/image14.png new file mode 100644 index 0000000..7dd937c Binary files /dev/null and b/docs/imgs/beginnen/image14.png differ diff --git a/docs/imgs/beginnen/image15.png b/docs/imgs/beginnen/image15.png new file mode 100644 index 0000000..ea40867 Binary files /dev/null and b/docs/imgs/beginnen/image15.png differ diff --git a/docs/imgs/beginnen/image16.png b/docs/imgs/beginnen/image16.png new file mode 100644 index 0000000..0f6b832 Binary files /dev/null and b/docs/imgs/beginnen/image16.png differ diff --git a/docs/imgs/beginnen/image17.png b/docs/imgs/beginnen/image17.png new file mode 100644 index 0000000..3fb2cbf Binary files /dev/null and b/docs/imgs/beginnen/image17.png differ diff --git a/docs/imgs/beginnen/image2.png b/docs/imgs/beginnen/image2.png new file mode 100644 index 0000000..5602dbb Binary files /dev/null and b/docs/imgs/beginnen/image2.png differ diff --git a/docs/imgs/beginnen/image3.png b/docs/imgs/beginnen/image3.png new file mode 100644 index 0000000..ff4294d Binary files /dev/null and b/docs/imgs/beginnen/image3.png differ diff --git a/docs/imgs/beginnen/image4.png b/docs/imgs/beginnen/image4.png new file mode 100644 index 0000000..80d9835 Binary files /dev/null and b/docs/imgs/beginnen/image4.png differ diff --git a/docs/imgs/beginnen/image5.png b/docs/imgs/beginnen/image5.png new file mode 100644 index 0000000..633880d Binary files /dev/null and b/docs/imgs/beginnen/image5.png differ diff --git a/docs/imgs/beginnen/image6.png b/docs/imgs/beginnen/image6.png new file mode 100644 index 0000000..c6bb84b Binary files /dev/null and b/docs/imgs/beginnen/image6.png differ diff --git a/docs/imgs/beginnen/image7.png b/docs/imgs/beginnen/image7.png new file mode 100644 index 0000000..ca79e45 Binary files /dev/null and b/docs/imgs/beginnen/image7.png differ diff --git a/docs/imgs/beginnen/image8.png b/docs/imgs/beginnen/image8.png new file mode 100644 index 0000000..90601fa Binary files /dev/null and b/docs/imgs/beginnen/image8.png differ diff --git a/docs/imgs/beginnen/image9.png b/docs/imgs/beginnen/image9.png new file mode 100644 index 0000000..ee64359 Binary files /dev/null and b/docs/imgs/beginnen/image9.png differ diff --git a/docs/imgs/beginnen/new_project.png b/docs/imgs/beginnen/new_project.png new file mode 100644 index 0000000..ab9c543 Binary files /dev/null and b/docs/imgs/beginnen/new_project.png differ diff --git a/docs/imgs/camera/image1.png b/docs/imgs/camera/image1.png new file mode 100644 index 0000000..9413603 Binary files /dev/null and b/docs/imgs/camera/image1.png differ diff --git a/docs/imgs/camera/image2.png b/docs/imgs/camera/image2.png new file mode 100644 index 0000000..8b91b77 Binary files /dev/null and b/docs/imgs/camera/image2.png differ diff --git a/docs/imgs/camera/image3.png b/docs/imgs/camera/image3.png new file mode 100644 index 0000000..5a58fa5 Binary files /dev/null and b/docs/imgs/camera/image3.png differ diff --git a/docs/imgs/dataklaarzetten/image1.png b/docs/imgs/dataklaarzetten/image1.png new file mode 100644 index 0000000..9645e49 Binary files /dev/null and b/docs/imgs/dataklaarzetten/image1.png differ diff --git a/docs/imgs/dataklaarzetten/image10.png b/docs/imgs/dataklaarzetten/image10.png new file mode 100644 index 0000000..a92d651 Binary files /dev/null and b/docs/imgs/dataklaarzetten/image10.png differ diff --git a/docs/imgs/dataklaarzetten/image2.png b/docs/imgs/dataklaarzetten/image2.png new file mode 100644 index 0000000..e3a222c Binary files /dev/null and b/docs/imgs/dataklaarzetten/image2.png differ diff --git a/docs/imgs/dataklaarzetten/image3.png b/docs/imgs/dataklaarzetten/image3.png new file mode 100644 index 0000000..c23f194 Binary files /dev/null and b/docs/imgs/dataklaarzetten/image3.png differ diff --git a/docs/imgs/dataklaarzetten/image4.png b/docs/imgs/dataklaarzetten/image4.png new file mode 100644 index 0000000..9ae6637 Binary files /dev/null and b/docs/imgs/dataklaarzetten/image4.png differ diff --git a/docs/imgs/dataklaarzetten/image5.png b/docs/imgs/dataklaarzetten/image5.png new file mode 100644 index 0000000..80224d5 Binary files /dev/null and b/docs/imgs/dataklaarzetten/image5.png differ diff --git a/docs/imgs/dataklaarzetten/image6.png b/docs/imgs/dataklaarzetten/image6.png new file mode 100644 index 0000000..643d5b7 Binary files /dev/null and b/docs/imgs/dataklaarzetten/image6.png differ diff --git a/docs/imgs/dataklaarzetten/image7.png b/docs/imgs/dataklaarzetten/image7.png new file mode 100644 index 0000000..c5cb612 Binary files /dev/null and b/docs/imgs/dataklaarzetten/image7.png differ diff --git a/docs/imgs/dataklaarzetten/image8.png b/docs/imgs/dataklaarzetten/image8.png new file mode 100644 index 0000000..4e75c2e Binary files /dev/null and b/docs/imgs/dataklaarzetten/image8.png differ diff --git a/docs/imgs/dataklaarzetten/image9.png b/docs/imgs/dataklaarzetten/image9.png new file mode 100644 index 0000000..779a9a5 Binary files /dev/null and b/docs/imgs/dataklaarzetten/image9.png differ diff --git a/docs/imgs/how-layers-work-in-a-project.png b/docs/imgs/how-layers-work-in-a-project.png new file mode 100644 index 0000000..e326fdd Binary files /dev/null and b/docs/imgs/how-layers-work-in-a-project.png differ diff --git a/docs/imgs/packages/draft-release.png b/docs/imgs/packages/draft-release.png new file mode 100644 index 0000000..5a048e8 Binary files /dev/null and b/docs/imgs/packages/draft-release.png differ diff --git a/docs/imgs/packages/openupm-add-package-location.png b/docs/imgs/packages/openupm-add-package-location.png new file mode 100644 index 0000000..39ad8b9 Binary files /dev/null and b/docs/imgs/packages/openupm-add-package-location.png differ diff --git a/docs/imgs/packages/openupm-provide-repository-location.png b/docs/imgs/packages/openupm-provide-repository-location.png new file mode 100644 index 0000000..4541d9c Binary files /dev/null and b/docs/imgs/packages/openupm-provide-repository-location.png differ diff --git a/docs/imgs/packages/openupm-submission-form.png b/docs/imgs/packages/openupm-submission-form.png new file mode 100644 index 0000000..37fc568 Binary files /dev/null and b/docs/imgs/packages/openupm-submission-form.png differ diff --git a/docs/imgs/wms/image01.png b/docs/imgs/wms/image01.png new file mode 100644 index 0000000..380bc30 Binary files /dev/null and b/docs/imgs/wms/image01.png differ diff --git a/docs/imgs/wms/image02.png b/docs/imgs/wms/image02.png new file mode 100644 index 0000000..07ab6aa Binary files /dev/null and b/docs/imgs/wms/image02.png differ diff --git a/docs/imgs/wms/image03.png b/docs/imgs/wms/image03.png new file mode 100644 index 0000000..6fa574a Binary files /dev/null and b/docs/imgs/wms/image03.png differ diff --git a/docs/imgs/wms/image04.png b/docs/imgs/wms/image04.png new file mode 100644 index 0000000..3d52aef Binary files /dev/null and b/docs/imgs/wms/image04.png differ diff --git a/docs/imgs/wms/image05.png b/docs/imgs/wms/image05.png new file mode 100644 index 0000000..f617506 Binary files /dev/null and b/docs/imgs/wms/image05.png differ diff --git a/docs/imgs/wms/image06.png b/docs/imgs/wms/image06.png new file mode 100644 index 0000000..14fa6d0 Binary files /dev/null and b/docs/imgs/wms/image06.png differ diff --git a/docs/imgs/zonnestand/image1.png b/docs/imgs/zonnestand/image1.png new file mode 100644 index 0000000..e01ef49 Binary files /dev/null and b/docs/imgs/zonnestand/image1.png differ diff --git a/docs/imgs/zonnestand/image2.png b/docs/imgs/zonnestand/image2.png new file mode 100644 index 0000000..10b9372 Binary files /dev/null and b/docs/imgs/zonnestand/image2.png differ diff --git a/docs/imgs/zonnestand/image3.png b/docs/imgs/zonnestand/image3.png new file mode 100644 index 0000000..b85893c Binary files /dev/null and b/docs/imgs/zonnestand/image3.png differ diff --git a/docs/imgs/zonnestand/image4.png b/docs/imgs/zonnestand/image4.png new file mode 100644 index 0000000..27a7f89 Binary files /dev/null and b/docs/imgs/zonnestand/image4.png differ diff --git a/docs/organisations/index.html b/docs/organisations/index.html new file mode 100644 index 0000000..8a5caa9 --- /dev/null +++ b/docs/organisations/index.html @@ -0,0 +1,1497 @@ + + + + + + + + + + + + + + + + + + + + + + + Voor organisaties - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Voor organisaties

+

Onder ontwikkeling

+

We werken momenteel aan de ontwikkeling van deze sectie om een uitgebreide bron van informatie te bieden. +Het doel is om organisaties te ondersteunen met gidsen, best practices en antwoorden op +veelgestelde vragen die essentieel zijn voor het gebruik van het Netherlands3D platform.

+

In de tussentijd moedigen we u aan om contact met ons op te nemen bij vragen met betrekking tot het platform.

+

Blijf op de hoogte voor updates en verbeteringen aan deze sectie. We kijken ernaar uit om te voorzien in de +informatie en hulpmiddelen die nodig zijn om goed gebruik te maken van Netherlands3D.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/favicon-16x16.png b/favicon-16x16.png new file mode 100644 index 0000000..adc5b89 Binary files /dev/null and b/favicon-16x16.png differ diff --git a/favicon-32x32.png b/favicon-32x32.png new file mode 100644 index 0000000..ee79c19 Binary files /dev/null and b/favicon-32x32.png differ diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..0e63d27 Binary files /dev/null and b/favicon.ico differ diff --git a/images/favicon.ico b/images/favicon.ico new file mode 100644 index 0000000..f4736f5 Binary files /dev/null and b/images/favicon.ico differ diff --git a/images/hero.png b/images/hero.png new file mode 100644 index 0000000..26a7067 Binary files /dev/null and b/images/hero.png differ diff --git a/images/homepage/artikelen/customization.png b/images/homepage/artikelen/customization.png new file mode 100644 index 0000000..6f90943 Binary files /dev/null and b/images/homepage/artikelen/customization.png differ diff --git a/images/homepage/artikelen/get-started-in-5-minutes.png b/images/homepage/artikelen/get-started-in-5-minutes.png new file mode 100644 index 0000000..63165d1 Binary files /dev/null and b/images/homepage/artikelen/get-started-in-5-minutes.png differ diff --git a/images/homepage/artikelen/to-cloud-or-not-to-cloud.png b/images/homepage/artikelen/to-cloud-or-not-to-cloud.png new file mode 100644 index 0000000..549c599 Binary files /dev/null and b/images/homepage/artikelen/to-cloud-or-not-to-cloud.png differ diff --git a/images/homepage/partners/gemeente-amsterdam.png b/images/homepage/partners/gemeente-amsterdam.png new file mode 100644 index 0000000..7621673 Binary files /dev/null and b/images/homepage/partners/gemeente-amsterdam.png differ diff --git a/images/homepage/partners/gemeente-rotterdam.png b/images/homepage/partners/gemeente-rotterdam.png new file mode 100644 index 0000000..2b164b8 Binary files /dev/null and b/images/homepage/partners/gemeente-rotterdam.png differ diff --git a/images/homepage/partners/gemeente-utrecht.png b/images/homepage/partners/gemeente-utrecht.png new file mode 100644 index 0000000..1d62d21 Binary files /dev/null and b/images/homepage/partners/gemeente-utrecht.png differ diff --git a/images/homepage/partners/provincie-flevoland.png b/images/homepage/partners/provincie-flevoland.png new file mode 100644 index 0000000..b7f92fc Binary files /dev/null and b/images/homepage/partners/provincie-flevoland.png differ diff --git a/images/homepage/partners/provincie-utrecht.png b/images/homepage/partners/provincie-utrecht.png new file mode 100644 index 0000000..6317c0c Binary files /dev/null and b/images/homepage/partners/provincie-utrecht.png differ diff --git a/images/logo-text.svg b/images/logo-text.svg new file mode 100644 index 0000000..9c46eb4 --- /dev/null +++ b/images/logo-text.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/logo.png b/images/logo.png new file mode 100644 index 0000000..5aec53a Binary files /dev/null and b/images/logo.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..563f7c9 --- /dev/null +++ b/index.html @@ -0,0 +1,1715 @@ + + + + + + + + + + + + + + + + + + + + + Start - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+ + + + +
+
+

+

Netherlands3D

+

Netherlands3D, het open-source Digital Twin-framework ontworpen voor Nederland.

+

Door middel van datavisualisatie stelt Netherlands3D u in staat om gedetailleerde, op data gebaseerde modellen weer te +geven.

+

Start +Wat is het?

+
+

Wat is Netherlands3D?

+
+

Visualiseren van data

+
+

Zoek je een mogelijkheid om data aantrekkelijk en in samenhang in +beeld te brengen in een 3D omgeving? Dat kan in Netherlands 3D!

+

Netherlands3D maakt het mogelijk datasets vanaf verschillende bronnen +en in verschillende bestandsformaten te koppelen en in samenhang te +visualiseren. Heb je een interessante dataset of link in GeoJSON of WFS +formaat? Ontdek dan hoe deze er in Netherlands 3D uit ziet. +Gecombineerd met andere datasets en in 3D context geeft dit vaak nèt +dat beetje extra informatie dat je nodig hebt.

+

Naast het visualiseren van data, zijn er ook een aantal functionaliteiten +beschikbaar die je kunt inzetten om jouw project tot een succes te +maken. Kies de relevante tools simpelweg bij het opstarten van +Netherlands 3D.

+
+
+
+

Wat onderscheid Netherlands3D van andere viewers?

+
+

Netherlands 3D richt zich op data-visualisatie. Het aantrekkelijk, +integraal en begrijpelijk verbeelden van data staat centraal.

+

Wat Netherlands 3D verder onderscheid is dat jij als gebruiker zelf kiest +welke data er gevisualiseerd wordt en welke functionaliteiten je daarbij +wilt gebruiken. Dus geen lijsten met vooraf gedefinieerde kaartlagen +maar slechts koppelmogelijkheden naar databronnen en externe +applicaties waar jij op dat moment in jouw project behoefte aan hebt. +Denk aan bronnen van jouw lokale server, het landelijke dataloket PDOK +of diverse dataportalen van gemeentelijke en provinciale organisaties.

+
+
+
+

Heel Nederland uniform en consistent beschikbaar

+
+

Netherlands 3D combineert voor heel Nederland een aantal basis +datasets waardoor een eenduidig 3D beeld van Nederland ontstaat.

+

Op dit moment zijn dat de 3D BAG, de 3D Basisvoorziening en boomlocaties +uit de BGT. Het gezamenlijke 3D beeld dat ontstaat kun je gebruiken als +input voor jouw project. Groot voordeel is dat deze brondata voor heel +Nederland uniform en consistent beschikbaar wordt gemaakt. Dus altijd +gemakkelijk en vanaf één plek te gebruiken. Het kan ook input zijn als +basis voor rekenmodellen en simulatiesoftware of juist de output +daarvan zijn zodat uitkomsten aan een breed publiek getoond kunnen +worden. Voor het realiseren van koppelingen tussen Netherlands 3D en +reken- simulatie- en analysetools, nodigen we iedereen actief uit om +mee te ontwikkelen.

+
+
+
+

3D omgeving op maat

+
+

Wil iets specifieks dat meer aansluit bij jouw project of organisatie?

+

Netherlands 3D biedt een oplossing om een eigen 3D omgeving op maat +in te richten. Bijvoorbeeld een aangepaste startlocatie of vooraf +ingestelde functionaliteiten en datasets. Meldt je dan als organisatie aan. +Tot slot is er voor ontwikkelaars de mogelijkheid om actief mee te +bouwen aan het platform.

+

We nodigen iedereen dan ook actief uit om +met ons mee te denken en te bouwen. En wil je dat allemaal niet? +Gebruik dan gewoon de basisviewer van Netherlands 3D hier!

+
+
+
+

In samenwerking met

+
+
    +
  • +
  • +
  • +
  • +
  • +
+
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 0000000..34312a9 --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["nl"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Start","text":""},{"location":"#netherlands3d","title":"Netherlands3D","text":"

Netherlands3D, het open-source Digital Twin-framework ontworpen voor Nederland.

Door middel van datavisualisatie stelt Netherlands3D u in staat om gedetailleerde, op data gebaseerde modellen weer te geven.

Start Wat is het?

"},{"location":"#wat-is-netherlands3d","title":"Wat is Netherlands3D?","text":""},{"location":"#visualiseren-van-data","title":"Visualiseren van data","text":"

Zoek je een mogelijkheid om data aantrekkelijk en in samenhang in beeld te brengen in een 3D omgeving? Dat kan in Netherlands 3D!

Netherlands3D maakt het mogelijk datasets vanaf verschillende bronnen en in verschillende bestandsformaten te koppelen en in samenhang te visualiseren. Heb je een interessante dataset of link in GeoJSON of WFS formaat? Ontdek dan hoe deze er in Netherlands 3D uit ziet. Gecombineerd met andere datasets en in 3D context geeft dit vaak n\u00e8t dat beetje extra informatie dat je nodig hebt.

Naast het visualiseren van data, zijn er ook een aantal functionaliteiten beschikbaar die je kunt inzetten om jouw project tot een succes te maken. Kies de relevante tools simpelweg bij het opstarten van Netherlands 3D.

"},{"location":"#wat-onderscheid-netherlands3d-van-andere-viewers","title":"Wat onderscheid Netherlands3D van andere viewers?","text":"

Netherlands 3D richt zich op data-visualisatie. Het aantrekkelijk, integraal en begrijpelijk verbeelden van data staat centraal.

Wat Netherlands 3D verder onderscheid is dat jij als gebruiker zelf kiest welke data er gevisualiseerd wordt en welke functionaliteiten je daarbij wilt gebruiken. Dus geen lijsten met vooraf gedefinieerde kaartlagen maar slechts koppelmogelijkheden naar databronnen en externe applicaties waar jij op dat moment in jouw project behoefte aan hebt. Denk aan bronnen van jouw lokale server, het landelijke dataloket PDOK of diverse dataportalen van gemeentelijke en provinciale organisaties.

"},{"location":"#heel-nederland-uniform-en-consistent-beschikbaar","title":"Heel Nederland uniform en consistent beschikbaar","text":"

Netherlands 3D combineert voor heel Nederland een aantal basis datasets waardoor een eenduidig 3D beeld van Nederland ontstaat.

Op dit moment zijn dat de 3D BAG, de 3D Basisvoorziening en boomlocaties uit de BGT. Het gezamenlijke 3D beeld dat ontstaat kun je gebruiken als input voor jouw project. Groot voordeel is dat deze brondata voor heel Nederland uniform en consistent beschikbaar wordt gemaakt. Dus altijd gemakkelijk en vanaf \u00e9\u00e9n plek te gebruiken. Het kan ook input zijn als basis voor rekenmodellen en simulatiesoftware of juist de output daarvan zijn zodat uitkomsten aan een breed publiek getoond kunnen worden. Voor het realiseren van koppelingen tussen Netherlands 3D en reken- simulatie- en analysetools, nodigen we iedereen actief uit om mee te ontwikkelen.

"},{"location":"#3d-omgeving-op-maat","title":"3D omgeving op maat","text":"

Wil iets specifieks dat meer aansluit bij jouw project of organisatie?

Netherlands 3D biedt een oplossing om een eigen 3D omgeving op maat in te richten. Bijvoorbeeld een aangepaste startlocatie of vooraf ingestelde functionaliteiten en datasets. Meldt je dan als organisatie aan. Tot slot is er voor ontwikkelaars de mogelijkheid om actief mee te bouwen aan het platform.

We nodigen iedereen dan ook actief uit om met ons mee te denken en te bouwen. En wil je dat allemaal niet? Gebruik dan gewoon de basisviewer van Netherlands 3D hier!

"},{"location":"#in-samenwerking-met","title":"In samenwerking met","text":""},{"location":"_nieuws/","title":"nieuws","text":""},{"location":"_nieuws/#nieuws","title":"Nieuws","text":"
  • Beginnen in 5 minuten

    Ontdek de mogelijkheden van Netherlands3D.

    Lees verder

  • Je eigen cloud, of niet?

    Beheer je Netherlands3D zelf of laat je je ontzorgen?

    Vind het uit

  • Toon de kaart die jij wil

    Wil je jouw project, stad, gemeente of provincie laten zien? Dat kan.

    Ontdek de opties

"},{"location":"_uitgelicht/","title":"uitgelicht","text":""},{"location":"_uitgelicht/#uitgelicht","title":"Uitgelicht","text":"
  • Beginnen in 5 minuten

    Ontdek de mogelijkheden van Netherlands3D.

    Lees verder

  • Je eigen cloud, of niet?

    Beheer je Netherlands3D zelf of laat je je ontzorgen?

    Vind het uit

  • Toon de kaart die jij wil

    Wil je jouw project, stad, gemeente of provincie laten zien? Dat kan.

    Ontdek de opties

"},{"location":"_wat-is-netherlands3d/","title":"wat is netherlands3d","text":""},{"location":"_wat-is-netherlands3d/#wat-is-netherlands3d","title":"Wat is Netherlands3D?","text":""},{"location":"_wat-is-netherlands3d/#visualiseren-van-data","title":"Visualiseren van data","text":"

Zoek je een mogelijkheid om data aantrekkelijk en in samenhang in beeld te brengen in een 3D omgeving? Dat kan in Netherlands 3D!

Netherlands3D maakt het mogelijk datasets vanaf verschillende bronnen en in verschillende bestandsformaten te koppelen en in samenhang te visualiseren. Heb je een interessante dataset of link in GeoJSON of WFS formaat? Ontdek dan hoe deze er in Netherlands 3D uit ziet. Gecombineerd met andere datasets en in 3D context geeft dit vaak n\u00e8t dat beetje extra informatie dat je nodig hebt.

Naast het visualiseren van data, zijn er ook een aantal functionaliteiten beschikbaar die je kunt inzetten om jouw project tot een succes te maken. Kies de relevante tools simpelweg bij het opstarten van Netherlands 3D.

"},{"location":"_wat-is-netherlands3d/#wat-onderscheid-netherlands3d-van-andere-viewers","title":"Wat onderscheid Netherlands3D van andere viewers?","text":"

Netherlands 3D richt zich op data-visualisatie. Het aantrekkelijk, integraal en begrijpelijk verbeelden van data staat centraal.

Wat Netherlands 3D verder onderscheid is dat jij als gebruiker zelf kiest welke data er gevisualiseerd wordt en welke functionaliteiten je daarbij wilt gebruiken. Dus geen lijsten met vooraf gedefinieerde kaartlagen maar slechts koppelmogelijkheden naar databronnen en externe applicaties waar jij op dat moment in jouw project behoefte aan hebt. Denk aan bronnen van jouw lokale server, het landelijke dataloket PDOK of diverse dataportalen van gemeentelijke en provinciale organisaties.

"},{"location":"_wat-is-netherlands3d/#heel-nederland-uniform-en-consistent-beschikbaar","title":"Heel Nederland uniform en consistent beschikbaar","text":"

Netherlands 3D combineert voor heel Nederland een aantal basis datasets waardoor een eenduidig 3D beeld van Nederland ontstaat.

Op dit moment zijn dat de 3D BAG, de 3D Basisvoorziening en boomlocaties uit de BGT. Het gezamenlijke 3D beeld dat ontstaat kun je gebruiken als input voor jouw project. Groot voordeel is dat deze brondata voor heel Nederland uniform en consistent beschikbaar wordt gemaakt. Dus altijd gemakkelijk en vanaf \u00e9\u00e9n plek te gebruiken. Het kan ook input zijn als basis voor rekenmodellen en simulatiesoftware of juist de output daarvan zijn zodat uitkomsten aan een breed publiek getoond kunnen worden. Voor het realiseren van koppelingen tussen Netherlands 3D en reken- simulatie- en analysetools, nodigen we iedereen actief uit om mee te ontwikkelen.

"},{"location":"_wat-is-netherlands3d/#3d-omgeving-op-maat","title":"3D omgeving op maat","text":"

Wil iets specifieks dat meer aansluit bij jouw project of organisatie?

Netherlands 3D biedt een oplossing om een eigen 3D omgeving op maat in te richten. Bijvoorbeeld een aangepaste startlocatie of vooraf ingestelde functionaliteiten en datasets. Meldt je dan als organisatie aan. Tot slot is er voor ontwikkelaars de mogelijkheid om actief mee te bouwen aan het platform.

We nodigen iedereen dan ook actief uit om met ons mee te denken en te bouwen. En wil je dat allemaal niet? Gebruik dan gewoon de basisviewer van Netherlands 3D hier!

"},{"location":"articles/","title":"Artikelen","text":"

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sodales est nec dolor luctus cursus. Sed tincidunt orci fermentum varius mattis. Quisque ut molestie augue, sit amet ornare sem. Praesent sit amet auctor nisi. Vivamus ornare tincidunt arcu in euismod. Proin ipsum ante, semper et sollicitudin eget, venenatis maximus nulla. Suspendisse potenti. Nulla facilisi.

"},{"location":"articles/2024/04/25/nieuwe-website/","title":"Nieuwe website","text":"

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sodales est nec dolor luctus cursus. Sed tincidunt orci fermentum varius mattis. Quisque ut molestie augue, sit amet ornare sem. Praesent sit amet auctor nisi. Vivamus ornare tincidunt arcu in euismod. Proin ipsum ante, semper et sollicitudin eget, venenatis maximus nulla. Suspendisse potenti. Nulla facilisi.

Fusce rutrum mi vel purus interdum imperdiet. Curabitur tempus fermentum odio et venenatis. In hac habitasse platea dictumst. Praesent pellentesque odio vel suscipit malesuada. Nulla facilisis mauris euismod pulvinar ullamcorper. Ut tincidunt lacus vitae nibh faucibus iaculis. Phasellus vitae risus ex.

Donec at orci urna. Ut ac turpis nec quam luctus cursus. Nunc lobortis quam vitae iaculis dignissim. Vestibulum pretium, nulla id volutpat luctus, mauris leo rhoncus tortor, ut fringilla massa purus in erat. Donec blandit id sem et accumsan. Pellentesque at eleifend urna. Mauris quis odio odio. Interdum et malesuada fames ac ante ipsum primis in faucibus. Vivamus sollicitudin magna libero, eget congue odio mollis gravida. Praesent arcu tellus, ultrices id porttitor vitae, tincidunt quis dolor. Vivamus aliquet nulla non augue molestie, sed elementum nisl maximus. Curabitur ut augue eu urna ornare hendrerit sed at dolor. Duis vulputate ullamcorper viverra. Proin id urna sed turpis euismod blandit. Etiam gravida ipsum eu ligula lacinia, ut ultricies magna porta. Cras sem metus, commodo tincidunt ultricies fringilla, facilisis sit amet enim.

Pellentesque nibh dui, iaculis at vehicula in, iaculis ut dui. Donec ac nisl eget massa pellentesque vehicula. Morbi interdum magna ac leo malesuada commodo. Nulla ac risus ut lectus eleifend semper. Nullam placerat sem quis lectus aliquam tristique. Nullam ante nisl, laoreet ut elit eu, laoreet gravida ex. Aenean eget sagittis magna. Donec non faucibus leo. Nullam rutrum euismod nisl, sed consequat arcu imperdiet et. Vivamus sit amet mi placerat, luctus sem a, posuere sem. Aliquam feugiat est eget risus dapibus efficitur.

Nam vehicula sollicitudin nibh, at efficitur ligula tincidunt in. Pellentesque turpis nibh, tempor vitae consequat non, molestie ac turpis. Duis tempor vulputate urna, non iaculis velit faucibus vel. Aliquam erat volutpat. Donec eu porttitor lacus, in dignissim odio. Nam at laoreet sapien, pharetra scelerisque sapien. Nulla sed quam pretium, pellentesque dolor pharetra, aliquam nulla.

"},{"location":"docs/developers/","title":"Introduction","text":"

Welcome to the technical documentation for Netherlands3D, an advanced Digital Twin platform designed to visualize and analyze geospatial data in a 3D environment. This documentation is intended for developers who want to understand how the platform works, explore its architecture, and contribute new modules or functionalities.

Why English?

While the rest of this website and the user-facing documentation is provided in Dutch, in accordance with government guidelines, the technical documentation is written in English.

This decision has been made to ensure a broad range of developers can contribute to Netherlands3D and to maintain consistency with the platform's codebase, which uses English terms and concepts. By using English in the technical documentation, we aim to make it easier for developers to map concepts between the documentation and the code itself, facilitating a smoother development and contribution process.

"},{"location":"docs/developers/#overview","title":"Overview","text":"

Netherlands3D is built on a modular architecture, allowing for extensibility. Developers can add, modify, or replace individual modules without affecting the entire system. The platform integrates data from various sources, such as 3D Tiles, the national georegister, and sensor information, to create an interactive and dynamic 3D environment for spatial analysis and visualization.

This documentation will provide you with a detailed understanding of how the system operates under the hood, including its component-based architecture, event-driven communication, and handling of secure data layers.

"},{"location":"docs/developers/#key-technologies","title":"Key Technologies","text":"
  • Unity3D: The platform is developed using Unity, leveraging its robust rendering capabilities to display high-performance 3D models and large-scale datasets.
  • Scriptable Objects: Used extensively to manage data, configuration, and communication between different modules and functionalities.
  • Event-Driven System: Internal communication within modules is handled via UnityEvents, while inter-module interactions are driven by Scriptable Object Events, which allow asynchronous communication and keep the system decoupled.

This documentation will guide you through the internal workings of the platform, including detailed API references, examples of module creation, and best practices for integrating new functionality.

"},{"location":"docs/developers/configuration/","title":"Configuration","text":"

The Netherlands3D platform has a modular configuration system in which functionalities in the application can register their own per-deployment defaults and settings. This can be used to -for example- pre-package API Keys and server URLs when deploying the application.

Info

The configuration system is not meant to store user preferences or per-project settings but instead it enables system operators to provide keys and similar information that is specific to the deployment of the platform.

"},{"location":"docs/developers/configuration/#defaults-are-settings-that-can-be-overridden-by-the-user","title":"Defaults are settings that can be overridden by the user","text":"

As a brief aside, let's first discuss what we mean by defaults and settings to provide context for the rest of this chapter.

Functionalities can have per-project and per-deployment properties. While the per-project properties as stored in the Project structure, the Configuration for each functionality may have defaults that can be used to pre-populate a new project, or when a project does not feature these properties.

Example

The Digital Twin for the province of Utrecht would want all new projects to have their camera focused on the city of Utrecht. While the starting position of the camera can be overridden as a project setting, the default is provided in the configuration that is provided by the province.

Configuration settings on the other hand are not changeable on a per-project basis and can be used to provide URLs to specific tilesets for the base layers, API Keys for working with a back-end, or pre-provide a set of access tokens for WFS and WMS services that the deployment should always have access to.

Warning

Information provided through the configuration system are exposed through a publicly accessible configuration file as the Digital Twin platform is fully client-side. Information that should stay secret should not be provided through this mechanism.

To summarize: A default is the same as a setting but it can be overridden from a query parameter in the URL or via a project file.

"},{"location":"docs/developers/configuration/#configuring-the-platform","title":"Configuring the platform","text":"

Settings can be provided through a configuration file called the app-config.json and should be present in the same folder as where your index.html file is located of your deployment.

Info

Are you using the Docker image? Then you can specify the settings using environment variables, please see https://github.com/Netherlands3D/twin/blob/main/.docker/README.md for more information on using the Docker image and which variables are supported.

This JSON file has the following structure -where [...] represents a placeholder where we omitted content for brevity or clarity:

{\n  \"origin\": {\n    \"epsg\": 7415,\n    \"x\": 161088,\n    \"y\": 503050,\n    \"z\": 500\n  },\n  \"functionalities\": {\n    [...]\n  }\n}\n

Where functionalities contains a listing of each available functionality and whether they are enabled, and what additional configuration they may have.

The origin section defines the initial location of the world origin. It is recommended to keep the epsg code at 7415 -representing the CRS EPSG:7415- and provide the x, y and z coordinates in Rijksdriehoekformaat with a height in NAP. The camera will initially be positioned at that location.

Functionalities always follow this format:

{\n  [...]\n  \"functionalities\": {\n    \"[CODE]\": {\n      \"enabled\": true/false,\n      \"configuration\": {\n        [...]\n      }\n    }\n  }\n}\n

In the snippet above, [CODE] can be substituted with the functionality code string as defined in the application -more on how to find this in the documentation on functionalities.

enabled represents a boolean whether the functionality is enabled in the application or not and the configuration json object represents the means to provide additional configuration for that functionality. If there is no additional configuration or you want the functionality its defaults, you are free to omit the additional configuration section.

"},{"location":"docs/developers/core-concepts/","title":"Core concepts","text":"

Whether you are plotting sensor telemetry, planning new building projects, or mapping underground utilities like cables and pipes, Netherlands3D offers a versatile environment to bring your spatial data to life. This page introduces the key concepts behind the platform, providing a foundation for understanding how the application is structured and how you can leverage it for your projects.

"},{"location":"docs/developers/core-concepts/#projects-the-canvas-of-your-work","title":"Projects: The Canvas of Your Work","text":"

In Netherlands3D, everything you interact with on the screen is part of a Project. A project encompasses all the data layers, styles, and configurations that define your current view and can be saved or loaded as needed. Think of a project as a snapshot of your work, capturing everything from the base map to the overlaid datasets, and allowing you to revisit or share your work with others seamlessly.

"},{"location":"docs/developers/core-concepts/#layers-the-foundation-of-visualization","title":"Layers: The Foundation of Visualization","text":"

In Netherlands3D, Layers are the fundamental components that bring spatial data to life on the map. Each layer represents a specific dataset, determining not only what data is visualized but also how it appears.

While Rasterlayers and Vectorlayers are the primary types, there are also additional supporting layers like folders and scenarios that help organize your project. However, the focus of this chapter will be on understanding the two main types of layers and the nature of the datasets they contain.

"},{"location":"docs/developers/core-concepts/#understanding-datasets-tiled-vs-fixed","title":"Understanding Datasets: Tiled vs. Fixed","text":"

Before diving into the specifics of Raster and Vector layers, it's important to understand the concept of how datasets are handled: Tiled versus Fixed.

  • Tiled Datasets: Tiling is a method used to optimize the performance of large datasets by breaking them down into smaller, more manageable pieces called tiles. These tiles are loaded dynamically as needed, which is especially useful when working with high-resolution data or when users need to navigate large geographical areas. Tiled datasets ensure that only the necessary portions of data are loaded at any given time, reducing memory usage and enhancing performance.

  • Fixed Datasets: In contrast, fixed datasets are loaded in their entirety at once. This approach can be beneficial when working with smaller datasets or when it's crucial to have immediate access to all the data without waiting for it to stream. However, this method can be more demanding on system resources, particularly for large or complex datasets.

The choice between tiled and fixed datasets affects how the data is visualized and interacted with in Netherlands3D. Whether a dataset is continuous (like elevation data) or discrete (like building footprints), it can be managed using either a tiled or fixed approach.

"},{"location":"docs/developers/core-concepts/#rasterlayers-visualizing-continuous-data","title":"Rasterlayers: Visualizing Continuous Data","text":"

Rasterlayers are used to display continuous data across a geographic area, such as heatmaps, terrain models, or aerial images. These layers are ideal for visualizing information that smoothly varies over space, like temperature changes across a city or elevation in a landscape.

In Netherlands3D, Rasterlayers are powerful for showing broad, sweeping patterns in your data. For example, if you want to understand how pollution levels vary across a region, a Rasterlayer can provide a clear, visual representation of these gradients.

"},{"location":"docs/developers/core-concepts/#vectorlayers-representing-discrete-data","title":"Vectorlayers: Representing Discrete Data","text":"

Vectorlayers are used to map specific features like buildings, roads, utilities, or zones. Each feature is represented by points, lines, or polygons, and comes with attributes that describe it \u2014such as the height of a building or the type of road.

Vectorlayers are essential for projects that require precise and detailed mapping. For instance, when planning new infrastructure, you can use Vectorlayers to map out exactly where new roads or pipelines will go. Each feature can be styled differently based on its attributes, making it easy to distinguish between different types of data.

"},{"location":"docs/developers/core-concepts/#features-the-building-blocks-of-layers","title":"Features: The Building Blocks of Layers","text":"

In Netherlands3D, Features are the individual elements that make up a Vectorlayer and can also be present in certain modern Rasterlayers. A feature represents a distinct geographical entity, such as a building, road, or zone, and can be styled and visualized according to your project\u2019s needs.

Understanding features is crucial because they are the entities you interact with and analyze within a layer. Whether you are drawing a map of utilities, defining zoning regulations, or visualizing sensor data, you are essentially working with features.

The ability to style and categorize these features, especially within the context of both vector and hybrid raster layers, provides the flexibility to create detailed, informative, and visually compelling maps.

"},{"location":"docs/developers/core-concepts/#features-in-vectorlayers","title":"Features in Vectorlayers","text":"

For Vectorlayers, features are the fundamental units. Each feature within a Vectorlayer has a specific geometry (point, line, or polygon) and is tied to a set of attributes that describe its properties. For example, a Vectorlayer representing buildings might have features corresponding to individual buildings, with attributes detailing their height, function, or construction material. These features can be styled in various ways using the platform's styling options, allowing you to differentiate between types of buildings, emphasize certain zones, or highlight critical infrastructure.

"},{"location":"docs/developers/core-concepts/#features-in-rasterlayers-the-hybrid-approach","title":"Features in Rasterlayers: The Hybrid Approach","text":"

Traditionally, Rasterlayers \u2014such as overlay images or heatmaps\u2014 do not contain discrete features. They represent continuous data spread across a grid, where each cell or pixel has a value but is not individually distinguished as a feature. However, with advances in 3D mapping technology, a new hybrid approach has emerged.

In this modern method, certain Rasterlayers, especially those involving 3D meshes, may incorporate features. For example, a 3D terrain mesh (which is typically raster data) can have specific parts of the mesh categorized as features. These features within a Rasterlayer might represent distinct geographical areas, such as regions of interest or zones within a city, and can be individually styled and interacted with, similar to features in Vectorlayers. This blending of raster and vector data allows for a more nuanced and detailed representation of the environment, offering new possibilities for visualizing and analyzing spatial data.

"},{"location":"docs/developers/core-concepts/#styles-bringing-data-to-life","title":"Styles: Bringing Data to Life","text":"

While layers define what data is displayed and where, Styles (or Symbology) determine how that data is visualized. Styles control the visual appearance of layers, including colors, line thickness, transparency, and more. Similar to how CSS styles HTML in web development, styles in Netherlands3D customize the visual representation of your spatial data. Whether you want to highlight certain areas, differentiate between types of infrastructure, or simply make your map more visually appealing, styles give you the flexibility to achieve your desired look and feel.

"},{"location":"docs/developers/core-concepts/#conclusion","title":"Conclusion","text":"

Understanding these core concepts \u2014Projects, Layers, Rasterlayers, Vectorlayers, Features, Styles, and the distinction between Tiled and Fixed datasets\u2014 is crucial for effectively using Netherlands3D. These elements form the backbone of the platform, allowing you to create, visualize, and interact with spatial data in a way that meets your project needs. As you dive deeper into the application, these concepts will guide you in organizing your work and optimizing your use of the platform\u2019s powerful features.

"},{"location":"docs/developers/functionalities/","title":"Functionalities","text":"

At the core of Netherlands3D, various functionalities serve as building blocks. Each functionality represents a distinct element or feature of the platform, designed to perform specific tasks. These functionalities are modular, meaning they can be enabled or disabled individually based on user requirements or the needs of a particular project.

This ability to toggle functionalities on and off ensures that you have control over the features you want to deploy in your project.

"},{"location":"docs/developers/functionalities/#configuring-functionalities","title":"Configuring Functionalities","text":"

Furthermore, each functionality can be individually configured according to specific project requirements. Read more about this in detail in the chapter about configuration.

Through additional configuration settings, you have the flexibility to adjust the functionality's behavior and characteristics to match your project's needs. You'll have access to a broad range of settings allowing you to refine each element of your project in detail.

To give some context to the above: examples of additional configuration options are API keys, URLs of services, default values for new projects and more.

"},{"location":"docs/developers/functionalities/#how-does-it-work","title":"How does it work","text":"

In Unity's context for the Netherlands3D platform, each functionality is encapsulated in a ScriptableObject. Exploring further, you'll discover that each ScriptableObject is highly configurable, granting you the ability to tailor it to satisfy specific demands.

"},{"location":"docs/developers/functionalities/#key-components","title":"Key Components","text":""},{"location":"docs/developers/functionalities/#scriptableobjects","title":"ScriptableObjects","text":"

The ScriptableObjects for the functionalities can be located in the Scriptables/Functionalities folder. Each ScriptableObject represents a distinct functionality in the application, and can be configured to define its behavior and properties within the application.

"},{"location":"docs/developers/functionalities/#functionality-system","title":"Functionality System","text":"

The code powering the functionality system is hosted within the embedded package named eu.netherlands3d.eu.twin-functionalities. Digging into this package reveals the design and implementation of the system that manages and orchestrates the functionalities.

"},{"location":"docs/developers/functionalities/#the-functionality-script","title":"The Functionality Script","text":"

At the core, the Functionality script exists as a foundational unit for the ScriptableObject. Here is where you define key attributes such as its title, description, ID, and any additional configuration. This script performs the following roles:

  1. Title and Description: The title and description fields of the ScriptableObject are primarily designed for your reference in the settings screen of the application.

  2. Functionality ID: The ID (or code) is used to refer to the functionality from the configuration file. This ID attribute is instrumental in organizing and managing configurations of functionalities within the system robustly.

  3. Enabled State: The enabled state of the functionality indicates whether it's active or inactive. The system can query this status to determine the functionality's availability and act accordingly in any part of the application.

  4. Additional Configuration: The ScriptableObject also includes an additional configuration section where you can define supplementary settings and properties that control its behavior within your application.

"},{"location":"docs/developers/layers-with-authentication/","title":"Managing Layers with Authentication","text":"

In Netherlands3D, certain data layers require user authentication to be accessed and displayed. This guide explains how the system manages authentication for these layers, handles different types of credentials, and ensures sensitive information remains protected while maintaining layer visibility settings.

"},{"location":"docs/developers/layers-with-authentication/#1-layer-authentication-process","title":"1. Layer Authentication Process","text":"

When a user adds a layer that requires authentication, they are prompted to provide credentials in one of the following forms:

  • Username and Password
  • Access Key

Regardless of the credential type, the system records that the source associated with the layer (typically the layer's URL) requires authentication. This information is stored in a KeyVault.

"},{"location":"docs/developers/layers-with-authentication/#2-credential-storage-and-security","title":"2. Credential Storage and Security","text":"

To ensure that sensitive information is never compromised:

  • Credentials are not stored in the project file. Instead, the KeyVault retains only the metadata necessary to recognize that the layer is protected and requires credentials.
  • The KeyVault is saved alongside the project, but it does not contain the actual credentials, only the authentication type and associated layer source.
  • This design prevents sensitive credentials from being shared when the project file is distributed to third parties, ensuring privacy and security.
"},{"location":"docs/developers/layers-with-authentication/#3-handling-credentials-http-basic-authentication-vs-access-key","title":"3. Handling Credentials: HTTP Basic Authentication vs. Access Key","text":"

The system supports two main methods of authentication, depending on the type of credentials provided by the user:

  • HTTP Basic Authentication (Username and Password): If the user provides a username and password, the system queries the protected endpoint using HTTP Basic Authentication. This involves sending the credentials in the request header in accordance with the Basic Authentication protocol.

  • Access Key: If an access key is provided, the system attempts to use the key within the query string of the endpoint's URL. The application performs auto-detection to determine the appropriate query parameter name by testing common parameters like:

  • code

  • key
  • token

The system will make repeated requests to the endpoint using each query parameter name and observe the HTTP response. If the server returns an HTTP status code 401 (Unauthorized) or 403 (Forbidden) for each attempt, the application concludes that the provided credential is incorrect. If a valid credential is found, the system records the correct query parameter name in the KeyVault for future use.

"},{"location":"docs/developers/layers-with-authentication/#4-handling-layers-without-credentials","title":"4. Handling Layers Without Credentials","text":"

If a layer requiring authentication is added (either manually by the user or through reloading a project), and no valid credentials are provided, the following behavior occurs:

  • The layer enters a non-visible state, as it cannot be displayed without valid credentials.
  • The system issues a warning prompting the user to enter (or re-enter) credentials for the layer.

Note: The user must resolve this issue by supplying valid credentials before the layer can be displayed.

"},{"location":"docs/developers/layers-with-authentication/#5-maintaining-layer-visibility-settings","title":"5. Maintaining Layer Visibility Settings","text":"

One critical aspect of this system is that the visibility of the layer is preserved, even if it cannot be displayed due to authentication issues:

  • If the layer was hidden (via the eye icon) when the project was saved, it will remain hidden after authentication is provided.
  • Similarly, if the layer was visible, it will become visible once valid credentials are entered and the authentication issue is resolved.

This approach ensures that the visibility state is consistent and independent of the authentication status.

"},{"location":"docs/developers/layers-with-authentication/#6-temporary-state-of-authentication","title":"6. Temporary State of Authentication","text":"

The system does not store the authentication status (whether credentials have been verified or not) in the project file. This is because authentication status is a temporary state that should not persist across sessions. Each time a project is loaded, the authentication process must be reinitiated for protected layers, ensuring that credentials are entered securely and as needed.

"},{"location":"docs/developers/layers-with-authentication/#7-need-to-re-authenticate-reopening-the-application","title":"7. Need to re-authenticate reopening the application","text":"

A key implication of this design is that users will need to re-authenticate any protected layers every time they reopen the application or load a saved project file. Since credentials are never stored in the project file for security reasons, the system requires users to provide valid credentials again when the project is reloaded.

This approach ensures that sensitive credentials are handled securely, but it also means that re-authentication is necessary whenever a protected layer is accessed in a new session. Users should be aware that any protected layers will initially be in a non-visible state until the required credentials are provided.

"},{"location":"docs/developers/layers-with-authentication/#8-warnings-and-user-flow","title":"8. Warnings and User Flow","text":"

The current user flow for managing layer authentication includes:

  • A warning message indicating that the layer cannot be displayed due to missing or invalid credentials.
  • A prompt that allows the user to supply new credentials for the layer.
  • Once the credentials are provided and validated, the layer\u2019s display status is updated accordingly, without altering its pre-existing visibility state.

By following this process, Netherlands3D ensures that users can securely manage layers requiring authentication while maintaining proper visibility states and safeguarding sensitive data.

"},{"location":"docs/developers/layers/","title":"Layers","text":""},{"location":"docs/developers/layers/#layers_1","title":"Layers","text":"

Layers in Netherlands3D are objects that have an effect on the 3D environment. Layers are hierarchical, similar to GameObjects in Unity. Because of this. the Layer data structure makes use of Unity's GameObject/Transform hierarchy in order to minimize custom coding of the data structure, and to visualize the data structure in the Unity Editor hierarchy.

"},{"location":"docs/developers/layers/#functionality-of-layers","title":"Functionality of layers","text":"

All layers have the following basic functionality:

  • Parenting: This works similar to the Unity Hierarchy.
  • Reordering: This works similar to changing the sibling index within the same parent in the Unity hierarchy.
  • Visibility: This works similar to setting a GameObject active/inactive in the Unity hierarchy.
  • Properties: Each layer can have its own settings specific to the layer type, or even more specific to the content of the layer. This is similar to how the inspector in Unity will display all components and serialized fields of a GameObject.

The layer data structure hierarchy is managed by the class LayerData. There should only be 1 LayerData object in the scene.

"},{"location":"docs/developers/layers/#layer-types","title":"Layer types","text":"

Layers are split in 2 categories:

  1. Regular layers that do not need their own GameObject hierarchy (basic layers)
  2. Layers that do require a GameObject hierarchy (referenced layers)
"},{"location":"docs/developers/layers/#basic-layers","title":"Basic layers","text":"

The first category of layers is the most simple. These layers are an extension of the LayerNL3DBase class.

Folder layer:

The most simple form of a Layer in this category is a FolderLayer. This layers has no functionality, except for providing the user with a way to organise other layers. It is similar to an empty GameObject.

Polygon selection layer:

A PolygonSelectionLayer is a layer that represents a polygon in the 3D environment. This layer can be created by providing a List<Vector3> of points (either by user input or from another source). This will create a layer in the hierarchy, with a visualisation that will listen to user input so that the layer can be selected by the user by clicking on the visualisation in the scene.

"},{"location":"docs/developers/layers/#referenced-layers","title":"Referenced layers","text":"

In case a layer has an internal hierarchy (such as a hierarchy of object parts, or a container with different tile objects) a layer can be integrated in the layer system as a referenced layer. A referenced layer exists outside of the Layer data structure, but has a connection to it through a ReferencedProxyLayer . The ReferencedProxyLayer will pass the required actions that affect the layer to its reference outside of the layer hierarchy, thereby making the referenced layer comply with the layer functionalities.

In order to do this, the layer object outside of the hierarchy must have an extension of the abstract class ReferencedLayer attached to it. This class will automatically create the ReferencedProxyLayer, thereby creating the connection to the layer system.

The following Layers are referenced layers:

Hierarchical object layer:

A HierarchicalObjectLayer is an object that can be placed in the 3D environment. By default, this object can be moved by the transform handles. Objects of this type either come from a library of developer defined objects, or by user uploads. The most simple Hierarchical object layer is a GameObject with a MeshFilter, MeshRenderer, Collider, and HierarchicalObjectLayer component attached. More complex Hierarchical object layers can have nested GameObjects with each their own internal logic.

Cartesian tile layer:

A CartesianTileLayer is a layer that makes use of the Netherlands3D custom tile file format. The objects and tiles in the layer are managed by the TileHandler and through the class CartesianTileLayer can interact with the layer system. For more information see the section on Cartesian Tiles.

3D tile layer:

A Tile3DLayer2 is a layer that makes use of the 3D Tiles file format. These layers are managed by Read3DTileset and through the class Tile3DLayer2 can interact with the layer system. For more information see the section on 3D Tiles.

Object scatter layer:

An ObjectScatterLayer is a special type of layer that will scatter the combined mesh of a HierarchicalObjectLayer in the area defined by a PolygonLayer. To do this, create a PolygonLayer and make a HierarchicalObjectLayer the child of the PolygonLayer. This will convert the HierarchicalObjectLayer to an ObjectScatterLayer. Unparenting the ObjectScatterLayer from a PolygonLayer will revert the layer back to a HierarchicalObjectLayer. The ObjectScatterLayer has scatter settings that determine how the scattering should occur.

Warning

The scattering is achieved through GPUInstancing, and therefore the mesh and material should support this. All objects in the ObjectLibrary support scattering.

"},{"location":"docs/developers/layers/#layer-ui","title":"Layer UI","text":"

Each Layer can have an associated UI component defined by the class LayerUI. This component controls the UI elements associated with the layer. Interacting with the LayerUI will affect the corresponding LayerNL3DBase. The Layer UI hierarcy is managed by LayerManager. This class holds a references to the layer type icons that are requested by LayerUI.

"},{"location":"docs/developers/layers/#creating-your-own-layers-of-an-existing-type","title":"Creating your own layers of an existing type","text":"

In order to add one of your own layers as an option through the existing UI, the following steps should be followed:

  1. Create a prefab with the layer object. Make sure this object has a component of either LayerNL3DBase or ReferencedLayer attached to it.
  2. Instantiate the prefab. If this is done in the existing LayerPanel UI, add a button or toggle in the prefab AddLayerPanel.prefab in the appropriate panel sub section. Then have the script that controls the button or toggle logic instantiate the prefab made in step 1.
"},{"location":"docs/developers/layers/#creating-your-own-layers-of-a-new-type","title":"Creating your own layers of a new type","text":"

Follow the following steps to create a new layer type:

  1. Choose if the new layer type can be a simple layer (direct extension of LayerNL3DBase) or it needs to be a more complex referenced layer (extension of ReferencedLayer).
  2. Extend the chosen class and implement the required methods.
  3. Add your own logic to the new class
  4. Add a reference to the appropriate type and icon in LayerManager to ensure the new layer does not use the default ? icon.
"},{"location":"docs/developers/layers/#layers-and-functionalities","title":"Layers and Functionalities","text":"

Specific layers or specific types of layers can also be enabled/disabled with the functionalities system. Follow these steps to do this:

  1. Add a FunctionalityListener to the prefab of the layer. Add the functionality you want to link the layer to, and in the OnDisableFunctionality event, add a function that will destroy the gameObject itself. This will clean up any active layers when disabling the functionality.
  2. For any buttons that instantiate the prefab above, add a FunctionalityListener to the button that will enable/disable the button's gameObject when the functionality is enabled/disabled respectively. This will ensure the user cannot instantiate layers from functionalities that are disabled.
  3. (Optional) In case your layers should by default spawn when the functionality is enabled, add a PrefabSpawner script to the scene where the functionality and the associated prefab are set in the inspector.
"},{"location":"docs/developers/projects/","title":"Projects","text":""},{"location":"docs/developers/projects/#overview","title":"Overview","text":"

Netherlands3D is a dynamic platform designed to allow users to interact with 3D geographical maps of locations across the Netherlands. This platform enables a wide array of spatial data interactions, from visualizing sensor telemetry to managing infrastructure data like cables, pipes, and zoning information. At the core of this functionality is the concept of Projects, which serve as containers for all user interactions and data on the platform.

Projects in Netherlands3D encompass everything from the base map to the specific data layers, styles, and configurations that define the user's current view. Users can save these projects as snapshots of their work, allowing them to revisit, share, or continue working seamlessly at any time. This structure not only facilitates complex data analysis but also ensures that all spatial relationships are preserved and accurately represented.

"},{"location":"docs/developers/projects/#project-file-format","title":"Project File Format","text":"

Before diving into the technical specifics of how projects operate within the system, it's essential to understand the file format used to store project data. The project file acts as a blueprint, capturing all the layers, styles, configurations, and settings that define the project.

Todo

Describe the file format

"},{"location":"docs/developers/projects/#system-operation-overview","title":"System Operation Overview","text":"

Netherlands3D operates on a robust system that initializes with a default project file^1. This default project acts as the foundation upon which all subsequent user interactions and loaded projects are based.

"},{"location":"docs/developers/projects/#included-empty-project","title":"Included Empty Project","text":"

Upon startup, the system loads an empty project file^1 from the Assets^2 folder. This project serves as the initial state of the application, providing a base map and predefined configurations that set the scene for the user's interaction. The empty project is crucial for ensuring that the application is ready to use immediately upon launch, without requiring any user input.

"},{"location":"docs/developers/projects/#customizable-default-project","title":"Customizable Default Project","text":"

A default project is fully configurable per deployment, allowing providers to tailor the initial view and settings to their specific needs. For instance, a province or municipality might want to highlight a particular region or set specific visualization parameters to align with their objectives. This flexibility ensures that the application can be used in various contexts, whether as an interactive tool for end-users or a static viewer for presenting a digital twin of a specific location.

Todo

Describe how this can be done

"},{"location":"docs/developers/projects/#project-loading-and-replacement","title":"Project Loading and Replacement","text":"

Once the application is running, users can load their own project files. When a new project file is loaded, it replaces the current project in its entirety, bringing in all new data layers, styles, and configurations. This process ensures a smooth transition between different datasets and views, allowing users to focus on their work without worrying about residual configurations from previous projects.

Todo

Describe how this works in more detail with a flowchart illustrating the overall process

"},{"location":"docs/developers/projects/#how-layers-work","title":"How Layers Work","text":"

Self-registering layers

A variant of the described flow exists where by adding the visualisation of a layer it self-registers the layer's data onto the current project. This flow will be described in a separate chapter for clarity.

The diagram provided illustrates the lifecycle of layers within a project, detailing how they are acquired, constructed, and visualized. Here's a breakdown:

"},{"location":"docs/developers/projects/#acquiring-layers","title":"Acquiring Layers","text":"

Layers in Netherlands3D can be added to a project in several ways:

  • Importing via URL or File Upload: The system allows users to import data layers by providing a URL or uploading a file. The uploaded or referenced file is stored within the current project, and the system determines the layer type based on the file name or its contents.

  • Dragging from Object Library: Users can also add layers by dragging objects from a pre-existing object library. The system determines the type of layer based on the selected object.

  • Opening a Project: When a project is opened, the system unpacks its contents to a predefined storage path. This process involves instantiating a ProjectData object and associated LayerData objects through deserialization.
"},{"location":"docs/developers/projects/#constructing-layers","title":"Constructing Layers","text":"

Once a layer is added, the system begins constructing it:

  • The system finds a visualizer identifier for the specific layer type and then instantiates a LayerData object accordingly.
  • The LayerData is then registered with the current project, linking it to the broader project context.

Layers consist of two primary components:

  • Layer Data: This is a lightweight, serializable data object containing information about the layer, such as its source data location (e.g., file paths, URIs).
  • Layer Visualizer: This component handles the actual visualization work, such as spawning tiles and rendering geometry.
"},{"location":"docs/developers/projects/#visualizing-layers","title":"Visualizing Layers","text":"

The final stage is visualizing the layers within the application:

  • The system grabs the appropriate visualizer prefab based on the identifier in LayerData.
  • The visualizer loads the data from LayerData, and the application schedules visualization activities.
  • If necessary, the source data is parsed or interpreted before visualization, ensuring that all data layers are accurately represented.
  • Finally, the visualization scheduler renders the data, allowing users to interact with the newly loaded layers seamlessly.

This structured approach to handling layers ensures that all data is properly managed, visualized, and integrated within the project framework, offering a powerful tool for spatial analysis and geographic data management in the Netherlands3D platform.

"},{"location":"docs/developers/quickstart/","title":"Quick Start Guide","text":"

Info

This quickstart is aimed at developers who want to build on top of this platform, if all you want is to deploy it pre-configured on your platform, please read the guide on deploying using Docker.

"},{"location":"docs/developers/quickstart/#prerequisites","title":"Prerequisites","text":"

Netherlands3D is developed in Unity, thus you'll need few prerequisites to start with the platform:

  1. Install Unity, the recommended version is Unity 2023 LTS, including the WebGL build options. Please check the Unity Download Page -or Unity Hub- and the official Installation Guide for assistance.
  2. Basic understanding of Unity and C# programming language.
"},{"location":"docs/developers/quickstart/#getting-started","title":"Getting Started","text":"
  1. Firstly, clone the Netherlands3D/twin repository from GitHub:

    git clone https://github.com/Netherlands3D/twin.git\n
  2. Once cloned, open the project in Unity. After the project has loaded, you can start tweaking and modifying the application as per your needs.

Do make sure that your build settings are set to build the project as a WebGL application, as several functionalities are tailored to that platform.

"},{"location":"docs/developers/quickstart/#recommendations","title":"Recommendations","text":""},{"location":"docs/developers/quickstart/#copy-the-main-scene","title":"Copy the Main scene","text":"

It's recommended to create a copy of the 'Main' scene before making any modifications. This ensures that you're not directly altering the original scene which, as the project is in continuous development, can lead to conflicts.

To copy a scene in Unity:

  • Select the 'Main' scene in the Project window.
  • Press Ctrl+D or right-click and select 'Duplicate' from the context menu.
"},{"location":"docs/developers/quickstart/#inspect-the-configuration-and-configurationstarter-scriptable-objects","title":"Inspect the Configuration and ConfigurationStarter Scriptable Objects","text":"

Check the 'Configuration' and 'ConfigurationStarter' scriptable objects for initial settings of the project. This includes the starting position of your digital twin. These configurable parameters allow you to customize initial settings as per your requirements.

"},{"location":"docs/developers/quickstart/#next-steps","title":"Next Steps","text":"

With just these steps, you can immediately build and release your own customized Digital Twin; next up you can read up on what and how you can configure the platform without making any code changes.

"},{"location":"docs/developers/contributing/coding-standards/","title":"Coding Standards","text":"

This document outlines the coding standards for our Unity project. Adhering to consistent coding styles helps maintain the readability, maintainability, and collaboration ease of our codebase. These standards are in alignment with Unity\u2019s C# scripting practices and incorporate additional guidelines specific to our project.

"},{"location":"docs/developers/contributing/coding-standards/#1-naming-and-code-style","title":"1. Naming and Code Style","text":"

We follow the naming and code style tips provided by Unity to ensure consistency and familiarity for all developers working in our Unity project. Key aspects of this style include:

  • PascalCasing for public properties, class names, and methods.
  • camelCasing for private fields and local variables.
  • Suffix boolean variables with Is, Has, or Can (e.g., isReady, hasAccess, canShoot).
  • Avoid abbreviations unless they are well-known (e.g., maxHealth is preferable over mH).

We also emphasize clarity and meaningful names over brevity, ensuring that variable and function names communicate their intent effectively.

It is important to read the full guide at https://unity.com/how-to/naming-and-code-style-tips-c-scripting-unity for a more complete picture of the coding guidelines followed in this project.

"},{"location":"docs/developers/contributing/coding-standards/#general-principles","title":"General Principles","text":"
  • Call a thing what it is. A bird should be called Bird.
  • Choose names that can be pronounced and remembered.
  • Be consistent. When you choose a name, stick to it.
  • Use Pascal case, like this: ComplicatedVerySpecificObject. Do not use spaces, underscores, or hyphens.
  • Do not use version numbers, or words to indicate their progress (WIP, final).
  • Do not use abbreviations: DVamp@W should be DarkVampire@Walk.
  • Keep the most specific descriptor on the left: PauseButton, not ButtonPaused. It is, for instance, easier to find the pause button in the inspector if not all buttons start with the word Button.
  • Some names form a sequence. Use numbers in these names, for example, PathNode0, PathNode1. Always start with 0, not 1.
  • Do not use numbers for things that don\u2019t form a sequence. For example, Bird0, Bird1, Bird2 should be Flamingo, Eagle, Swallow.
"},{"location":"docs/developers/contributing/coding-standards/#structure","title":"Structure","text":"

The organisation of your project folder should follow a similar pattern.

Warning

We are in the process of moving project files to the structure below, until that process is complete you can expect to find asset folders directly in the root of the Assets folder.

For new components it is recommended to follow this structure.

We differentiate between architectural folders and asset folders. Architectural folders are meant to organize the project in such a way that we have a modular -or composable- architecture with independent layers; asset folders can be found inside each architectural folder and is similar to the regular folder structure that is normal with Unity.

"},{"location":"docs/developers/contributing/coding-standards/#architectural-folders","title":"Architectural folders","text":"

In the Assets folder there are four special folders:

  1. _Application - assets needed for the application itself, such as
    • Rendering pipelines,
    • Inputsystem,
    • Overall ui,
    • Bootstrapping
    • etc.
  2. _BuildingBlocks - contains a series of folders for each building block that can be consumed by a functionality
  3. _Functionalities
  4. _UIKit
"},{"location":"docs/developers/contributing/coding-standards/#unity-folder-structure","title":"Unity Folder Structure","text":"
  • Animations
  • Editor
  • Fonts
  • Materials
  • Models
  • Resources
  • Prefabs
  • Scenes
  • ScriptableObjects
  • Scripts
  • Shaders
  • Sprites
  • Textures
"},{"location":"docs/developers/contributing/coding-standards/#2-bracing-style","title":"2. Bracing Style","text":"

We use the Allman style for braces, which is standard in most C# coding conventions. This style places opening braces on a new line:

public class Player\n{\n    public void Jump()\n    {\n        // Code here\n    }\n}\n

This ensures clear structure and improves readability, especially in nested code blocks.

"},{"location":"docs/developers/contributing/coding-standards/#3-indentation","title":"3. Indentation","text":"

Indentation is essential for making code readable and understandable at a glance. We use 4 spaces for indentation rather than tabs. This ensures consistent formatting across different environments and prevents issues with mixed tab and space usage.

Ensure the indentation is correct, especially when working with nested blocks of code.

"},{"location":"docs/developers/contributing/coding-standards/#nesting-levels","title":"Nesting Levels","text":"

To maintain clarity and readability, the number of nesting levels within any method or function should be limited to 2 at most. If a deeper level of nesting is required, consider refactoring the code into supporting methods or even supporting services. This helps in avoiding overly complex methods and improves the maintainability of the code.

Example

Instead of having deeply nested if-statements or loops:

public void ProcessPlayerInput(Player player)\n{\n    if (player != null)\n    {\n        if (player.HasControl())\n        {\n            if (player.IsAlive)\n            {\n                if (player.HasWeapon())\n                {\n                    if (player.CanShoot())\n                    {\n                        ShootWeapon(player);\n                    }\n                }\n            }\n        }\n    }\n}\n

This code is difficult to read due to the excessive nesting. To improve readability, we can refactor the logic into separate methods:

public void ProcessPlayerInput(Player player)\n{\n    if (!IsValidPlayer(player)) return;\n\n    if (CanPlayerShoot(player))\n    {\n        ShootWeapon(player);\n    }\n}\n\nprivate bool IsValidPlayer(Player player)\n{\n    return player != null && player.HasControl() && player.IsAlive;\n}\n\nprivate bool CanPlayerShoot(Player player)\n{\n    return player.HasWeapon() && player.CanShoot();\n}\n

In this refactored example, the nesting is limited to 2 levels, and the logic has been broken down into meaningful, self-explanatory methods, making the code easier to understand and maintain. Each method handles a specific responsibility, which also helps with unit testing and debugging.

"},{"location":"docs/developers/contributing/coding-standards/#4-git-branching-strategy","title":"4. Git Branching Strategy","text":"

When creating branches for new features or bug fixes, follow the naming conventions outlined below. These conventions make it clear what type of change is being worked on and make branches easier to track and manage.

  • Branch Prefixes:

  • feature/ for new features.

  • fix/ for bug fixes or adjustments.

  • Branch Name Format: Use kebab-case for branch names, which involves using hyphens (-) to separate words.

Examples:

  • feature/implement-player-movement
  • fix/camera-rotation-bug

Following this strategy keeps the Git history clean and makes collaboration smoother, as everyone can quickly understand the purpose of each branch.

"},{"location":"docs/developers/contributing/documentation/","title":"Contributing to our Documentation","text":"

Thank you for your interest in contributing to the Netherlands3D documentation. This guide provides all the necessary information to help you get started with contributing to our documentation, which is composed of Markdown documents in our GitHub repository and is automatically built using MkDocs whenever a pull request is merged.

"},{"location":"docs/developers/contributing/documentation/#getting-started","title":"Getting Started","text":"

First, you'll need to set up your environment for making contributions:

  1. Fork the Repository: Go to https://github.com/Netherlands3D/netherlands3D.github.io/ and fork the repository to your GitHub account.
  2. Clone the Fork: Clone your forked repository to your local machine to make changes.
  3. Install MkDocs (Optional): If you wish to preview your changes locally, you'll need to install MkDocs. Follow the installation guide on their website.
"},{"location":"docs/developers/contributing/documentation/#making-contributions","title":"Making Contributions","text":""},{"location":"docs/developers/contributing/documentation/#language-and-voice","title":"Language and Voice","text":"
  • For user and organisation facing parts of the documentation, write in Dutch using the passive voice.
  • For developer-facing documentation, write in English.
"},{"location":"docs/developers/contributing/documentation/#adding-or-editing-content","title":"Adding or Editing Content","text":"
  1. Create a New Branch: Before making any changes, create a new branch in your forked repository.
  2. Add/Edit Markdown Files: Make your changes or add new content in the appropriate Markdown (.md) files. Be sure to follow the language and voice guidelines mentioned above.
  3. Update mkdocs.yml: If you are adding a new page, don't forget to update the mkdocs.yml configuration file to include your new page in the navigation.
"},{"location":"docs/developers/contributing/documentation/#previewing-your-changes","title":"Previewing Your Changes","text":"

If you've installed MkDocs locally, you can preview your changes by running the following command in your project directory:

mkdocs serve\n

This will start a local web server. Open your web browser and go to http://127.0.0.1:8000 to see your changes.

"},{"location":"docs/developers/contributing/documentation/#submitting-your-contributions","title":"Submitting Your Contributions","text":"

Once you are satisfied with your changes:

  1. Commit Your Changes: Add your changes to your fork and commit them with a clear commit message.
  2. Push to Your Fork: Push your changes to your GitHub fork.
  3. Create a Pull Request: Go to the original Netherlands3D/docs repository and create a new pull request. Target the main branch for your pull request and provide a clear description of your changes.
  4. Review Process: Your pull request will be reviewed by the Netherlands3D documentation team. Be open to feedback and make any requested changes.
"},{"location":"docs/developers/contributing/documentation/#conclusion","title":"Conclusion","text":"

Contributing to the Netherlands3D documentation is a collaborative effort. We appreciate your willingness to contribute and look forward to improving our documentation with your help. If you have any questions or need assistance, please open an issue in the GitHub repository.

"},{"location":"docs/developers/decisions/","title":"Architectural Decisions","text":"

In the Netherlands3D project, we recognize the importance of documenting key architectural decisions to ensure that the rationale behind our choices is clear, traceable, and accessible to all contributors and stakeholders. This section serves as a living document where we track these decisions, both accepted and rejected, to provide a transparent record of our technical direction.

"},{"location":"docs/developers/decisions/#why-track-architectural-decisions","title":"Why Track Architectural Decisions?","text":"

Architectural decisions often shape the foundation of a project and influence how future features are built. By documenting these decisions:

  • We ensure clarity and alignment: Everyone working on the project understands why certain paths were chosen over others.
  • We maintain consistency: Having a record of decisions helps to maintain a coherent and stable architectural vision as the project evolves.
  • We enable future-proofing: Teams and future developers can reference past decisions to avoid re-evaluating or re-implementing resolved issues.
  • We encourage informed collaboration: It creates an open dialogue for discussing technical options and ensures everyone has access to the decision-making process.
"},{"location":"docs/developers/decisions/#what-to-expect","title":"What to Expect","text":"

In this section, you will find a record of:

  • Accepted proposals: Decisions that have been approved and are guiding the current development.
  • Rejected proposals: Ideas that were considered but ultimately not adopted, along with the reasons why.
  • Ongoing discussions: Some entries may reflect decisions that are still under consideration, providing insight into our ongoing thought process.

Each architectural decision is written in a lightweight, actionable format that includes a summary of the problem, the considered options, the decision taken, and the rationale behind it. We strive to keep these entries concise while capturing the essential technical and business context.

"},{"location":"docs/developers/decisions/#language-considerations","title":"Language Considerations","text":"

Since the core Netherlands3D team operates primarily in Dutch, many architectural proposals and discussions will initially be documented in Dutch. This allows us to facilitate faster and more in-depth discussions within the team. However, we strive to provide English translations for most of these decisions to ensure that non-Dutch speakers can follow and contribute to the project. Whenever possible, both Dutch and English versions of each decision will be available.

"},{"location":"docs/developers/decisions/20241009-styling-of-layers/","title":"2024-10-09 - Laagstijlen / Symbologie","text":"Datum 09-10-2024 Status Geaccepteerd"},{"location":"docs/developers/decisions/20241009-styling-of-layers/#context","title":"Context","text":"

Als uitkomst van dit voorstel willen we dat Netherlands3D gebruik maakt van visualisaties waarbij lagen worden gestileerd met behulp van symbologie, gebaseerd op de OGC Cartographic Symbology - Part 1: Core Model & Encodings (https://docs.ogc.org/DRAFTS/18-067r4.html). De implementatie is een systeem vergelijkbaar met CSS, waarbij een Symbolizer styling-eigenschappen bevat, en Selectors (beschreven als Expressions) bepalen wanneer en hoe deze eigenschappen worden toegepast.

Hoewel de term \"symbologie\" gebruikelijk is in GIS, noemen we dit systeem \"styling\" om het toegankelijker te maken voor een breder publiek en de parallel met CSS te benadrukken.

Scheiding tussen Styling en Visualisatie

Het styling-systeem zelf doet niets met de daadwerkelijke visualisatie. Het systeem beschrijft alleen hoe de styling moet zijn, maar het is aan de visualisatiecode die hoort bij een laag om te bepalen of en hoe de styling-eigenschappen worden toegepast. Dit betekent dat het mogelijk is dat bepaalde styling-eigenschappen niet worden toegepast als de visualisatiecode daarvoor geen ondersteuning biedt.

"},{"location":"docs/developers/decisions/20241009-styling-of-layers/#besluit","title":"Besluit","text":"

We implementeren een flexibel styling-systeem waarin Symbolizers styling-eigenschappen bevatten, en Selectors en Expressions bepalen wanneer deze eigenschappen van toepassing zijn. Dit systeem wordt toegepast op lagen, en de mogelijke waarden binnen een Symbolizer worden bepaald door de aanwezige Symbolizer-extensies.

Externe partijen kunnen hun eigen extensies en styling-eigenschap prefixes toevoegen, volgens het patroon uit hoofdstuk 8.1.7 van de OGC-specificatie: vendor.<vendor naam>.<eigenschap naam>. Voor Netherlands3D betekent dit dat we de prefix vendor.netherlands3d.<eigenschap naam> zullen hanteren.

"},{"location":"docs/developers/decisions/20241009-styling-of-layers/#rationale","title":"Rationale","text":"

Het gebruik van een systeem, vergelijkbaar met CSS, biedt de flexibiliteit en uitbreidbaarheid die nodig is voor het werken met dynamische geodata. Door selectors en expressies te gebruiken, kunnen we styling-eigenschappen toepassen op basis van attribuutwaarden of andere condities. Symbolizer-extensies zorgen ervoor dat nieuwe styling-eigenschappen kunnen worden toegevoegd zonder de kernstructuur te verstoren. Door gebruik te maken van extension methods met default-waarden, waarborgen we een voorspelbare werking van het systeem, zelfs wanneer geen expliciete waarde aanwezig is.

"},{"location":"docs/developers/decisions/20241009-styling-of-layers/#codeorganisatie","title":"Codeorganisatie","text":"

De implementatie van het systeem wordt ondergebracht in een aparte OpenUPM-package. De code voor de verschillende Symbolizer-extensies wordt georganiseerd in mappen, waarbij iedere extensie een Requirement Class uit de OGC-specificatie volgt. Dit maakt het beheer van verschillende extensies overzichtelijk en zorgt voor een heldere scheiding tussen kernfunctionaliteit en uitbreidingen.

Wij passen zelf de styling-eigenschap prefix vendor.netherlands3d.<eigenschap naam> toe voor onze eigen uitbreidingen. Derde partijen kunnen hun eigen extensies via hun eigen packages toevoegen en daarbij hun eigen styling-eigenschap prefixes introduceren volgens hetzelfde patroon uit hoofdstuk 8.1.7 van de OGC-specificatie.

"},{"location":"docs/developers/decisions/20241009-styling-of-layers/#implementatiestrategie","title":"Implementatiestrategie","text":"

We voeren de implementatie stapsgewijs uit. Dit houdt in dat we niet alle eigenschappen van de Symbolizer in \u00e9\u00e9n keer toevoegen, maar in kleine iteraties werken. Elke iteratie implementeert \u00e9\u00e9n of meer specifieke eigenschappen van de Symbolizer, afhankelijk van de eisen van een user story. Bijvoorbeeld: wanneer een user story vraagt om een laag een enkele vulkleur te geven, wordt alleen de fill-eigenschap van de Symbolizer-extensie voor vectorfeatures ge\u00efmplementeerd.

Hoewel hoofdstuk 17 van de OGC-specificatie extra opties beschrijft zoals het gebruik van het \"alter\" veld, variabelen en het includen van stylesheets, zullen we deze opties expliciet niet implementeren. Ons doel is niet om de volledige specificatie te implementeren, maar om de organisatiestructuur van symbolizers, expressies en styling te adopteren. Dit voorkomt dat we het wiel opnieuw uitvinden, maar zorgt ervoor dat we ons beperken tot een basisstructuur die ons flexibiliteit biedt zonder de complexiteit van volledige implementatie.

"},{"location":"docs/developers/decisions/20241009-styling-of-layers/#implementatie","title":"Implementatie","text":"
classDiagram\n    namespace Netherlands3D {\n        class Layers\n    }\n    namespace Layers {\n        class LayerData {\n            +LayerStyle styles\n        }\n        class Styles\n    }\n\n    namespace Styles {\n    class LayerStyle {\n        +StylingRule stylingRules\n        +Metadata metadata\n    }\n\n    class Metadata {\n        + string name\n    }\n\n    class StylingRule {\n        +string name\n        +Symbolizer symbolizer\n        +Expression selector\n    }\n\n    class Expression {\n    }\n\n    class Symbolizer {\n        - Dictionary properties\n        ~ void GetProperty(string name)\n        ~ object SetProperty(string name, object value)\n    }\n\n    class CoreSymbolizerExtension {\n        +GetVisibility() bool\n        +SetVisibility(bool visible)\n        +GetOpacity() float\n        +SetOpacity(float opacity)\n    }\n\n    class VectorSymbolizerExtension {\n        +GetFill() Color\n        +SetFill(Color fillColor)\n    }\n    }\n\n    LayerData --> \"0..*\" LayerStyle\n    LayerStyle --> \"0..*\" StylingRule\n    LayerStyle --> \"0..1\" Metadata\n    StylingRule --> \"0..1\" Symbolizer\n    StylingRule --> \"0..1\" Expression\n    CoreSymbolizerExtension .. Symbolizer : extension of\n    VectorSymbolizerExtension .. Symbolizer : extension of\n

Let op

We wijken in de naamgeving af van de beschrijving in de OGC standaard bij de LayerStyle klasse, in de OGC standaard heeft deze enkel Style, maar om consistent te zijn in de applicatie en ter verduidelijking voegen wij het voorzetsel Layer toe om te benadrukken dat dit een style is behorende bij layers; vergelijkbaar met LayerPropertyData

"},{"location":"docs/developers/decisions/20241009-styling-of-layers/#opslag-van-stijlen-in-het-projectbestand","title":"Opslag van Stijlen in het Projectbestand","text":"

Elke layer in het projectbestand krijgt een nieuw veld genaamd \"styles\" waarin de stylinginformatie wordt opgeslagen. Dit veld verwijst naar een JSON-structuur die hetzelfde formaat is zoals gedefinieerd in hoofdstuk 17 van de OGC Cartographic Symbology - Part 1: Core Model & Encodings OGC-standaard. Hierdoor kunnen we stijlen opnieuw laden en heen en weer converteren tussen het interne representatiemodel en het JSON-bestand. Dit zorgt ervoor dat stijlen behouden blijven wanneer ze eenmaal gedefinieerd zijn.

Technische Vereisten voor Serialization

Omdat het hele projectbestand wordt geserialized met Newtonsoft's JSON.net, moet de klassestructuur van de symbolizers ook in dit format worden geserialized. Dit waarborgt strict typing en maakt het consistent met de rest van het project.

Voor de Symbolizer zal een aparte JsonConverter geschreven moeten worden die ervoor zorgt dat de waarden in de properties dictionary als losse velden direct in de JSON voor het symbolizer object verschijnen en niet dat een extra veld properties in de JSON terecht komt.

"},{"location":"docs/developers/decisions/20241009-styling-of-layers/#expressions-en-selectors","title":"Expressions, en Selectors","text":"

Bij het implementeren van expressions en selectors volgen we de structuur zoals beschreven in hoofdstuk 8.1.5 van de OGC Cartographic Symbology - Part 1: Core Model & Encodings. Net als bij de styling-eigenschappen, zullen we de expressie-taal niet in \u00e9\u00e9n keer volledig implementeren, maar stap voor stap, afhankelijk van de vereisten van user stories.

Voorbeeld

Een voorbeeld hiervan is een user story waarin we een groep features kleuren op basis van een attribuutwaarde. Voor deze implementatie hebben we alleen de volgende onderdelen nodig:

  • Een vergelijkingsoperator (alleen gelijkheid),
  • Een attribuut-identiteits-expressie (om het attribuut aan te duiden),
  • Een TextLiteral-expressie (om de letterlijke waarde te beschrijven).

Dit iteratieve proces maakt het mogelijk om gericht de functionaliteiten toe te voegen die direct noodzakelijk zijn voor de behoeften van de gebruikers.

"},{"location":"docs/developers/decisions/20241009-styling-of-layers/#risicos","title":"Risico's","text":"

De OGC Cartographic Symbology-standaard bevindt zich in een draft-stadium, wat betekent dat de specificaties nog kunnen veranderen. We accepteren dat onze implementatie niet volledig overeenkomt met de definitieve specificatie, en houden hier rekening mee in toekomstige aanpassingen.

"},{"location":"docs/developers/decisions/20241009-styling-of-layers/#appendix-a-woordenlijst","title":"Appendix A: Woordenlijst","text":"
  • OGC Cartographic Symbology-standaard: De standaard waarop het styling-systeem is gebaseerd.
  • Layer: Een verzameling van geografische objecten die in een bepaalde stijl worden weergegeven.
  • Styling: Het toekennen van visuele eigenschappen aan geografische objecten.
  • Style: Een verzameling van stylingregels die wordt toegepast op een layer.
  • Styling-eigenschap: Een specifieke visuele eigenschap, zoals kleur of lijnstijl, die wordt toegepast op een object.
  • Symbolizer: Een object dat styling-eigenschappen bevat voor lagen.
  • Symbologie: Een GIS-term voor het beschrijven van hoe geografische objecten visueel worden weergegeven.
  • Selector: Een conditie die bepaalt wanneer een symbolizer moet worden toegepast.
  • Expression: Een voorwaarde of waarde die bepaalt hoe en wanneer een bepaalde styling-eigenschap wordt toegepast.
  • Symbolizer-extensie: Uitbreidingen die nieuwe styling-eigenschappen toevoegen aan de Symbolizer.
  • Styling-eigenschap prefix: Een voorvoegsel toegevoegd aan eigen styling-eigenschappen om conflicten met OGC-gedefinieerde eigenschappen te voorkomen, zoals beschreven in hoofdstuk 8.1.7 van de OGC-specificatie.
"},{"location":"docs/developers/decisions/20241009-styling-of-layers/#appendix-b-voorbeeld-flow-voor-het-wijzigen-van-kleur","title":"Appendix B: Voorbeeld flow voor het wijzigen van kleur","text":"

Wanneer een gebruiker de kleur van een laag aanpast in de UI, doorloopt de applicatie de volgende stappen:

graph TD\n    A[Gebruiker past kleur aan in UI] --> B[UI haalt huidige laag op]\n    B --> C[UI zoekt standaard stylingregel op en haalt Symbology object op]\n    C --> D[UI roept SetFillColor aan op Symbology-object]\n    D --> E[Symbology slaat nieuwe kleur op in private dictionary]\n    subgraph Update van visualisatie\n        E --> F[Laag triggert update naar visualisatieklasse]\n        F --> G[Visualisatieklasse haalt Symbology op]\n        G --> H[Visualisatieklasse leest FillColor uit]\n        H --> I[Visualisatieklasse past nieuwe kleur toe]\n    end\n    I --> J[Visualisatie wordt bijgewerkt]
  1. Gebruiker past kleur aan in UI: De gebruiker kiest een nieuwe kleur voor een laag.
  2. UI haalt huidige laag en standaard stylingregel op: De UI haalt de geselecteerde laag en de standaard stylingregel op, inclusief het bijbehorende Symbology-object.
  3. Symbology-object bijwerken: De UI roept de SetFillColor method aan op het Symbology-object, dat de nieuwe kleur opslaat in een private dictionary.
  4. Update van visualisatie: De laag triggert een update naar de visualisatieklasse, die vervolgens het Symbology-object ophaalt, de nieuwe kleur uitleest, en deze toepast op de visualisatie.
  5. Visualisatie wordt bijgewerkt: De visualisatie wordt automatisch bijgewerkt om de nieuwe stijl van de laag te reflecteren.

Het updaten van de visualisatie is een herbruikbaar blok dat niet alleen in deze flow kan worden ingezet, maar ook bij het openen van een project. Door de scheiding tussen de symbologie data en de visualisatie kan, vergelijkbaar met de scheiding tussen LayerData en visualisatie, styling in het project uniform worden opgeslagen.

"},{"location":"docs/developers/features/embedding/","title":"Embedding Netherlands3D on Your Website","text":"

You can easily embed Netherlands3D on your own website using an iframe. This allows you to showcase the 3D representation of the Netherlands directly within your web pages. You can also customize the location and features displayed using a query string.

"},{"location":"docs/developers/features/embedding/#using-an-iframe","title":"Using an Iframe","text":"

To embed Netherlands3D with Rijksdriehoekformaat (EPSG:28992) coordinates, use the following HTML code snippet. Replace the src attribute value with the URL of the Netherlands3D digital twin you'd like to embed:

<iframe\n        src=\"https://netherlands3d.eu/twin/?origin=161088,503050,300&features=terrain,buildings,trees,sun\"\n        width=\"640\"\n        height=\"480\"\n        frameborder=\"0\"\n></iframe>\n
  • src: Set this attribute to the URL of the Netherlands3D digital twin. You can customize the location and features using the query string.

  • width and height: Adjust these attributes to define the dimensions of the iframe on your webpage. Make sure the width fits your design, and the height provides an optimal viewing experience.

  • frameborder: Set this attribute to \"0\" to remove the iframe border.

"},{"location":"docs/developers/features/embedding/#configuring-location-and-features","title":"Configuring Location and Features","text":"

You can customize the location and features displayed in your embedded Netherlands3D using a query string. In the query string, the origin parameter should follow the format origin=X,Y,elevation, where X and Y is the position of the camera in Rijksdriehoekformaat (EPSG:28992) coordinates and elevation is the desired viewing elevation.

Here's an example URL with a query string for your reference:

https://netherlands3d.eu/twin/?origin=161088,503050,300&features=terrain,buildings,trees,sun\n
  • origin: This parameter sets the initial view location in the Rijksdriehoekformaat (EPSG:28992) coordinate system. It follows the format origin=X,Y,elevation, where elevation is a value between 0 and 1400.

  • features: Use this parameter to specify which features to display in the digital twin. You can include multiple features, separated by commas.

"},{"location":"docs/developers/features/embedding/#using-the-setup-wizard","title":"Using the Setup Wizard","text":"

For an even easier way to generate the query string with the desired configuration, you can visit the Netherlands3D Setup Wizard (https://netherlands3d.eu/twin/). This interactive tool allows you to select the location, features, and other options, and it generates the corresponding query string for your iframe.

Once you have the query string, insert it into the src attribute of your iframe code, and you'll have a fully customized embedded Netherlands3D digital twin on your website.

"},{"location":"docs/developers/features/floating-origin/","title":"Working with large distances (Floating Origin)","text":"

The Floating Origin System is a solution designed to manage 3D world coordinates in Unity3D. It operates on the idea of retaining the real-world coordinates of objects while the origin within the computer-generated world adjusts. The Floating Origin System achieves this through the integral use of the following components:

"},{"location":"docs/developers/features/floating-origin/#overview-of-the-floating-origin-system","title":"Overview of the Floating Origin System","text":"

Before the deep-dive into the components of the Floating Origin system, a basic understanding of the concepts in this system is required:

"},{"location":"docs/developers/features/floating-origin/#real-world-coordinates-and-unity-positions","title":"Real world coordinates and Unity positions","text":"

In Unity, the transform component is responsible for tracking the position relative to Unity's (0,0,0) origin point, when working with large worlds -such as our real-world- this system no longer works due to floating point precision errors. To solve this, a second mechanic for tracking positions is introduced using real world coordinates in the WGS84 lat/long ECEF coordinate system.

As the distances expressed by real world coordinates are greater than Unity's floating point based transforms, it is needed to track the real world coordinate of the (0,0,0) origin -called the Floating Origin- and the coordinates of real world objects.

When the camera moves thus far away from the Floating Origin that floating point issues may occur, the origin will change its real-world coordinate to be nearer to the camera's real-world coordinate and shift the Unity position of all real-world objects to match the new real-world coordinate of Unity's (0,0,0) origin.

"},{"location":"docs/developers/features/floating-origin/#floating-origin","title":"(Floating) Origin","text":"

The Origin represents the real world coordinates of Unity's (0,0,0) origin and is responsible for tracking the distance of the Camera from Unity's (0,0,0) origin. If the Camera moves too far away, rendering and calculation anomalies can happen due to floating point precision issues and all real world objects -including the origin's own real-world coordinates- need to be shifted so that all elements are back into a safe distance in Unity's coordinate system.

"},{"location":"docs/developers/features/floating-origin/#shifting","title":"Shifting","text":"

Shifting is the term for when real world objects need to have their Unity position adjusted to get closer to the Floating Origin. Because the origin's Unity position never changes -it is always at 0,0,0- but its real-world coordinate can change, we need to respond to that by moving real-world objects' Unity position. As soon as this happened, all Game Objects should be within a safe distance from 0,0,0 again.

"},{"location":"docs/developers/features/floating-origin/#real-world-objects","title":"Real-world objects","text":"

Real-world objects -sometimes referred to as spatial objects- are game objects whose location is tied to the real-world and have a location in the real world. An example may be a windmill, where a UI element is an example of something that is not.

Real-world objects have a World Transform component that tracks the real-world position and orientation of the object and when a shift is emitted, they will reposition themselves or its parts.

"},{"location":"docs/developers/features/floating-origin/#world-transform","title":"World Transform","text":"

The World Transform tracks the real-world position and orientation for a real-world object. The World Transform also reacts to the Origin calling for a shift, but delegates the actual work to a World Transform Shifter component. This allows for objects to respond to shifts differently.

"},{"location":"docs/developers/features/floating-origin/#world-transform-shifter","title":"World Transform Shifter","text":"

The World Transform Shifter is the component that handles the shifting of a Game Object. Usually you do not need to provide a specific World Transform Shifter with a World Transform, in that case the default GameObjectWorldTransformShifter is used.

"},{"location":"docs/developers/features/floating-origin/#origin","title":"Origin","text":"

In the Floating Origin System, the Origin is an important concept referring to the focal point within the Unity game world. It is crucial for large scale projects where precision becomes a challenge.

The Origin is typically associated with its own separate GameObject. This GameObject serves as an indicator or marker of the origin point, and doesn't have any visual representation in the game environment. Its primary role is to maintain the game world's central point and handle shifts in that point to ensure accuracy and consistency in objects' locations in the world.

To use the Origin, create an empty GameObject in your scene. This empty GameObject often doesn't need any other components besides Origin. Then, attach the Origin script to this GameObject. Now, the Origin GameObject will continually monitor its distance from the camera (or a specified mainShifter object) while playing.

"},{"location":"docs/developers/features/floating-origin/#performance-optimizations-in-origin","title":"Performance Optimizations in Origin","text":"

Understanding and managing performance is key in any system, and the Floating Origin System includes several performance optimizations to ensure efficient operation.

"},{"location":"docs/developers/features/floating-origin/#using-sqrmagnitude-instead-of-magnitude","title":"Using sqrMagnitude Instead of Magnitude","text":"

The process of calculating distance often involves the operation of square roots, which introduce computational complexity. In the Floating Origin System, we use Unity's built-in sqrMagnitude method instead of magnitude for distance calculations.

The sqrMagnitude method calculates the square of the magnitude of a distance vector, which omits the need for a square root operation. Since the Origin uses a squared threshold for determining shifting necessity, this optimization aligns perfectly. Simply put, both the mainShifter's distance and the threshold are squared values, so no square root operation is needed, resulting in improved performance without comprising correct operation.

"},{"location":"docs/developers/features/floating-origin/#using-a-coroutine-to-wait-until-the-end-of-frame","title":"Using a Coroutine to Wait Until the End of Frame","text":"

The Floating Origin System uses Unity's Coroutine feature to postpone the shifting evaluation to the end of each frame. The shifting operation checks at the end of the frame if it needs to reposition the Origin based on the final position of the mainShifter.

This optimization minimizes unnecessary Origin shifting, as multiple changes to the mainShifter position within a single frame will only trigger the shifting operation once at the end of the frame. This approach effectively reduces computational load by minimizing the frequency of costly shifting operations.

"},{"location":"docs/developers/features/floating-origin/#the-origin-triggers-the-shifts","title":"The Origin triggers the shifts","text":"

The Origin in the Floating Origin System demonstrates an example of encapsulation, a fundamental principle of object-oriented programming. Instead of distributing responsibilities across multiple components (like making the camera responsible for calculating distances and triggering shifts), the Floating Origin System centralizes these responsibilities within the Origin.

Encapsulating Responsibilities

The Origin is responsible for continually monitoring its distance from the camera (or another defined mainShifter object). Using a pre-determined threshold, the Origin decides when the shift operation needs to occur. Once that threshold is surpassed, the Origin triggers a shift operation to reestablish itself closer to the camera or mainShifter. This design decision encapsulates the behavior within the Origin, making it a standalone component that can independently determine and respond to shifts.

Performance and Design Improvements

The encapsulation model presents several advantages:

  • Performance: By monitoring the mainShifter's distance at the end of each frame and shifting only when necessary, the Origin can optimize performance and reduce unnecessary shifts, irrespective of how many changes occur to the mainShifter during the frame.

  • Design Simplicity: Having the Origin act as the central point of control simplifies task delegation amongst components and makes the overall system easier to understand.

  • Encapsulation: By keeping related functions within the Origin, the design favors encapsulation, ensuring a clean system where components' responsibilities are clearly defined and do not blend.

"},{"location":"docs/developers/features/floating-origin/#worldtransform","title":"WorldTransform","text":"

An analogy: transforms and rigidbodies

To illustrate how the WorldTransform and their Shifters work, you can compare them to the way Transform and RigidBodies work in Unity. The Transform holds the position, rotation and scale and can be manipulated; but the actual physics interaction -movement forced by external factors- is accomplished through a RigidBody component, or even a CharacterController.

In Unity, you have one transform and one type of RigidBody/Character Controller; and with the WorldTransform this works similarly: the WorldTransform works like a Transform and records the position of the real-world object, where a WorldTransformShifter functions like a RigidBody and exerts force on the WorldTransform when a Shift happens.

The WorldTransform is effectively the real-world equivalent of an object's location within the Unity game world. When the Origin shifts, the WorldTransform is adjusted correspondingly. Meanwhile, Unity's native Transform class remains untouched. This results in two interconnected, but separate, coordinate systems.

Not all objects will need to handle a shift in the same way. The default is to shift the GameObject itself, but some objects are containers whose children need to shift and not themselves -example: tile handlers- or where transformation matrices are used instead of transform components -example: instanced objects-. By delegating this responsibility to a World Transform Shifter all real-world objects will track their coordinates in the same way but have different ways to react to shift events.

"},{"location":"docs/developers/features/floating-origin/#worldtransformshifter","title":"WorldTransformShifter","text":"

The WorldTransformShifter is meant to manage the effects of moving the Origin on GameObjects. Custom behaviors for different types of GameObjects when the Origin shifts can be defined by implementing the method ShiftTo in derived classes of WorldTransformShifter.

When it is not the GameObject itself whose position needs to change but a child or something not directly linked to the transform position, then you need to use one of the other strategies. See below for more information.

Tip

The WorldTransform and its Shifters is an implementation of the Strategy design pattern.

"},{"location":"docs/developers/features/floating-origin/#specialized-shifters","title":"Specialized Shifters","text":"

Specialized Shifters are used to manage Origin shifts for unique types of GameObjects not part of the Unity base package. These could be packages like Netherlands3D 3DTiles that have their unique behaviors. An example of this is ThreeDTilesWorldTransformShifter. This sub-class handles shifts for child GameObjects associated with the Content component. When an Origin shift happens, it iterates over all Content components and adjusts the positions of all their children.

"},{"location":"docs/developers/features/floating-origin/#usage","title":"Usage","text":"

To use the Floating Origin System within your Unity projects:

  1. Attach the Origin component to a GameObject that will act as the world origin.
  2. For objects that should maintain real-world positions, attach the WorldTransform component.
  3. For specialized objects, for example, objects from the Netherlands3D 3DTiles package, attach a specialized WorldTransformShifter, like ThreeDTilesWorldTransformShifter.
"},{"location":"docs/developers/features/geojson/","title":"GeoJSON","text":"

Netherlands3D offers support for importing GeoJSON data.

"},{"location":"docs/developers/features/geojson/#features","title":"Features","text":"
  • Currently only GeoJSON files are supported. The default file extensions are .json or .geojson. In case custom file types should be supported, these can be added in the FileOpen script (to allow selection of other file types) and in the FileTypeAdapter (to process the opened file).
  • The GeoJSON parsing is spread over multiple frames, (default is max 0.01s of parsing per frame) as to not freeze the application when importing large files. This duration can be changed by changing the variable maxParseDuration in GeoJSONLayer.cs
  • GeoJSON features of type Polygon, MultiPolygon, LineString, MultiLineString, Point, and MultiPoint are supported.
  • GeoJSON coordinates are passed to the eu.netherlands3d.coordinates package using the parsed CRS in order to convert them to Unity coordinates (see also the section on limitations for CRS limitations). A CRS object of type Linked CRS is currently not supported
  • A material can be assigned to the GeoJSONLayer that determines the appearance of the visualization.
  • An event is invoked when parsing fails, to allow for display of error messages on screen.
  • GeoJSON files can be uploaded and embedded in the NL3D Project as assets
  • URLs pointing to GeoJSON files -such as https://raw.githubusercontent.com/blackmad/neighborhoods/refs/heads/master/utrecht.geojson- will be added as a remote url so that they are reloaded every time a project opens
"},{"location":"docs/developers/features/geojson/#limitations","title":"Limitations","text":"

The GeoJSON support currently has the following limitations:

  • A LinkedCRS object is parsed correctly in the GeoJSONLayer, however it is currently not interpreted to provide a valid input for the visualization through the eu.netherlands3d.coordinates package.
  • All features of the Type Polygon and MultiPolygon are combined to form 1 layer in the application
  • All features of the Type LineString and MultiLineString are combined to form 1 layer in the application
  • All features of the Type Point and MultiPoint are combined to form 1 layer in the application
"},{"location":"docs/developers/packages/creating-and-managing/","title":"Creating and managing Packages","text":"

Netherlands3D is a framework where you, as developer, can pick the functionality that you need from a series of packages. This guide will empower you to develop your own packages by providing step-by-step instructions how you can create your own package, release it and last, but not least, publish it to OpenUPM.

"},{"location":"docs/developers/packages/creating-and-managing/#variables-placeholders","title":"Variables / Placeholders","text":"

Before we get started, let's define some terms as if they were variables. This documentation and the template files in the Gist mentioned in the chapter Creating a new package uses various placeholders. These can recognized as they are between brackets, for example {NAME}.

This table provides an overview of them and their meaning.

Name Explanation Example NAME The name of the code repository, this is usually the DISPLAY_NAME in PascalCase. It is recommended to limit the name to two words, if more words add clarity than that takes precedence. PeriodicSnapshots PACKAGE_NAME The package name as known in the package manager and/or package.json; always starts with eu.netherlands3d. and followed by the display name -or similar- in kebab-case eu.netherlands3d.periodic-snapshots DISPLAY_NAME The human-readable name of a package, shown in the README or in the Package Manager Periodic Snapshots REPOSITORY_NAME Alias of NAME, used to make explicit that we want the repository name there. PeriodicSnapshots VERSION The version number in the package.json, according to SemVer. Important: contrary to the git tag, this does not start with a letter v 1.0.1 DESCRIPTION A single-line short description describing the package, can also be used on Github as the description for the repository This package provides the means to do take a series of snapshots for specific moments throughout the year. LONG_DESCRIPTION The description of the package detailing what it is used for. USAGE_INFORMATION Documentation how to use this package. See existing packages for examples NOTE One or more entries in the CHANGELOG -according to Keep A Changelog- describing what has been added, changed, removed or deprecated in a package

This vocabulary and their definitions will help to have a consistent experience when working with packages.

"},{"location":"docs/developers/packages/creating-and-managing/#creating-a-new-package","title":"Creating a new package","text":"

When creating a new package, it is recommended to first create it as an embedded package in the Netherlands3D twin project. This will help to prototype and experiment until the point where it is stable enough to be distributed.

This can be done using the following steps:

  1. Clone the Netherlands3D Twin project, located at https://github.com/Netherlands3D/twin.

  2. Make sure Unity is not open while performing the following steps; it generally tends to crash when it updates while setting up a package.

  3. Create a directory for your embedded package in the Packages folder.

    Important

    The directory name MUST follow the structure eu.netherlands3d.[name] where name is in lower-case kebab-case.

  4. Copy the template files, located at https://gist.github.com/mvriel/8a8251b492d9d8f742da16667c49e412, and fill in the placeholders.

  5. Set up the package according to the following recommended directory structure: https://docs.unity3d.com/Manual/cus-layout.html

  6. Start Unity and allow for it to install the new package, you should see your new package in the \"Installed Packages\" section of the Package Manager with the tag Custom behind the name.

  7. When you want to add Scripts to this package: Make sure you have created the folder Runtime\\Scripts as a location to store them and add an Assembly Definition with the name {PACKAGE_NAME}.Runtime.

"},{"location":"docs/developers/packages/creating-and-managing/#promoting-an-embedded-package","title":"Promoting an embedded package","text":"

It takes time and effort to get a package into a usable and moderately stable condition. Because of this the recommended way of working is to first create an embedded package in the Twin project -like an incubator project- and once it is moderately stable: promote it to its own repository.

Promoting an embedded package is done by one of the project maintainers -if not alone for the fact that a repository needs to be made- and follows the following sequence of steps:

  1. Create a repository whose name matches the {NAME} placeholder, thus a PascalCase representation of the human-readable name.

  2. Move all files from the Packages\\{PACKAGE_NAME} folder in the Twin repository to the newly made repository's root folder

  3. Check and adjust, at least, the URL's for the LICENSE, README and CHANGELOG in the package.json.

  4. Release and publish the promoted package, see the chapters on releasing and publishing for the involved steps.

  5. In the Twin project, add a reference to {PACKAGE_NAME} to the package.json or install the package through the package manager.

    Important

    After a package is added to OpenUPM for the first time, it can take between 30 and 60 minutes for it to show up in the package listing.

Once a package is promoted, you can no longer edit it as part of the Twin project; in the next chapter Changing a Package it is described how you can change a promoted package without all too much hassle.

"},{"location":"docs/developers/packages/creating-and-managing/#changing-a-package","title":"Changing a package","text":"

To be written

"},{"location":"docs/developers/packages/creating-and-managing/#releasing-a-package","title":"Releasing a package","text":"

When you want to release a new version of a package you generally go through the following steps:

Tip

Before releasing, make sure you have tested your package in the Twin -or another Netherlands3D-based project- installed as a local package.

Especially important is:

  • verifying that the correct dependencies are set and installed
  • that the Assembly Definition can be used and
  • that the Assembly Definition does not depend on Assembly Definitions that are not a dependency of your package, especially Netherlands3D.Core.Runtime should be avoided

Failure to do so may cause extra work because once a package is released, it is impossible to change something in the release and a new release must be made.

  1. Go to the repository of your package. If there is none: see the chapter on Promoting an embedded package and do that first.

  2. Ensure the version number in the package.json is updated

  3. Check the CHANGELOG.md:

  4. Does it contain all changes since the last version?

  5. Is the top-entry [Unreleased]? Fill in the new version number and add a date, see: https://keepachangelog.com/en/1.0.0/#effort

  6. Ensure any changes in the above are in the main branch

  7. Go to \"Releases\" on Github (https://github.com/Netherlands3D/{NAME}/releases) and

  8. Draft a new release

  9. Click on \"Choose a tag\"
  10. Enter the version number from the package.json with a preceding letter v, for example: v1.0.1.
  11. Click on the option \"Create new tag: v{VERSION} on publish\"
  12. (optional) Add a release title and description
  13. Click on the button \"Publish Release\"

Once a new release/tag has been made, your new release of your package is all set! If it has already been published on OpenUPM before, no further action is needed. OpenUPM will automatically pick up on the new tag and make the new version available.

When the package has not been published on OpenUPM yet, now is a good time to do it.

"},{"location":"docs/developers/packages/creating-and-managing/#publishing-a-package-on-openupm","title":"Publishing a package on OpenUPM","text":"

When a package is released for the first time, it needs to be published by registering it on OpenUPM.

Tip

Before publishing, make sure you have tested your package in https://github.com/Netherlands3D/twin -or another Netherlands3D-based project- installed as a local package.

Especially important is:

  • verifying that the correct dependencies are set and installed
  • that the Assembly Definition can be used and
  • that the Assembly Definition does not depend on Assembly Definitions that are not a dependency of your package, especially Netherlands3D.Core.Runtime should be avoided

Failure to do so may cause extra work because once a package is published, it is inconvenient to unpublish it or change publication details such as the name.

To do so, you can take the following steps:

  1. Go to https://openupm.com.

  2. Click on the \"+\" button in the menu bar to add a new package

  3. Fill out the Github repository name in the intended field and click on \"Go\". A form expands where you can verify the package name, ReadMe location and more

  4. Enter your github username in the \"Discovered by\" field; this is used to base a fork of OpenUPM from and add the package as a pull request.

    Note

    The section \"Advanced\" is for advanced use when multiple packages are hosted in the same repository; at the moment this method is not recommended and as such that part of the submission form can be skipped

  5. Check the \"Promotion\" section if the package fits any of these categories, and check these.

  6. Click on the \"Verify Package\" button, all other fields can be left unchanged.

As soon as the steps above have completed, you are asked to fork the OpenUPM repository and to commit these changes in a feature branch. At this point you can use the \"Create pull request\" button to create a pull request to OpenUPM.

Tip

If this is your first time submitting a package to OpenUPM, the maintainer of OpenUPM needs to approve the Pull Request manually; this is generally done within 24 hours. Any subsequent pull requests will be automatically merged.

After the pull request is merged, it will take 30 to 60 minutes for OpenUPM to add the package to the registry.

"},{"location":"docs/developers/packages/creating-and-managing/#faq","title":"FAQ","text":"

Can I depend on the Netherlands3D.Core.Runtime assembly?

At time of writing, this assembly is within the code of the https://github.com/Amsterdam/Netherlands3D skeleton; meaning that this dependency only works when the package is used within a project that is based on this skeleton. This can be a problematic dependency since we are actively working on moving the contents of this skeleton into components.

Because of the above, it is not recommended for a published package to depend on the Netherlands3D.Core.Runtime assembly.

If code from this assembly is needed, it is recommended to extract this code into another package that you can depend on or duplicate it into your own package until such a package can be made.

For more information on this, see the question Can I depend on Assemblies that are not in my own package?.

Can I depend on Assemblies that are not in my own package?

You sure can! As long as these assemblies are in a package that is published in a Unity Package Registry such as OpenUPM, and that you have added that package as a dependency to your package.json file.

If you add an assembly whose package cannot be included in your package.json -either because it is not a package or a git-based package- then you will need to add installation instructions in the README. Without these instructions, any user of the package will have a missing assembly -and thus errors- without knowing how to fix it.

One of the packages does not show up in the Package Manager after publishing

Have you checked the minimum unity version in the package.json? If the minimum version is newer than your installed version, it will not be visible.

Why OpenUPM and not add packages through a Git URL?

Good question! When you add packages through a Git URL you lose certain features that packages hosted on a registry do have. This includes but is not limited to:

  1. Unable to update to a new version: when you use a Git tag/release, then you need to uninstall and reinstall the package when a new becomes available instead of just using the Update button.

  2. Git-url based packages cannot be used as dependencies: this means that if a package depends on another, with git urls you need to manually install the correct dependencies and face possible version conflicts with newer versions.

"},{"location":"docs/developers/ui/introduction/","title":"Introduction","text":"

Welcome to the foundational section of our technical documentation, where we introduce the user interface (UI) design system of Netherlands3D. This documentation is crafted to guide developers, designers, and contributors through the design architecture that shapes the user experience within our digital twin environment.

"},{"location":"docs/developers/ui/introduction/#purpose-of-the-design-system","title":"Purpose of the Design System","text":"

The design system serves as a comprehensive framework that harmonizes UI design and development, ensuring consistency, scalability, and efficiency across the application. It is the blueprint that orchestrates the visual and interactive elements, making the digital twin not only a technological marvel but also an intuitive and engaging experience for users.

"},{"location":"docs/developers/ui/introduction/#overview-of-the-ui-design-system","title":"Overview of the UI Design System","text":"

At the heart of our UI design system are two fundamental concepts: Blocks and Components. These elements are the building blocks of our application's interface, structured to offer clarity and flexibility in design and implementation. Inspired by the BEM (Block, Element, Modifier) methodology, our system disambiguates between blocks and components to streamline the development process and enhance the UI's modularity and reusability.

"},{"location":"docs/developers/ui/introduction/#blocks","title":"Blocks","text":"

Blocks are the larger, more general sections of the UI that function as the containers for components. They define the structural layout and set the stage for the detailed elements within. Blocks can be as broad as the entire application window or as specific as a user interaction panel.

"},{"location":"docs/developers/ui/introduction/#components","title":"Components","text":"

Components, on the other hand, are the more granular, reusable elements that reside within blocks. They are the nuts and bolts of the UI, encompassing buttons, sliders, text fields, and other interactive elements. Components are designed with flexibility in mind, allowing them to be adapted and reused across different blocks.

"},{"location":"docs/developers/ui/introduction/#objectives-of-this-documentation","title":"Objectives of This Documentation","text":"

The subsequent chapters of this documentation are dedicated to diving deeper into the specifics of blocks and components, outlining their characteristics, how they are designed, and how they interact within the UI framework of our digital twin platform. Our goals are to:

  • Clarify the distinction between blocks and components, providing a clear understanding of their roles within the UI design system.
  • Guide the design and development process, offering insights into best practices for creating and integrating blocks and components.
  • Facilitate scalability and customization, enabling developers and designers to extend the UI efficiently and creatively.

As we proceed, this documentation will serve as a reference for enhancing and expanding the user interface of Netherlands3D, ensuring that the developers and designers within the ecosystem are aligned.

In the next chapter, we will explore the concept of blocks in detail, examining their structure, functionality, and how they contribute to the overall user experience of the application.

"},{"location":"docs/developers/ui/keeping-our-documentation-current/","title":"Keeping Our Documentation Current","text":"

Important Note: While we strive to maintain this documentation as the definitive guide to our UI design system, the realm of digital development is one of constant evolution. To accommodate this, we ensure that the most up-to-date design resources and UI guidelines are housed in an internal Figma board. This board is regularly updated to reflect the latest design decisions, component updates, and UI patterns adopted by our team.

"},{"location":"docs/developers/ui/keeping-our-documentation-current/#accessing-the-latest-information","title":"Accessing the Latest Information","text":"

For the most current design specifications and UI elements, we encourage team members to refer to our internal Figma board. This resource is designed to be a living document, offering real-time updates and insights into our UI design strategy.

"},{"location":"docs/developers/ui/keeping-our-documentation-current/#requesting-documentation-updates","title":"Requesting Documentation Updates","text":"

We understand that despite our best efforts, there may be instances where this documentation does not fully capture the latest UI developments or design nuances. In such cases, we welcome requests for documentation updates.

By fostering a culture of continuous improvement and open communication, we aim to ensure that our UI design system supports the dynamic needs of our project and team. Whether you're a developer, a designer, or a project stakeholder, your contributions and feedback are invaluable to the ongoing refinement of Netherlands3D.

"},{"location":"docs/developers/ui/understanding-blocks/","title":"Understanding Blocks","text":"

In the Netherlands3D platform, our design system is structured for clear organization and effective scaling. Blocks form the fundamental part of this structure, acting as the primary containers for the interface's elements. This chapter focuses on blocks, explaining their purpose, features, and their role in creating a unified user experience in Netherlands3D.

"},{"location":"docs/developers/ui/understanding-blocks/#what-are-blocks","title":"What Are Blocks?","text":"

Blocks are the large-scale elements of our UI design system. They serve as the main sections that organize and contain various components. Think of blocks as the framework of a building, providing structure and defining the layout within our digital environment. In Netherlands3D, blocks have specific functions, such as guiding navigation, displaying information, facilitating user interactions, or showcasing data visually.

"},{"location":"docs/developers/ui/understanding-blocks/#features-of-blocks","title":"Features of Blocks","text":"
  • Modular: Blocks are designed as self-contained units that can be mixed and matched in different layouts, offering flexibility and reuse across the platform.

  • Scalable: They are built to support the platform's growth, making it easy to add new features or adapt to changing user needs without disrupting the existing design.

  • Consistent: Despite their variety, blocks maintain a unified design language and user experience across Netherlands3D.

"},{"location":"docs/developers/ui/understanding-blocks/#designing-blocks","title":"Designing Blocks","text":"

Creating blocks for Netherlands3D requires attention to:

  • User Needs: Design each block with the user's requirements in mind, ensuring it fulfills its intended purpose effectively.

  • Adaptability: Make blocks adaptable for different types of content and scenarios within the platform.

"},{"location":"docs/developers/ui/understanding-blocks/#managing-blocks","title":"Managing Blocks","text":"

The evolution of Netherlands3D involves regular updates to blocks, including additions, removals, or modifications. This process entails:

  1. Planning: Identifying the need based on user feedback or new functionalities.

  2. Design and Development: Crafting the block to fit Netherlands3D's design norms and technical specifications.

  3. Testing: Ensuring compatibility and performance across devices.

  4. Implementation: Incorporating the block into Netherlands3D and monitoring its effectiveness.

"},{"location":"docs/developers/ui/understanding-blocks/#distinction-between-blocks-and-components","title":"Distinction Between Blocks and Components","text":"

Blocks and components are fundamental elements of the Netherlands3D UI, but they serve distinct purposes.

Blocks are the overarching sections that structure and organize the user interface, similar to the chapters in a book. They are designed for flexibility, accommodating a variety of components to fulfill different functionalities within the platform. Blocks ensure a consistent layout and design language across Netherlands3D, providing a cohesive user experience.

Components, on the other hand, are the detailed, interactive or static elements such as buttons, text fields, and sliders that reside within blocks. These elements are highly customizable and reusable, designed to perform specific functions or convey particular information. Components depend on blocks for context and placement, but their modularity allows them to be used in multiple scenarios across the platform.

The primary distinction lies in their scale and function: blocks form the UI's structural foundation, while components populate these structures with content and interactivity. Understanding this difference is crucial for efficient design and development within Netherlands3D, ensuring a robust and user-friendly interface.

"},{"location":"docs/developers/ui/understanding-blocks/#conclusion","title":"Conclusion","text":"

Blocks are foundational to the Netherlands3D UI design system, offering the structural basis for our digital twin platform. By leveraging blocks effectively, we can ensure a robust, user-friendly interface that reflects the innovative spirit of digital twin technology.

The following chapter will detail the components within these blocks, including their design and role in enhancing the interactive experience of Netherlands3D.

"},{"location":"docs/developers/ui/understanding-components/","title":"Understanding Components","text":"

In this chapter, we delve deeper into the essence of components\u2014the dynamic and interactive elements that enable users to engage with the Netherlands3D platform effectively. Components are the individual pieces of the user interface, such as buttons, sliders, dropdown menus, and data visualization charts, which users interact with directly.

"},{"location":"docs/developers/ui/understanding-components/#what-are-components","title":"What Are Components?","text":"

Components are the versatile and interactive building blocks housed within the UI's structural blocks. They range from input fields (where users can enter data), buttons (like 'Submit' or 'Cancel'), sliders (for adjusting settings or zoom levels), to more complex elements such as the minimap (allowing users to explore).

"},{"location":"docs/developers/ui/understanding-components/#features-of-components","title":"Features of Components","text":"
  • Reusability: Designed for reuse, components like toggle switches for on/off settings ensure consistency and streamline development across Netherlands3D.

  • Customization: Components can be tailored for different informational purposes to adhere to the specific aesthetic of the platform.

  • Interactivity: Interactive components, such as accordions, for customization features or detailed content display, to engage users directly, offering a dynamic experience.

"},{"location":"docs/developers/ui/understanding-components/#designing-components","title":"Designing Components","text":"

The design of components focuses on the user's needs, aiming for clarity, efficiency, and aesthetic harmony:

  • User-Centric Design: Components are crafted to be intuitive and accessible for easy navigation or with validation to guide user input.

  • Aesthetic Consistency: Despite their diversity, components maintain the Netherlands3D's cohesive design language,

  • reinforcing brand identity.
"},{"location":"docs/developers/ui/understanding-components/#managing-components","title":"Managing Components","text":"

The lifecycle of components in Netherlands3D involves continuous evaluation and refinement:

  1. Identification of Needs: New functionalities might require the development of novel components interaction with the 3D environment or its data.

  2. Rigorous Testing: Components undergo extensive testing.

  3. Iterative Improvement: Feedback might lead to the evolution of components, such as enhancing them for better data visualization or for improved usability.

"},{"location":"docs/developers/ui/understanding-components/#conclusion","title":"Conclusion","text":"

Components are vital for the interactive and functional richness of the Netherlands3D platform. Each component plays a crucial role in engaging users and facilitating a smooth experience. Through careful design and management, components ensure Netherlands3D remains an intuitive, efficient, and visually appealing platform.

"},{"location":"docs/developers/ui/blocks/property-panel-and-sections/","title":"Properties","text":""},{"location":"docs/developers/ui/blocks/property-panel-and-sections/#design","title":"Design","text":""},{"location":"docs/developers/ui/blocks/property-panel-and-sections/#usage","title":"Usage","text":"

The \"Properties\" block in Netherlands3D serves as a critical interface element, allowing users to interact with and modify the attributes of various layers within the application. This documentation provides insights into the design considerations and implementation strategies for this block, ensuring a seamless integration within the Netherlands3D platform.

This block enables users to access and adjust the transformation properties (position, rotation, scale) of selected 3D objects or layers. Additionally, it provides access to specific attributes for certain objects, such as Windmills, which may include properties like axis height or rotor diameter.

A screenshot of a windmill with an open properties panel"},{"location":"docs/developers/ui/blocks/property-panel-and-sections/#anatomy","title":"Anatomy","text":"Anatomical breakdown of the properties block

The Properties panel is constructed using a Card component, organized into several key areas for intuitive user interaction:

  1. Card: The overarching container for the Properties panel, designed for clarity and cohesion.
  2. Card Header: Displays the Properties icon and the term \"Eigenschappen,\" indicating the panel's purpose.
  3. Section: Each layer or object type, such as Windmills, has its dedicated section within the card.
  4. Section Header: Identifies the specific layer or object type, e.g., \"Windmolen\" for Windmills, guiding users to the relevant properties.
  5. Section Content: Contains form elements for adjusting the specific properties of the selected layer or object. This area is designed for direct interaction, allowing users to modify attributes like axis height or rotor diameter.
"},{"location":"docs/developers/ui/blocks/property-panel-and-sections/#design-principles","title":"Design Principles","text":"
  • Clarity: The use of a card-based layout ensures that the Properties panel is easily navigable and understandable, with distinct sections for different layers or objects.
  • Consistency: Maintaining a uniform structure across different sections ensures that users have a cohesive experience, regardless of the variety of objects or layers they interact with.
  • Immediate Feedback: Interactions within the Properties panel are designed to provide immediate visual feedback in the Netherlands3D environment, emphasizing the panel's role in direct manipulation of objects and layers.
"},{"location":"docs/developers/ui/blocks/property-panel-and-sections/#interaction","title":"Interaction","text":"

Users interact with the Properties panel by selecting an object or layer within the Netherlands3D platform, and clicking on the properties button with that layer.

Upon opening, the panel updates to display the relevant sections for the chosen item. Adjustments made within the form elements of a section are immediately applied, allowing users to see the effects of their changes in real-time. This design supports a tactile and intuitive experience, encouraging exploration and modification within the digital twin environment.

"},{"location":"docs/developers/ui/blocks/property-panel-and-sections/#implementation","title":"Implementation","text":""},{"location":"docs/developers/ui/blocks/property-panel-and-sections/#overview","title":"Overview","text":"

The Properties panel's functionality in the Netherlands3D platform is achieved through a combination of Unity prefabs and scripting. This implementation ensures dynamic interaction within the UI, allowing for the seamless addition of properties sections based on the selected layer or object.

"},{"location":"docs/developers/ui/blocks/property-panel-and-sections/#prefab-structure","title":"Prefab Structure","text":"
  • Main Properties Prefab: The core of the Properties panel, this prefab is a part of the default canvas and acts as the container for all dynamically added sections.

  • Section Prefabs: Individual prefabs for each type of section (e.g., Windmill properties) that can be instantiated within the main Properties panel.

"},{"location":"docs/developers/ui/blocks/property-panel-and-sections/#scripting-and-interfaces","title":"Scripting and Interfaces","text":""},{"location":"docs/developers/ui/blocks/property-panel-and-sections/#ipropertysection-interface","title":"IPropertySection Interface","text":"

An essential element in the dynamic nature of the Properties panel is the IPropertySection interface. This interface allows for the detection and instantiation of properties sections based on the presence of specific MonoBehaviours within a layer's prefab.

  • Detection and Instantiation: A script running in the layer system detects MonoBehaviours that implement the IPropertySection interface on a layer's prefab being added to the scene. Upon detection, the AddToProperties method of the interface is called.

  • Factory Behavior: Implementations of the IPropertySection can act as factories, instantiating the appropriate section prefab and adding it to the Properties panel as a \"slot\". The AddToProperties method is being provided with the properties content area to which the section can be added.

"},{"location":"docs/developers/ui/blocks/property-panel-and-sections/#addtoproperties-method","title":"AddToProperties Method","text":"

The AddToProperties method is where the logic for instantiating the section prefab and integrating it into the Properties panel is defined. This method allows for flexibility in the UI, permitting custom designs per section while adhering to the overall design guidelines.

Example

"},{"location":"docs/developers/ui/blocks/property-panel-and-sections/#example-windmill-property-section","title":"Example: Windmill Property Section","text":"

The Windmolen prefab, used for the Windmill implementation in the Object Library, includes a MonoBehaviour named WindmillPropertySectionInstantiator that demonstrates this system in action.

  • Functionality: The WindmillPropertySectionInstantiator responds to the addition of a Windmill object and instantiates a dedicated section within the Properties panel. This section is specifically designed to bind and display the properties of the windmill, such as axis height or rotor diameter.

  • Binding: The script binds the windmill's data to the instantiated section, ensuring that any adjustments made within the Properties panel are immediately reflected in the windmill's properties within the scene.

"},{"location":"docs/developers/ui/blocks/property-panel-and-sections/#implementation-steps","title":"Implementation Steps","text":"

Given an existing layer's prefab:

Implement the IPropertySection interface: For each layer requiring a properties section, create a MonoBehaviour that implements IPropertySection and defines how its section prefab should be instantiated and added to the Properties panel.

"},{"location":"docs/developers/ui/blocks/property-panel-and-sections/#design-adherence","title":"Design Adherence","text":"

While implementing sections, it's crucial to adhere to the established design rules to maintain consistency across the Properties panel. However, the flexibility of this system allows for the introduction of unique UI elements where necessary to better represent specific properties.

"},{"location":"docs/developers/ui/components/accordion/","title":"Accordion","text":""},{"location":"docs/developers/ui/components/accordion/#design","title":"Design","text":""},{"location":"docs/developers/ui/components/accordion/#usage","title":"Usage","text":"

The Accordion component in the Netherlands3D platform is a versatile UI element designed to efficiently manage and display hierarchical content in a compact form. Featuring a title, an optional label, and a toggle mechanism indicated by a chevron, the Accordion allows for an organized presentation of content sections, each with its dedicated header to signify its integration within the Accordion. This component is ideal for:

  • Settings Menus: Grouping related settings or options to streamline user navigation, such as layer property sections.

  • Grouping Content: Grouping related content to focus on related information, such as quality indicator dossiers.

  • Interactive FAQs: Presenting frequently asked questions in a concise, expandable format.

"},{"location":"docs/developers/ui/components/accordion/#modes-of-operation","title":"Modes of Operation","text":"
  1. Fixed Mode: This mode locks the Accordion in an always-open state, without the option to collapse or expand. The absence of a chevron signifies its static nature.

  2. Multiple Open Mode: Allows for multiple Accordions within a group to be opened or closed independently. The chevron points up when opened, indicating a \"roll-up\" action, and down when closed, suggesting a \"roll-down\" action.

  3. Single Open Mode: In a group of Accordions, only one can be open at a time. Opening another Accordion automatically closes the previously open one. This mode features a chevron pointing down when closed; and when open, the chevron is hidden, supplemented by an additional outline to highlight the Accordion is considered to be an active -or selected- Accordion.

"},{"location":"docs/developers/ui/components/accordion/#anatomy","title":"Anatomy","text":"Anatomical breakdown of the Accordion component
  1. Title: A text representative of the Accordion's content.

  2. Icon (Optional): Provides additional context or classification.

  3. Expand/Contract Toggle (Optional): A chevron-based toggle mechanism indicating the Accordion's state and possible actions.

  4. Content Section: The expandable area containing detailed content or components.

  5. Content Header: A header within the content section styled specifically to emphasize its connection to the Accordion.

"},{"location":"docs/developers/ui/components/accordion/#design-principles","title":"Design Principles","text":""},{"location":"docs/developers/ui/components/accordion/#title-and-icon","title":"Title and Icon","text":"

The title provides a succinct summary of the content contained within the Accordion, while the optional icon offers additional context or categorization.

Both elements are designed to be immediately visible to enhance usability and guide the user's navigation through the content sections.

"},{"location":"docs/developers/ui/components/accordion/#expandcontract-toggle","title":"Expand/Contract Toggle","text":"

The Expand/Contract Toggle includes a chevron symbol that visually communicates the Accordion's state and possible actions to the user. The direction of the chevron indicates the effect of the interaction (down for roll-down, up for roll-up).

The toggle may be absent when there is no action available, as can be the case for the fixed mode of operation or when no roll-up is provided because one element always needs to be open.

"},{"location":"docs/developers/ui/components/accordion/#content-section","title":"Content Section","text":"

The content section beneath each Accordion title is designed for expandability, housing detailed information or additional UI components.

Optionally, a specific header within the content section further emphasizes its association with the Accordion.

"},{"location":"docs/developers/ui/components/accordion/#best-practices","title":"Best Practices","text":"
  • Clarity: Ensure the title and label (if used) are clear and informative, guiding the user's understanding of the content within.

  • Intuitive Interaction: Design the Expand Toggle for easy recognition and interaction, with chevron icons that intuitively indicate the possible actions.

  • Consistency: Maintain visual and functional consistency across all Accordion components, especially within groups operating in multiple or single open modes.

"},{"location":"docs/developers/ui/components/accordion/#implementation","title":"Implementation","text":"

Info

As of writing, there is no component in Unity available to provide the Accordion component. This part of the documentation should be updated as soon as one is available.

"},{"location":"docs/developers/ui/components/cards/","title":"Cards","text":""},{"location":"docs/developers/ui/components/cards/#usage","title":"Usage","text":"

Cards are surfaces that display content and actions on a single topic.

They should be easy to scan for relevant and actionable information. Elements, like text and images, should be placed on them in a way that clearly indicates hierarchy.

"},{"location":"docs/developers/ui/components/cards/#anatomy","title":"Anatomy","text":"

Info

The contents of this chapter need to be written

"},{"location":"docs/developers/ui/components/double-sliders/","title":"Double Slider","text":""},{"location":"docs/developers/ui/components/double-sliders/#design","title":"Design","text":""},{"location":"docs/developers/ui/components/double-sliders/#usage","title":"Usage","text":"

Similar to the slider component, the double slider component in the Netherlands3D platform provides a graphical interface element enabling users to select a range between two values from a predefined scale. Accompanied by two Text Fields, this component allows for both graphical adjustment and direct numerical input, ensuring a versatile and user-friendly experience. The main difference between the double slider and the regular slider is that the double slider has two handles and input fields, defining the minimum and maximum value of the range.

"},{"location":"docs/developers/ui/components/double-sliders/#immediate-effects","title":"Immediate effects","text":"

Changes made with double sliders are immediate, allowing the user to make slider adjustments while determining a selection. Sliders shouldn\u2019t be used to adjust settings with any delay in providing user feedback.

"},{"location":"docs/developers/ui/components/double-sliders/#current-state","title":"Current state","text":"

Sliders reflect the current state of the settings they control.

"},{"location":"docs/developers/ui/components/double-sliders/#anatomy","title":"Anatomy","text":"Anatomical breakdown of the double slider component
  1. Slider Track: The horizontal line along which the double slider thumb moves, representing the range of possible values.
  2. Minimum Slider Thumb: The circular control that users drag to select the minimum value on the slider track.
  3. Minimum Text Field (Optional): Adjacent to the double slider, this field displays the currently selected minimum value and allows for manual entry. It can be configured as readonly or omitted based on specific requirements.
  4. Maximum Slider Thumb: The circular control that users drag to select the maximum value on the slider track.
  5. Maximum Text Field (Optional): Adjacent to the double slider, this field displays the currently selected maximum value and allows for manual entry. It can be configured as readonly or omitted based on specific requirements.
"},{"location":"docs/developers/ui/components/double-sliders/#implementation","title":"Implementation","text":"

This chapter guides you through adding the double slider prefab to your scene, configuring its behavior, and responding to user input.

"},{"location":"docs/developers/ui/components/double-sliders/#adding-the-slider-to-your-scene","title":"Adding the Slider to Your Scene","text":"
  1. Prefab Placement: Locate the double slider prefab in the project's asset directory. Drag and drop the prefab into your scene or hierarchy where you need the slider component to appear.

  2. Initial Setup: By default, the double slider comes with linked Text Fields for numerical input. Together, they provide a cohesive component for value selection.

"},{"location":"docs/developers/ui/components/double-sliders/#configuring-the-slider","title":"Configuring the Slider","text":""},{"location":"docs/developers/ui/components/double-sliders/#listening-to-value-changes","title":"Listening to Value Changes","text":"

Use the Unity Editor to attach a listener function to the slider's onMinValueChanged and onMaxValueChanged events. These functions will be called whenever the slider's minimum or maximum value changes, respectively, whether through direct manipulation of the double slider or numerical entry in the Text Fields.

"},{"location":"docs/developers/ui/components/double-sliders/#customizing-the-text-field","title":"Customizing the Text Field","text":"
  • Readonly Option: The DoubleSlider MonoBehaviour includes a readonly boolean. When set to true, the Text Fields becomes readonly, allowing users to see the value but preventing manual entry. This is useful in situations where you want to restrict input to slider manipulation only.

  • Omitting the Text Field: If your use case does not require numerical input, you can deactivate the Text Fields entirely. Simply set the Text Field GameObjects to inactive, and the double slider will automatically expand to fill the space, maintaining the UI's visual continuity.

"},{"location":"docs/developers/ui/components/sliders/","title":"Slider","text":""},{"location":"docs/developers/ui/components/sliders/#design","title":"Design","text":""},{"location":"docs/developers/ui/components/sliders/#usage","title":"Usage","text":"

The slider component in the Netherlands3D platform provides a graphical interface element enabling users to select a value or range from a predefined scale. Accompanied by a Text Field, this component allows for both graphical adjustment and direct numerical input, ensuring a versatile and user-friendly experience.

"},{"location":"docs/developers/ui/components/sliders/#immediate-effects","title":"Immediate effects","text":"

Changes made with sliders are immediate, allowing the user to make slider adjustments while determining a selection. Sliders shouldn\u2019t be used to adjust settings with any delay in providing user feedback.

"},{"location":"docs/developers/ui/components/sliders/#current-state","title":"Current state","text":"

Sliders reflect the current state of the settings they control.

"},{"location":"docs/developers/ui/components/sliders/#anatomy","title":"Anatomy","text":"Anatomical breakdown of the slider component
  1. Slider Track: The horizontal line along which the slider thumb moves, representing the range of possible values.
  2. Slider Thumb: The circular control that users drag to select a value on the slider track.
  3. Text Field (Optional): Adjacent to the slider, this field displays the currently selected value and allows for manual entry. It can be configured as readonly or omitted based on specific requirements.
"},{"location":"docs/developers/ui/components/sliders/#implementation","title":"Implementation","text":"

This chapter guides you through adding the slider prefab to your scene, configuring its behavior, and responding to user input.

"},{"location":"docs/developers/ui/components/sliders/#adding-the-slider-to-your-scene","title":"Adding the Slider to Your Scene","text":"
  1. Prefab Placement: Locate the slider prefab in the project's asset directory. Drag and drop the prefab into your scene or hierarchy where you need the slider component to appear.

  2. Initial Setup: By default, the slider comes with a linked Text Field for numerical input. Together, they provide a cohesive component for value selection.

"},{"location":"docs/developers/ui/components/sliders/#configuring-the-slider","title":"Configuring the Slider","text":""},{"location":"docs/developers/ui/components/sliders/#listening-to-value-changes","title":"Listening to Value Changes","text":"

Use the Unity Editor to attach a listener function to the slider's onValueChanged event. This function will be called whenever the slider's value changes, whether through direct manipulation of the slider or numerical entry in the Text Field.

"},{"location":"docs/developers/ui/components/sliders/#customizing-the-text-field","title":"Customizing the Text Field","text":"
  • Readonly Option: The PropertySlider MonoBehaviour includes a readonly boolean. When set to true, the Text Field becomes readonly, allowing users to see the value but preventing manual entry. This is useful in situations where you want to restrict input to slider manipulation only.

  • Omitting the Text Field: If your use case does not require numerical input, you can deactivate the Text Field entirely. Simply set the Text Field GameObject to inactive, and the slider will automatically expand to fill the space, maintaining the UI's visual continuity.

"},{"location":"docs/developers/ui/components/text-fields/","title":"Text Field","text":""},{"location":"docs/developers/ui/components/text-fields/#design","title":"Design","text":""},{"location":"docs/developers/ui/components/text-fields/#usage","title":"Usage","text":"

\"Text Fields\" in the Netherlands3D platform allow users to input or modify text information. They can be used singularly or in combination with other components like a Slider or Form Label in the Properties block. They're an essential part of Netherlands3D's user interface, providing users with interactive fields to enter or manipulate various properties.

"},{"location":"docs/developers/ui/components/text-fields/#parts","title":"Parts","text":"

The Text Field component comprises two main parts:

  1. Form Label: The descriptive text associated with the Text Field, explaining what value is being manipulated. This is a reusable part and can be shared with other components like the Slider.

  2. Form Field: It serves as the component for user text input or changing text information. It is styled to account for different states like Idle, Focus, Pressing, and Read-Only.

"},{"location":"docs/developers/ui/components/text-fields/#anatomy","title":"Anatomy","text":"

Note

Add an image to showcase the anatomy of a form field and elaborate some more on this in the text

  1. Form Label: Positioned above or adjacent to the Form Field, providing a textual description of the information required or displayed.

  2. Form Field: The interactive text input area where users can enter or modify data. It transitions between various states (Idle, Focus, Pressed, Read-only) to visually communicate its current mode of interaction.

"},{"location":"docs/developers/ui/components/text-fields/#design-principles","title":"Design Principles","text":"
  • Clarity: Labels are clear and informative, ensuring users understand what information is required.

  • Feedback: Built-in states (Idle, Focused, Pressed, and Read-Only) provide immediate visual feedback to users, aiding an interactive experience.

"},{"location":"docs/developers/ui/components/text-fields/#implementation","title":"Implementation","text":"

The Text Field is modeled using Unity prefabs and scripting, offering dynamic interaction within the user interface, allowing for adaptive properties adjustment based on user input.

"},{"location":"docs/developers/ui/components/text-fields/#adding-a-text-field-to-your-scene","title":"Adding a Text Field to Your Scene","text":"
  • Prefab Placement: Locate the Text Field prefab in the project's asset directory. Drag and drop the prefab into your scene where you need the Text Field component to appear.
"},{"location":"docs/developers/ui/components/text-fields/#configuring-the-text-field","title":"Configuring the Text Field","text":"

You can utilize the Unity Editor to add listener functions that get triggered on text value change events, enabling you to manipulate and use the user-input text as per application needs.

"},{"location":"docs/organisations/","title":"Voor organisaties","text":""},{"location":"docs/organisations/#onder-ontwikkeling","title":"Onder ontwikkeling","text":"

We werken momenteel aan de ontwikkeling van deze sectie om een uitgebreide bron van informatie te bieden. Het doel is om organisaties te ondersteunen met gidsen, best practices en antwoorden op veelgestelde vragen die essentieel zijn voor het gebruik van het Netherlands3D platform.

In de tussentijd moedigen we u aan om contact met ons op te nemen bij vragen met betrekking tot het platform.

Blijf op de hoogte voor updates en verbeteringen aan deze sectie. We kijken ernaar uit om te voorzien in de informatie en hulpmiddelen die nodig zijn om goed gebruik te maken van Netherlands3D.

"},{"location":"articles/archive/2024/","title":"2024","text":""},{"location":"articles/nieuws/","title":"Nieuws","text":""}]} \ No newline at end of file diff --git a/site.webmanifest b/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..0f8724e --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 0000000..a2964eb Binary files /dev/null and b/sitemap.xml.gz differ diff --git a/stylesheets/extra.css b/stylesheets/extra.css new file mode 100644 index 0000000..49df1cb --- /dev/null +++ b/stylesheets/extra.css @@ -0,0 +1,183 @@ +.md-typeset h2 { + margin-top: 2rem; +} + +/*****************************************************************************/ +/** Full-width tricks ********************************************************/ +/*****************************************************************************/ + +.md-container { + /** + We use tricks to create a full-width background color, + but hiding the overflow prevents the page from breaking + **/ + overflow: hidden; +} + +.md-header__button.md-logo { + display: flex; + justify-content: center; + align-items: center; + padding: 0; +} + +.md-header__button.md-logo img { + height: 1.8rem; +} + +/*****************************************************************************/ +/** HERO *********************************************************************/ +/*****************************************************************************/ + +.hero { + margin-top: -1.5rem; + margin-bottom: 1.5rem; + background-image: url('../images/hero.png'); + background-position: center; + background-repeat: no-repeat; + background-size: cover; + height: 24rem; +} + +.hero p { + margin: 0; + padding: 0; +} + +@media screen and (min-width: 425px) { + .hero { + margin: 0 calc(50% - 50vw) -3rem; + position: relative; + top: -2.5rem; + } +} + +/*****************************************************************************/ +/** HERO Teaser **************************************************************/ +/*****************************************************************************/ + +.teaser { + background: var(--md-primary-fg-color); +} + +.teaser h1 { + display: none; +} + +@media screen and (min-width: 425px) { + .teaser { + margin-top: 0; + position: absolute; + top: 15vw; + padding: .75rem 1.5rem .75rem; + max-width: 20rem; + border-radius: .25rem; + border: 1px solid var(--md-default-fg-color--lightest); + } +} + +@media screen and (min-width: 768px) { + .teaser { + top: 7.5vw; + } +} + +@media screen and (min-width: 1220px) { + .teaser { + top: 10vw; + } +} + +/*****************************************************************************/ +/** Photo Cards - Grid Cards with photos filling the card to the border ******/ +/*****************************************************************************/ + +.photo-cards { + li > p:first-of-type { + margin: -.8rem -.8rem 0 -.8rem; + margin-top: -.8rem !important; + } +} + +/*****************************************************************************/ +/** Full Width Colored Sections **********************************************/ +/*****************************************************************************/ + +.full-width { + position: relative; + padding: 1rem 0; +} + +.full-width::before { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: -50vw; + right: -50vw; + background-color: #f7f7f7; + z-index: -1; +} + +.md-content__inner > .full-width:last-child { + margin-bottom: -2rem; +} + + +/*****************************************************************************/ +/** Specialised styling for the 'In Samenwerking Met' card section ***********/ +/*****************************************************************************/ + +.in-cooperation-with { + /* make sure heading padding doesn't collapse, but don't add a significant padding ourselves */ + padding-top: 0.1rem; + margin-top: 2.5rem; +} +.md-typeset .in-cooperation-with__partners { + grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr)); +} +.md-typeset .in-cooperation-with__partners li { + display: flex !important; + align-items: center; + justify-content: center; +} +.md-typeset .in-cooperation-with__partners img { + max-height: 150px; + filter: grayscale(100%); + opacity: 0.6; +} +.md-typeset .in-cooperation-with__partners img:hover { + filter: grayscale(0%); + opacity: 1; +} + +/*****************************************************************************/ +/** Two column flex layout ***************************************************/ +/*****************************************************************************/ + +.two-column { + display: flex; + flex-direction: column; +} + +.two-column + .two-column { + margin-top: 1.5rem; + border-top: 2px dotted var(--md-default-fg-color--lighter); + padding-top: 1.5rem; +} + +@media screen and (min-width: 768px) { + .two-column { + gap: 2rem; + flex-direction: row; + } + + .two-column h2, + .two-column h3 + { + flex: 0 0 16.66667%; + + margin-top: .7rem; + padding-top: 0; + } +}

To embed Netherlands3D with Rijksdriehoekformaat (EPSG:28992) coordinates, use the following +HTML code snippet. Replace the src attribute value with the URL of the Netherlands3D digital twin you'd like to embed: